[英]Display component's content from @ContentChildren in multiple rows using *ngFor on Angular 2
[英]Got ExpressionChangedAfterItHasBeenCheckedError when using *ngFor and ContentChildren
我有选项卡和选项卡组件:
tabs.component.ts:
@Component({
selector: 'cl-tabs',
template: `<ng-content></ng-content>`,
styleUrls: ['tabs.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TabsComponent implements AfterContentInit {
@ContentChildren(TabComponent) tabs: QueryList<TabComponent>;
ngAfterContentInit(): void {
if (0 < this.tabs.length) {
this.activate(this.tabs.first);
}
}
activate(activatedTab: TabComponent) {
this.tabs.forEach(tab => tab.active = false);
activatedTab.active = true;
}
}
tab.component.ts:
@Component({
selector: 'cl-tab',
template: `<ng-content></ng-content>`,
styleUrls: ['tab.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TabComponent {
@Input() title: string;
@Input() @HostBinding('class.is-active') active: boolean = false;
}
app.component.html:
<cl-tabs>
<cl-tab *ngFor="let item of items" [title]="item.name">
<any-component [item]="item"></any-component>
</cl-tab>
</tabs>
当使用 *ngFor 创建选项卡时,会抛出 ExpressionChangedAfterItHasBeenCheckedError。
cdRef.detectChanges(),正如在许多情况下所建议的,没有帮助。
如果出现以下情况,错误就会消失:
为什么在前两种情况下错误会消失? 在这种特殊情况下,有没有更好的 setTimeout 替代方案?
更新:在 plunker https://embed.plnkr.co/ZUOvf6NXCj2JOLTwbsYU/ 中重现错误
要理解答案,您必须首先阅读并理解这两篇文章:
让我们看一下App
组件的以下模板:
<cl-tabs>
<cl-tab *ngFor="let item of items" [title]="item.name"></cl-tab>
</cl-tabs>
这里有内容投影和嵌入视图(由ngFor
创建)。
有两点是必不可少的:
ngAfterContentInit
之前检查嵌入视图。 现在,请特别注意以上几点
export class App {
items = [
{name: "1", value: 1},
{name: "2", value: 2},
{name: "3", value: 3},
];
}
您可以对AppComponent视图的以下结构进行成像:
AppView.nodes: [
clTabsComponentView
ngForEmbeddedViews: [
ngForEmbeddedView<{name: "1", value: 1}>
ngForEmbeddedView<{name: "2", value: 2}>
ngForEmbeddedView<{name: "3", value: 3}>
当为AppView触发更改检测时,这里是操作顺序:
1)检查clTabsComponentView
2)检查ngForEmbeddedViews中的所有视图。
这里记住每个视图的值active = false
。
3)调用ngAfterContentInit
生命周期钩子。
在这里更新active = true
。 因此,在验证阶段,Angular将检测差异并报告错误。
为什么前两种情况下错误消失了? 现在确定你的意思是
statically
。 如果你的意思是不使用*ngFor
那么就没有错误,因为每个TabComponentView都是子视图,而不是嵌入视图。 并且在ngAfterContentChecked
生命周期钩子之后处理子视图。
如果删除@HostBinding
则Angular无需检查组件,并且不会记住active
值 - 因此无需验证,也无需检查。
cdRef.detectChanges(),在许多情况下建议,没有帮助。
您需要在AppComponent上调用更改检测才能使其生效。
我来得太晚了。 无论如何。
我确实添加了一个空模板来为 ngTemplateOutlet 提供一个值,同时每个模板都准备好了。
<div class="stepper--stages">
<div class="stepper--single-stage" *ngFor="let stage of stages">
<ng-container [ngTemplateOutlet]="stage.templateRef || placeholder"></ng-container>
</div>
<ng-template #placeholder></ng-template>
</div>
编辑:
我在模板中使用了一个简单的 OR 运算符。 这里有趣的事情是:为什么此代码段在应用程序流程中稍后由 Angular 应用程序呈现时可以正常工作,而不是在窗口重新加载洞 Angular 应用程序时。 在第二种情况下,Angular 似乎直接使用 #placeholder 而不是 templateOutlet。
问题是你在通过角度变化检测检查后正在改变价值。 这是问题所在:
ngAfterContentInit(): void {
if (0 < this.tabs.length) {
this.activate(this.tabs.first);
}
}
setTimeout
将是创建macrotask的最佳解决方案。 另一种解决方案是再次触发更改检测,但这将为您的整个应用程序调用它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.