【问题标题】:Angular 2 life cycle hook after all children are initialized?初始化所有孩子后的Angular 2生命周期钩子?
【发布时间】:2016-12-10 08:02:53
【问题描述】:

我正在寻找一个概念来实现以下案例:

我有一个父搜索组件,它有一些组件作为视图子项/内容子项,用于显示构面和搜索结果。我现在想在应用程序完成加载后触发搜索,这样用户就不会看到空白页面。

我现在的问题是找不到适合我需要的生命周期挂钩。构面/搜索结果订阅各自 ngOnInit 中的搜索结果。所以我需要一个在所有子组件完成初始化之后调用的钩子。

我在父组件上尝试了以下钩子

  • ngAfterContentInit:在子级调用 ngOnInit 之前调用它
  • ngAfterViewInit:这个可行,但在搜索结果返回后,子视图会更新,这会导致错误,因为在 ngAfterViewInit 中不允许操作视图的操作

任何想法如何解决这个问题?对我来说,我似乎没有掌握这里的基本内容。

干杯

汤姆

【问题讨论】:

  • 您好,请问您最后解决了什么问题?
  • 我使用了我标记为答案的那个,也许现在有一个更好的,因为这篇文章已经很老了,但是那个解决方案仍然对我有用。

标签: angular


【解决方案1】:

我最终通过以下方式使用了 ngAfterViewInit() 钩子:

ngAfterViewInit(){
  //stuff that doesn't do view changes
  setTimeout(_=> this.methodThatKicksOffAnotherRoundOfChanges());
}

与设置实际时间的其他 setTimeout 调用相比,这应该是安全的,因为它应该立即开始(并且只会影响线程/上下文行为)

【讨论】:

  • 为什么要在这里使用 setTimeout?与直接调用所需方法有何不同?
  • 你在这里问的基本上是原始问题。正如我在解释中所说,我们需要在这里分叉一个子线程。在我最初的问题中,我遇到了“ngAfterViewInit”的问题,因为 Angular 不允许您更新“ngAfterViewInit”中的视图子项。这就是为什么你需要把主线程留在这里,如果你直接调用,你就会遇到我在问题中描述的问题。穿线在另一个线程上进行操作,因此角度不会爆炸。 (setTimeout 作为 js 中的一种线程方式)。现在直接调用它是否适用于发布版本?
  • @Tom 这似乎也是 Angular 文档本身中“官方”提出的解决方案。只需在Lifecycle Hooks 指南及其对应的full source code 中搜索tick_then
  • 这实际上对我有用。关于如何处理这种不那么骇人听闻的任何想法,例如Angular 团队已经实现了一些东西吗?
  • @Nicky 很想看到一个不那么 hacky 的变体。感觉还是不对。但当时这(我认为)来自官方的 angular 2 示例。但是从那以后我就没有和 a2 合作过,所以在这方面可能发生了一些变化。 A2 一直在快速移动。
【解决方案2】:

如果您需要您的父组件等待多个子组件之间的自定义异步行为,那么这是我设计的一个很好的解决方案。

任何异步初始化的子组件都扩展了这个:

import {Subject} from 'rxjs/Subject';
import {Observable} from 'rxjs/Observable';

export class AsynchronouslyInitialisedComponent {
  loadedState: Subject<boolean> = new Subject<boolean>();
  loadedState$ = this.loadedState.asObservable();
  constructor() {
  }
  protected componentLoaded() {
    this.loadedState.next(true);
  }
}

示例子组件:

import {Component} from '@angular/core';
import {AsynchronouslyInitialisedComponent} from './../base_component';

@Component({
  moduleId: module.id,
  selector: 'child'
})
export class Child extends AsynchronouslyInitialisedComponent {
  constructor() {
    super();
  }
  ngOnInit() {
    //Some async behavoir:
    setTimeout(() => {
      this.componentLoaded();
    }, 10000);
  }
}

现在您的 Parent 组件需要做的事情如下:

import {Component, ViewEncapsulation, ViewChild};
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/zip';

@Component({
  moduleId: module.id,
  selector: 'parent'
})
export class Parent { 

  @ViewChild('child') child; //Just use <child #child></child> in your template

  constructor() {
  }
  ngOnInit() {
    Observable
    .zip(this.child.loadedState$, this.otherChild.loadedState$) //Add as many as you want here... 
    .subscribe(pair => {
      console.log('All child components loaded');
    });
  }
}

