Search code examples
angularangular13

Angular 13 - When to create an embedded View?


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


Solution

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