初始化所有孩子后的Angular 2生命周期钩子?

Posted

技术标签:

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

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

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

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

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

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

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

干杯

汤姆

【问题讨论】:

您好,请问您最后解决了什么问题? 我使用了我标记为答案的那个,也许现在有一个更好的,因为这篇文章已经很老了,但是那个解决方案仍然对我有用。 【参考方案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 的代码。 这就是我现在正在做的事情。具有明确时间的超时,因此它会立即启动并且只会影响线程行为。

以上是关于初始化所有孩子后的Angular 2生命周期钩子?的主要内容,如果未能解决你的问题,请参考以下文章

自学ng -生命周期钩子

Vue笔记(Vue生命周期 11个钩子)

Angular 2 动态组件加载 ngOnChanges 生命周期钩子调用

Angular 生命周期钩子,让你掌控每个关键时刻!

Angular 2 应用程序中 NgOnInit 生命周期钩子中移动方法的问题

处理 Angular 4 生命周期钩子