【讨论】:

    【解决方案3】:

    如果您还有内容子项(使用 &lt;ng-content&gt; 转入),那么 ngAfterContentInit() 是正确的生命周期回调。

    https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html#!#aftercontent

    ngAfterContentInit() {
      doSomething();
    }
    

    【讨论】:

      【解决方案4】:

      让子组件在它们的 Init 上发出一个事件,并在你需要的地方监听它

      【讨论】:

      • 这实际上是我当前的实现,每个子组件都会发出一个事件,我收集这些事件,一旦最终事件到达,我就会触发动作。但我不喜欢这样,因为每次我添加一个新组件时,我都需要等待一个额外的 onInit 事件并且必须实现它,因为我的口味对于某些东西来说不够通用,应该是通用的。
      • 是的,这不是最优雅的解决方案。你试过 AfterViewChecked 钩子吗?
      • 我试过了,每次 Angular 检查视图是否需要更新时,AfterViewChecked 都会被触发。所以它经常被触发,并且在几个周期后它也会遇到视图更新错误。
      • 我在想,我发现了这个angular.io/docs/ts/latest/api/core/index/ViewChildren-var.html,这个在 ngAfterViewInit 之后触发,据我所知允许操作
      • ngAfterViewInit 之后触发的是哪一个?在文档页面上,他们解释了在 ngAfterViewInit 中可以使用视图子项。您是否链接了错误的 API 页面,还是我现在失明了 :) 还在寻找干净的解决方案。如果我发现了什么,会更新这个帖子。
      【解决方案5】:

      OP 似乎认为它不适合他的需要,或者不优雅,但似乎 Vale Steve 的回答是这里最合乎逻辑的方法。

      在我的情况下,我需要等待 2 个子组件初始化,它们控制着我的应用程序中的左右面板。我有两个子组件都将它们的初始化声明为共享服务:

        constructor(
            public _navigate: RouteNavigationService // service shared between all child components and parent
        ){}
      
        ngOnInit() {
          this._navigate.setChildInit(this.panel);
        }
      

      共享服务(RouteNavigationService):

        private _leftChildInit  = new Subject();
        private _rightChildInit = new Subject();
      
        leftChildInit$          = this._leftChildInit.asObservable();
        rightChildInit$         = this._rightChildInit.asObservable();
      
        setChildInit(panel){
          switch(panel){
            case 'leftPanel':
              console.log('left panel initialized!');
              this._leftChildInit.next(true);
              break;
            case 'rightPanel':
              console.log('right panel initialized!');
              this._rightChildInit.next(true);
              break;
          }
        }
      

      然后在我的父组件中,我使用zip 方法将多个 Observable 组合在一起(您也可以在此处添加其他子组件)并等待它们全部完成:

        childInitSub: Subscription;
      
        constructor(
            public _navigate: RouteNavigationService
        ) {
          this.childInitSub = Observable.zip( // WAIT FOR BOTH LEFT & RIGHT PANELS' ngOnInit() TO FIRE
              this._navigate.leftChildInit$,
              this._navigate.rightChildInit$).subscribe(data =>
              // Both child components have initialized... let's go!
          );
        }
      

      OP 在评论中声明

      “我不喜欢这样,因为每次我添加一个新组件我都需要 等待一个额外的 onInit 事件并且必须实现它”

      但实际上,您所要做的就是在您的服务中为新的子组件 ngOnInit 创建另一个 Subject,并在父组件的 zip 方法中添加一个额外的行。

      请注意,这里接受的答案,使用ngAfterViewInit() 与上面的相当不同。在我的例子中,使用ngAfterViewInit() 会产生一个ExpressionChangedAfterItHasBeenCheckedError,这超出了这个问题的范围,但可以证明这种方法实际上并没有做同样的事情。

      相比之下,上述方法实际上只是作为所有子组件的ngOnInit() 事件已触发的直接结果触发

      【讨论】:

      • 这似乎只在您不处理动态数据时才有效。就我而言,我有几个基于当前日期显示的子组件。这基本上意味着:更改数据库数据和更改要观察的组件数量。以目前的形式,这个答案并不能解决这个特定问题。
      【解决方案6】:

      你可以试试这个。它对我有用

      async ngOnInit() {
          // method to call
         await this.myMethod(); 
        }
      }
      

      【讨论】:

        【解决方案7】:

        在开发模式(默认)下,更改检测机制会对组件树进行额外检查,以确保您没有在挂钩中操纵 UI。这就是您遇到错误的原因。

        如果您确定在 ngAfterViewInit 中对 UI 所做的更改是有效的,请在 bootstrap() 之前调用 enableProdMode()。这告诉 Angular,“在更改检测期间不要进行第二次传递”。

        【讨论】:

        • 但这意味着应用程序在开发模式下会出现功能障碍,我会失去开发模式所具有的健全性检查的所有好处。
        • 您可以在开发模式下运行,但使用生命周期挂钩中的 setTimeout() 来调用更新 UI 的代码。
        • 这就是我现在正在做的事情。具有明确时间的超时,因此它会立即启动并且只会影响线程行为。
        猜你喜欢
        • 2017-11-13
        • 2017-01-18
        • 2017-11-22
        • 1970-01-01
        • 2018-08-30
        • 1970-01-01
        • 2018-01-11
        • 2019-06-26
        相关资源
        最近更新 更多