0

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]);
    });
  });
}
...
MattTreichel
  • 1,418
  • 3
  • 18
  • 35
  • Couldn't you use *ngIfs to display the components upon resolving a condition? This would avid any complications you are actually facing – mikegross Nov 08 '21 at 23:17
  • @mikegross Do you mean `ngIf` on the "insertedElement" templates? In my real situation, we're inserting table rows in differing orders based on data pulled from state - the fixed array in the example here is actually something dynamic. – MattTreichel Nov 09 '21 at 15:20
  • The "insertedElement" templates aren't universal, so I can't really `ngFor` loop through this order array to create the dynamic template. – MattTreichel Nov 09 '21 at 15:29
  • If I understand well you can. What you can do is declare an empty array inyour initial NgFor and on receiving new data assigning this data to the empty array which will render your data. You should not insert your elements manually in an empty table. If you build a minimal example of stackblitz I could edit your example? – mikegross Nov 09 '21 at 18:10

0 Answers0