Search code examples
javascriptangularangular17

Angular Render Component using NgComponentOutlet with dynamic content projection


    <mat-button-toggle-group appearance="legacy"    [(ngModel)]="selectedClockType">
        <mat-button-toggle *ngFor="let item of items" [value]="item" color="primary">
           {{ item.label }}
        </mat-button-toggle>  
    </mat-button-toggle-group>
       
     @for (item of items; track:item.id) {   
          <ng-container *ngComponentOutlet="item .component"></ng-container> 
        }

     <ng-template #actionItesm let-data="data">{{data}} </ng-template>

component-a.html

    <ng-content></ng-content>

component-b.html

<ng-content></ng-content>

Am Trying to pass ngTemplate by using below code

 <ng-container *ngComponentOutlet="item .component;content: actionItesm ></ng-container> 

and using inside all the components that am using inside Items


Solution

  • You need to use the createEmbeddedView of ViewContainerRef where we input the template as a first argument, followed by a second argument containing the template context. I have created a new property templateRef which contains the created template ref structure. Then finally we pass that input to the property content of *ngComponentOutlet

    The example mentioned in NgComponentOutlet is a great example for understanding the steps and process.

    FULL CODE:

    PARENT TS:

    import { CommonModule } from '@angular/common';
    import {
      Component,
      ViewChild,
      ViewContainerRef,
      TemplateRef,
      Injector,
    } from '@angular/core';
    import { FormsModule } from '@angular/forms';
    import { MatButtonToggleModule } from '@angular/material/button-toggle';
    import { AComponent } from 'src/app/a/a.component';
    import { BComponent } from 'src/app/b/b.component';
    
    /**
     * @title Basic button-toggles
     */
    @Component({
      selector: 'button-toggle-overview-example',
      templateUrl: 'button-toggle-overview-example.html',
      standalone: true,
      imports: [
        MatButtonToggleModule,
        CommonModule,
        AComponent,
        BComponent,
        FormsModule,
      ],
    })
    export class ButtonToggleOverviewExample {
      @ViewChild('actionItesm', { static: true }) actionItesm!: TemplateRef<any>;
      selectedClockType: any;
      items: Array<any> = [
        { id: 1, label: 'a', component: AComponent },
        { id: 2, label: 'b', component: BComponent },
      ];
      myContent?: any[][];
      myInjector: Injector;
    
      constructor(injector: Injector, private vcr: ViewContainerRef) {
        this.myInjector = injector;
      }
    
      ngOnInit() {
        // Create the projectable content from the templates
        if (this.items?.length) {
          this.items.forEach((item: any) => {
            item.templateRef = [
              this.vcr.createEmbeddedView(this.actionItesm, { item: item })
                .rootNodes,
            ];
          });
        }
      }
    }
    

    PARENT HTML:

    <mat-button-toggle-group appearance="legacy" [(ngModel)]="selectedClockType">
      <mat-button-toggle *ngFor="let item of items" [value]="item" color="primary">
        {{ item.label }}
      </mat-button-toggle>
    </mat-button-toggle-group>
    <br />
    <br />
    <br />
    @for (item of items; track item.id;) { {{item.label}}
    <ng-container
      *ngComponentOutlet="item.component;
      content: item.templateRef;"
    ></ng-container>
    }
    <ng-template #actionItesm let-item="item">{{item.label}} asdf</ng-template>
    

    CHILD TS:

    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-b',
      standalone: true,
      imports: [],
      templateUrl: './b.component.html',
      styleUrl: './b.component.scss'
    })
    export class BComponent {
    
    }
    

    CHILD HTML:

    <br />
    <hr />
    B COMPONENT
    <ng-content></ng-content>
    <hr />
    <br />
    

    Stackblitz Demo