Angular Dynamic Components: @ViewChildren get ViewContainerRef for every component in QueryList
Asked Answered
B

2

6

I am building a dialog with dynamic tabs that can receive a component to be placed in the tab body. I am having a hard time creating multiple dynamic components using @ViewChildren. I have successfully done this with a single component and @ViewChild in the past quite easily.

Here is my template:

<mat-tab *ngFor="let tab of tabs" [label]="tab.label">
     <ng-template #comp></ng-template>
</mat-tab>

Here is my component logic:

@ViewChildren("comp") dynComponents: QueryList<any>;


public ngAfterContentInit() {
  this.tabs.forEach(tab => {
     const factory = this._resolver.resolveComponentFactory(tab.component);
     console.log(this.dynComponents); // Returns undefined.

     // this.componentRef = this.vcRef.createComponent(factory);
  });
}

My dynComponents are undefined even when hard-coding components in the Template. I need to seemingly get the ViewContainerRef from this dynComponents QueryList, but I am not sure why it is not populating at all. I used this post for reference: Post

Bollard answered 30/8, 2019 at 6:6 Comment(1)
try use ngAfterViewInit. Anyway -I must check it- I think that if your templates are not in view when AfterViewInit happens you need subscribe to this.dynComponents.changes.subscribe(res=>{..here you has your dnyComponent..}Villar
P
10

The @ViewChildren in the component is not working because it is missing the read metadata property indicating ViewContainerRef.

Component

import {
  AfterContentInit, Component, ComponentFactoryResolver, QueryList, Type, ViewChildren, ViewContainerRef
} from '@angular/core';


@Component({
  selector: 'dynamic-dialog',
  templateUrl: './dynamic-dialog.component.html',
  styleUrls: ['./dynamic-dialog.component.scss']
})
export class DynamicDialogComponent implements AfterContentInit {
  @ViewChildren('comp', { read: ViewContainerRef })
  public dynComponents: QueryList<ViewContainerRef>;

  public tabs = [];

  constructor(private _resolver: ComponentFactoryResolver) {}

  ngAfterContentInit() {
    this.dynComponents.map(
      (vcr: ViewContainerRef, index: number) => {
        const factory = this._resolver.resolveComponentFactory(
          this.tabs[index].component);
        vcr.createComponent(factory);
      }
    )
  }
}

p.s. Dynamic content may be loaded using lifecycle hook AfterContentInit or AfterViewInit.

Pork answered 30/8, 2019 at 11:45 Comment(1)
This works as expected. Nice solution! Anyone who views this post in the future, make sure to store your comp refs when creating them and destroy them on ngOnDestroy!Bollard
M
3

In my project i do this to build dynamically the components:

App Component

   <div *ngFor="let field of fields">
        <app-dynamic-component [field]="field" ></app-dynamic-component>
    </div>

App-dynamic-component.ts

  @ViewChild(DynamicComponentDirective, {static: true}) adHost: DynamicComponentDirective;

...
loadComponent() {
    const componentFactory = 
        this.componentFactoryResolver.resolveComponentFactory(this.field.component);

    const componentRef = <any>viewContainerRef.createComponent(componentFactory);
}

App-dynamic-component.html

<ng-template dynamic-component></ng-template>

Finally my dynamic-component Directive

import { Directive, ViewContainerRef } from '@angular/core';

@Directive({
  selector: '[dynamic-component]',
})
export class DynamicComponentDirective {
  constructor(public viewContainerRef: ViewContainerRef) { }
}
Malmo answered 30/8, 2019 at 9:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.