2

I'm learning TemplateRef and ViewContainerRef concepts in Angular 13.3.0.

My component template is very simple:

<ng-container #container></ng-container>

<ng-template #templ let-name="name">
    Example {{name}}
</ng-template>

In the component code:

export class MyComponent implements OnInit {

  @ViewChild("container", {read: ViewContainerRef})
  container!: ViewContainerRef;

  @ViewChild("templ", {read: TemplateRef})
  templ!: TemplateRef<any>;

  constructor() { }

  ngAfterViewInit() {
    this.container.createEmbeddedView(this.templ, { name: "John" });
  }
}

But I get the runtime error:

ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'. Current value: 'John'. It seems like the view has been created after its parent and its children have been dirty checked. Has it been created in a change detection hook?. Find more at https://angular.io/errors/NG0100

What is the correct hook method in which call createEmbeddedView(...) and why is that? I've already tried ngOnInit and ngAfterContentInit

Thanks

EduBic
  • 227
  • 4
  • 17

3 Answers3

1

You should use ngAfterViewInit, and then call this.cd.detectChanges() to update the view after creating the embedded component

cd is a reference to ChangeDetectorRef which you can inject in the constructor

Angular details on ViewChild instantiation and lifecycle hooks

Drenai
  • 11,315
  • 9
  • 48
  • 82
  • This solve the runtime issue, is this the only way? – EduBic Jul 18 '22 at 10:49
  • You could also wrap the `createEmbeddedView` in a `setTimeout` - usually with no (or very low) milliseconds delay e.g. 20. I prefer `detectChanges` as it's clearer what it's asking Angular to do – Drenai Jul 18 '22 at 10:52
1

@EduBic you can also set the static: true param to your ViewChild query and the query will be resolved before the change detection run and you will have your viewChild component available already inside the NgOnInit hook where you can do whatever you want and create embedded view, you won't have the ExpressionsChanged.. error since the change detection hasn't run yet. But the static: true will only work if you have your child to query not inside any binding-dependent child nodes. More about the static param you can read from this answer here

export class MyComponent implements OnInit {

  @ViewChild("container", {read: ViewContainerRef, static: true})
  container!: ViewContainerRef;

  @ViewChild("templ", {read: TemplateRef, static: true})
  templ!: TemplateRef<any>;

  constructor() { }
  
  ngOnInit() { 
    this.container.createEmbeddedView(this.templ, { name: "John" });
  }
}

here is the working stackblitz example

P.s. You had a typo in your template param, the right one would: let-name="name"

P.p.s. I would not use cdr.detectChanges because it triggers manual synchronous change detection run for all the descendants of the current view. But it's another topic to discuss.

VladMstv
  • 56
  • 3
0

The correct hook method to call createEmbeddedView() is ngAfterViewInit(). Embedded View is created here because views are initialized in ngAfterViewInit.

According to Angular official docs,

ngAfterViewInit() - Respond after Angular initializes the component's views and child views, or the view that contains the directive.

Implementing the logic in ngOnInit and ngAfterContentInit didn’t work because the view has not initialized yet.

  • ngOnInit - invoked before any of the view or content children have been checked
  • ngAfterContentInitis - invoked after Angular has fully initialized all content of a directive

The Angular lifecycle hook method sequence is as follows:

enter image description here

To learn more about lifecycle hooks, read Angular official docs.

Angela Amarapala
  • 1,002
  • 10
  • 26