Angular 13 - When create an embedded View?

Viewed 160

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

2 Answers

@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.

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

Related