初始化所有孩子后的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】:如果您还有内容子项(使用 <ng-content>
转入),那么 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生命周期钩子?的主要内容,如果未能解决你的问题,请参考以下文章
Angular 2 动态组件加载 ngOnChanges 生命周期钩子调用