10

I want to create some dynamic components inside another component, but not just once, many times. And that number is also dynamic.

So I was using ComponentFactoryResolver to create the component and then ViewChild to inject that component dynamically. The problem, as I said, is that I need to do that in many places, so I'd need to use ViewChildren to retrieve all references and inject the component (with a certain input data).

I've a plunker where I reduced the problem to what I need. I have a component which is passed an array of data and the type of the component to create.

@Component({
  selector: 'my-app',
  template: `
    <div>
      <my-parent [data]="rows" [inner]="component">
      </my-parent>
    </div>
  `
})
export class AppComponent { 

  rows = [
    { name: 'data1' },
    { name: 'data2' },
    { name: 'data3' },
    { name: 'data4' }
  ];

  component = ChildComponent;
}

In my-parent, I display a table with one row for each element in the data received as parameter, but also, I want to display below each row, another row with a component of the type also passed as parameter.

As an example, above all rows I'm doing this exact thing for one component, where I'm passing name='TOTAL'. So I use an <ng-container #entry> to retrieve the reference and inject the component.

I try to the same for the dynamic refs and it does not work. I created a directive Host so that I can retrieve it with ViewChildren with an specific id, but then when I try to create the component dynamically, it does not work.

This is the code

@Directive({
  selector: 'host'
})
export class Host {
  @Input() id: string;
}

@Component({
  selector: 'my-parent',
  template: `
    <table>
      <thead>
        <tr>
          <th>my data header</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>
            <ng-container #entry></ng-container>
          </td>
        </tr>
        <ng-container *ngFor="let d of data; let i = index;">
          <tr>
            <td>{{ d.name }}</td>
          </tr>
          <tr>
            <td>
              <host id="{{i}}"></host>
            </td>
          </tr>
        </ng-container>
      </tbody>
    </table>
  `
})
export class ParentComponent implements OnInit, AfterContentInit {

  @Input() data: any;
  @Input() inner: any;

  @ViewChildren(Host) hosts: QueryList<Host>;
  @ViewChild('entry', { read: ViewContainerRef }) entry: ViewContainerRef;

  constructor(private resolver: ComponentFactoryResolver) {}

  ngOnInit() {
  }

  ngAfterViewInit() {
    if (this.hosts) {
      const componentFactory = this.resolver.resolveComponentFactory(this.inner);
      this.hosts.forEach(host => {
        console.log(host);
        const component = host.createComponent(componentFactory);
        component.instance.name = host.id;
      });
    }
  }

  ngAfterContentInit() {
    const componentFactory = this.resolver.resolveComponentFactory(this.inner);
    const component = this.entry.createComponent(componentFactory);
    component.instance.name = 'TOTAL';
  }

}

And here is the plunker

It seems the hosts elements that I retrieve can´t injected upon. Any ideas to fix it?

Thanks.

David
  • 3,364
  • 10
  • 41
  • 84
  • 1
    The only way to get dynamic templates is by compiling them as a components template at runtime http://stackoverflow.com/questions/34784778/equivalent-of-compile-in-angular-2/37044960#37044960 – Günter Zöchbauer May 12 '17 at 18:00

1 Answers1

4

Inject ViewContainerRef into host directive :

@Directive({
  selector: 'host'
})
export class Host {
  constructor(private viewRef: ViewContainerRef) {}

  @Input() id: string;
}

and after that do host.viewRef.createComponent(componentFactory)

 ngAfterViewInit() {
   if (this.hosts) {
     const componentFactory = this.resolver.resolveComponentFactory(this.inner);
      this.hosts.forEach(host => {
       console.log(host);
       const component = host.viewRef.createComponent(componentFactory);
       component.instance.name = host.id;
   });
  }
}
Adrita Sharma
  • 21,581
  • 10
  • 69
  • 79
Julia Passynkova
  • 17,256
  • 6
  • 33
  • 32