angularangularjsangular-dynamic-components

Angular: Append dynamic created component as a child of the target-Element


I'm implementing a directive to add actions to a mat-table's mat-cell:

<ng-container matColumnDef="actions">
  <mat-header-cell *matHeaderCellDef></mat-header-cell>
  <mat-cell
    *matCellDef="let row"
    tableActions
    [editAction]="true"
    [actionInputData]="actionInputData"
    [row]="row"
  >
  </mat-cell>
</ng-container>

tableActions is the directive. My goal ist to append the actions as a child of mat-cell.

I've already tried lots of ways and I've searched a lot but haven't found any way to do that in a way, that all works as expected:

  1. I used the _viewContainerRef to create and append the action but the action is alaways added next to the mat-cell and not as a child.

    let componentRef: ComponentRef = this._viewContainerRef.createComponent(component);

enter image description here

  1. I used Angular's renderer (Renderer2). It worked to append the component as a child of mat-cell but the mat-icon, mat-tooltip and Angular's standard directives such as *ngIf don't work. It seems so that all directives added to the action's html don't work.

Here I created the componentRef with the angular core's createComponent-function and appended it to the target-Element (mat-cell)

  let componentRef: ComponentRef<any> = createComponent(component, {
    environmentInjector: this._appRef.injector,
  }); 

  this._renderer.appendChild(
    this._elementRef.nativeElement as HTMLElement,
    componentRef.location.nativeElement
  );

enter image description here

The edit action's html is:

<button
  mat-icon-button
  (click)="editRow(row)"
  color="primary"
  matTooltip="{{ 'edit' | translate }}"
>
  <mat-icon color="primary">edit</mat-icon>
</button>

My final question is: How can I append these action-components in a way that all is working as I would implement them directly into the mat-cell?


Solution

  • It started working for me, by doing the below steps,

    1. Ensuring the imports are added (CommonModule, MatIconModule, MatButtonModule) to the component that is going to be rendered dynamically

    2. Using the setInput method to create the binding needed for the angular component, else binding will not work for @Input

    3. Running the change detection of the child component, so that the component changes are detected, using componentRef.changeDetectorRef.detectChanges();

    rendered component

    import { CommonModule } from '@angular/common';
    import { Component, Input } from '@angular/core';
    import { MatButtonModule } from '@angular/material/button';
    import { MatIconModule } from '@angular/material/icon';
    
    @Component({
      selector: 'app-table-action-container',
      standalone: true,
      imports: [CommonModule, MatIconModule, MatButtonModule],
      template: `
      <button mat-button color="primary" *ngIf="show">
        asdf
        <mat-icon color="primary"></mat-icon>
      </button>
      <button mat-button color="primary" *ngIf="!show">
        qwer
        <mat-icon color="primary"></mat-icon>
      </button>
      `,
      styleUrl: './table-action-container.component.scss',
    })
    export class TableActionContainerComponent {
      @Input() show = false;
    }
    

    directive

    import {
      ApplicationRef,
      ComponentRef,
      Directive,
      ElementRef,
      Input,
      Renderer2,
      createComponent,
    } from '@angular/core';
    import { TableActionContainerComponent } from './table-action-container/table-action-container.component';
    
    @Directive({
      selector: '[appTableActions]',
      standalone: true,
    })
    export class TableActionsDirective {
      @Input() show = false;
      constructor(
        private _appRef: ApplicationRef,
        private _renderer: Renderer2,
        private _elementRef: ElementRef
      ) {}
    
      ngOnInit() {
        const component = TableActionContainerComponent;
        let componentRef: ComponentRef<any> = createComponent(component, {
          environmentInjector: this._appRef.injector,
        });
        componentRef.setInput('show', this.show);
    
        this._renderer.appendChild(
          this._elementRef.nativeElement as HTMLElement,
          componentRef.location.nativeElement
        );
        componentRef.changeDetectorRef.detectChanges();
      }
    }
    

    html snippet

      ...
      <ng-container matColumnDef="symbol">
        <th mat-header-cell *matHeaderCellDef>Action</th>
        <td
          mat-cell
          *matCellDef="let element;let index = index"
          appTableActions
          [show]="index % 2 === 0"
        ></td>
      </ng-container>
      ...
    

    Stackblitz Demo