Angular保存/恢复组件状态

5
我正在开发一个使用多标签布局的Angular 7应用程序。每个标签页包含一个组件,该组件可以引用其他嵌套组件。
当用户选择新的/另一个标签页时,当前标签页上显示的组件将被销毁(我不仅仅是隐藏它们,并且自从打开的标签页可能很多,这对我来说似乎是更快的解决方案)。由于加载组件可能很耗费时间(可能需要从API检索大量数据),因此我正在尝试在选项卡容器中保持每个组件状态(它将保持整个应用程序生命周期)。
一切都按预期工作,但我认为我已经使用了很多代码来满足我的需求,希望一切都能更简单(并且更少出错)。
下面是我如何实现它的摘录。首先,我创建了一个抽象类,每个选项卡中的组件都应该扩展该类。
export abstract class TabbedComponent implements OnInit, OnDestroy {
  abstract get componentName(): string;

  tab: LimsTab;
  host: TabComponent;
  private _componentInitialized = false;

  constructor(
    private _host: TabComponent,
  ) {
    this.host = _host;
  }

  get componentInitialized(): boolean {
    return this._componentInitialized;
  }

  ngOnInit(): void {
    this.tab = this.host.tab;
    this.loadDataContext();
    this._componentInitialized = true;
  }

  ngOnDestroy(): void {
    this.saveDataContext();
  }

  get dataContext() {
    return this.tab.dataContext;
  }

  protected abstract saveDataContext(): void;
  protected abstract loadDataContext(): void;
}

loadDataContextsaveDataContext 在具体组件中实现,包含保存/检索组件实例值的逻辑。此外,我使用了 _componentInitialized 来确定组件何时调用其生命周期钩子 OnInit:组件可以从其父组件接收 @Inputs,并且根据我所见,ngOnChangesOnInit 之前触发。

以下是具体实现示例:

@Component({
   ...
})
export class MyComponent extends TabbedComponent implements OnChanges {

  private qualityLotTests: QualityLotTestListItem[];
  private _selectedTest: QualityLotTestListItem;

  @Input()
  selectedQualityLot: string;

  @Output()
  selectedTest: EventEmitter<QualityLotTestListItem> = new EventEmitter<QualityLotTestListItem>();

  constructor(_host: TabComponent, private qualityLotService: QualityLotService) {
    super(_host);
  }

  get componentName(): string {
    return 'QualityLotTestListComponent';
  }

  ngOnChanges(changes: SimpleChanges): void {
    if (this.componentInitialized) {
      // before calling loadData I need to restore previous data (if any) 
      // this allow me to undestrand if loaded data filters has changed 
      // I know I restored it if onInit has executed

      // if componentInitialized -> onInit has been executed and input parameters passed from parent are changed
      this.loadData();
    }
  }

  protected saveDataContext(): void {
    this.dataContext[this.componentName].selectedQualityLot = this.selectedQualityLot;
    this.dataContext[this.componentName].state = this.state;
    this.dataContext[this.componentName].qualityLotTests = this.qualityLotTests;
    this.dataContext[this.componentName].selectedSampleTestIDs
        = this.selectedSampleTestIDs;
    this.dataContext[this.componentName]._selectedTest = this._selectedTest;

  }

  protected loadDataContext(): void {
    let previouslySelectedQualityLot: string;

    if (this.dataContext[this.componentName]) {
      previouslySelectedQualityLot = this.dataContext[this.componentName].selectedQualityLot;
      this.state = this.dataContext[this.componentName].state || this.state;
      this.qualityLotTests = this.dataContext[this.componentName].qualityLotTests;
      this._selectedTest = this.dataContext[this.componentName]._selectedTest;      
    } else {
      this.dataContext[this.componentName] = {};
    }
    this.selectedTest.emit(this._selectedTest);

    if (this.qualityLotTests && previouslySelectedQualityLot === this.selectedQualityLot) {
      this.dataStateChange(<DataStateChangeEvent>this.state);
    } else {
      this.loadData();
    }
  }


  loadData(): void {
    if (this.selectedQualityLot) {
      this.loading = true;
      this.qualityLotService
        .getQualityLotTests(this.selectedQualityLot)
        .subscribe(
          qualityLotTests => {
            this.qualityLotTests = qualityLotTests;
            this.gridData = process(this.qualityLotTests, this.state);
            this.loading = false;
          },
          error => (this.errorMessage = <any>error)
        );
    } else {
      this.qualityLotTests = null;
      this.gridData = null;
      this.state = {
        skip: 0,
        take: 10
      };
      this.selectedSampleTestIDs = [];
      this.selectedTest.emit(null);
    }
  }

  public dataStateChange(state: DataStateChangeEvent): void {
    this.loading = true;
    this.state = state;
    this.gridData = process(this.qualityLotTests, this.state);
    this.loading = false;
  }
}

您认为我如何改进上面的代码?

我在这里加载了我的应用程序的摘录 https://stackblitz.com/edit/angular-tabbed-app

1个回答

2

我们能否看到一个Plunkr以进行澄清?

我认为有三种常见的模式可以利用,以保持状态而无需所有对象继承。

  1. 容器/演示(也称为智能和哑)组件

在这里,您将在容器组件中获取和存储数据,并通过@Input()将它们传递给演示组件。您可以在此处销毁演示组件,而不必担心丢失状态。

  1. 路由导航

最好使用路由导航的概念来显示所需的容器组件,不仅可以利用现有的实践,还可以让用户始终了解他们在应用程序中的位置。

  1. 最重要的-状态管理库-我使用NgRx,但听说过AkiraNGXS

虽然如果您从未使用过Redux模式,则存在学习曲线,但您可以将应用程序状态存储在一个地方,使其不可变,并从任何组件引用它。


1
感谢您的回答。我已经在这里添加了代码:https://stackblitz.com/edit/angular-tabbed-app。我必须说我是Angular的新手,从未使用过@ng-rx,但我认为在找到正确的保存/加载方法之后,它可能是下一步。我认为它可以被视为“保存位置”。对于路由器,我认为我不能按照此模式使用导航属性,而且我想保存数组等内容,而不仅仅是导航属性。 - gipinani
检查组件数据是否已加载是 NgRx 真正擅长的地方之一。流程通常是这样的 - 在 ngOnInit 中检查存储以查看数据是否存在(如果是动态组件,则通常按 ID 进行检查),如果存在,则将其存储在可观察对象中 - 如果不存在,则分派一个操作来将数据加载到存储中。 - favdev
如果您最终使用路由,您可以利用守卫(Guards),这允许您在路由导航时加载数据。我不确定您所说的导航属性是什么意思 - 您的组件是否像设置页面一样静态,还是像显示有关对象的信息一样动态? - favdev
你在网格上点击的组件有什么功能/上下文? - favdev
@gipinani 我会建立一个容器组件(在你的情况下,是网格),你可以在其中选择一个项目,该项目将路由你离开网格组件并进入“人员”组件,路由包括人员对象的ID - 例如 localhost:4200/person/999'在前往此组件的路上,根据路由参数进行数据获取,并检查数据是否存在于你的状态管理中(推荐使用redux,但类似服务的东西也可以)。如果不存在,则添加数据。 - favdev
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接