很高兴听到您反对 setTimeout 并手动致电 detectChanges。
我知道这在您的应用程序结构中很明显,但这里有一个示例可以告诉您错误的来源:
https://ng-run.com/edit/d9EEfZLhGfM0fYQtehhs?open=app%2Fgrandparent%2Fparent%2Fchild%2Fchild.component.html&layout=2
因为子组件值绑定后,父组件
值由祖父组件更新,导致抛出
角度脏检查时出错
看来你是对的,你的子组件是在 ngAfterContentInit 发生之前渲染的。
Angular 应用程序是一个视图树。当 Angular 运行更改检测机制时,它会遍历此视图树并为每个视图调用 checkAndUpdateView 函数。
在这个函数的执行过程中,Angular 会按照严格定义的顺序调用其他函数。在我们的例子中,我们对这些函数很感兴趣:
execEmbeddedViewsAction
callLifecycleHooksChildrenFirst (AfterContentInit)
execComponentViewsAction
这意味着angular会在执行ngAfterContentInit钩子之前调用嵌入视图的变化检测循环(ng-template生成嵌入视图)。这就是您的示例中发生的情况:
正如我们在上图中看到的,当 Angular 检查 AppComponent 视图时,它首先检查嵌入式视图,然后调用 ngAfterContentInit 获取 GrandParentComponent 并下降到 GrandParentComponent 视图。
似乎有很多方法可以解决它。我在这个 Demo
中试穿了它们
关键时刻不是使用ngAfterContentInit钩子,而是在ParentComponent中设置索引和活动值:
parent.component.ts
export class ParentComponent {
private _active: boolean = false;
public index = 0;
constructor(
@Host() @Inject(forwardRef(() => GrandParentComponent))
public grandParentComponent: GrandParentComponent) {
this.index = this.grandParentComponent.parents.length;
this.grandParentComponent.parents.push(this)
}
ngOnInit() {
this.active = this.index == this.grandParentComponent.currentStep;
}
ngOnDestroy() {
if (this.grandParentComponent) {
this.grandParentComponent.parents = this.grandParentComponent.parents.filter(x => x !== this);
}
}
在GrandParentComponent 中声明了父属性的位置:
grandparent.component.ts
export class GrandParentComponent {
parents = [];