【问题标题】:angular save / restore component state角度保存/恢复组件状态
【发布时间】:2019-09-29 21:04:09
【问题描述】:

我正在开发一个具有多选项卡布局的 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

【问题讨论】:

    标签: angular


    【解决方案1】:

    我们可以看一个 Plunkr 来澄清一下吗?

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

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

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

    1. 路由器导航

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

    1. 最重要的一个——状态管理库——我使用NgRx,但听说过AkiraNGXS的好消息。

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

    【讨论】:

    • 感谢您的回答。我在此处添加了代码:stackblitz.com/edit/angular-tabbed-app 我不得不说我是 Angular 新手,从未使用过 @ng-rx,但我认为在找到正确的保存/加载方法之后可能是下一步。我认为它可以被认为是“在哪里保存”。对于路由器,我认为我不能按照这种模式使用导航属性,而且我想保存数组等,而不仅仅是导航属性
    • 检查组件的数据是否已经加载是 NgRx 真正擅长的地方。流程通常会继续 - 在 ngOnInit 上检查存储以查看数据是否存在(如果它是动态组件,通常通过 ID 进行),是否将其存储在可观察对象中 - 以及它是否不调度将数据加载到的操作商店。
    • 如果您最终使用路由,您可以利用 Guards,它允许您在路由导航中加载数据。我不确定您所说的导航属性是什么意思 - 您的组件是静态的(如设置页面)还是动态的(如显示有关对象的信息)?
    • 好吧,换句话说,我不知道如何在当前的实现中使用路由,或者如何更改它以受益于路由。也许我只需要指出正确的方向。组件是动态的,例如包含通过过滤器对话框加载的网格,当您从网格中选择一个元素时,子组件将其作为 @Input 接收,并加载自身相关数据。如果有帮助,我可以为您提供示例图片
    • 我想导航讨论与 NgRx 相比与您的问题无关。由于您似乎对改进您的工作代码更感兴趣,我建议您借此机会检查一下。
    猜你喜欢
    • 1970-01-01
    • 2013-04-04
    • 2011-07-23
    • 2014-04-25
    • 2012-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多