I have a setup in this parent.component
where some complicated dynamic templates are resolved within some named ng-template
elements. I then want to insert these in a dynamic order on the page, which I do in the ngAfterViewInit
using createEmbeddedView
. This required a setTimeout
or I would get an Angular ExpressionChangedAfterItHasBeenCheckedError
.
This seemed messy on its own, but now I'm trying to content project these dynamic elements into a child component. The elements don't appear. How should I be working with Angular's lifecycle and change detection to do this correctly?
One additional quirk that would complicate the example: In my practical implementation, the child component's ng-content
spot is hidden behind an ngIf
directive. The projected, dynamic content doesn't appear if it is initialized as on, but does appear if it is toggled from off to on, so the child component is receiving and saving the projection in some way.
Example
parent.component.html
<ng-template #insertedElement1>
...
</ng-template>
<ng-template #insertedElement2>
...
</ng-template>
<ng-template #insertedElement3>
...
</ng-template>
<child-component>
<span #insertionPoint></span>
</child-component>
parent.component.ts
...
@ViewChild('insertionPoint', { static: false, read: ViewContainerRef })
insertionPoint: ViewContainerRef;
// static false because these elements have structural logic within them
@ViewChild('insertedElement1', { static: false })
insertedElement1: TemplateRef<any>;
@ViewChild('insertedElement2', { static: false })
insertedElement2: TemplateRef<any>;
@ViewChild('insertedElement3', { static: false })
insertedElement3: TemplateRef<any>;
// ngAfterViewInit to let the structural logic complete
ngAfterViewInit() {
// After timeout or I get ExpressionChangedAfterItHasBeenCheckedError
setTimeout(() => {
// This array is fixed for this example, but would be dynamic
[
"insertedElement3"
"insertedElement1"
"insertedElement2"
].forEach(element => {
this.insertionPoint.createEmbeddedView(this[element]);
});
});
}
...