Search code examples
angulartypescriptangular-componentsangular-template

Import Angular Component into another Component by specifying the component name as input parameter


I would like to create an angle component that consists of several sub-components. Eventually I want to create a large reusable tree component whose nodes have different data types. For each data type, the tree component imports sub-components that specify how the node should be displayed. The user should be able to specify a custom component to override the default component for a particular data type. An import syntax like this would be nice:

<app-tree [customDateComp]="CustomDateComponent"></app-tree>

This could be the simplified tree.component.html:

<ng-container *ngIf="!customDateComp">
 <app-default-date></app-default-date>
</ng-container>
<ng-container *ngIf="customDateComp">
 {{ customDateComp }}
</ng-container>

Of course this does not work yet, because the components are imported with the syntax. The approach below does not work either, because angular escapes it:

<app-tree [customDateComp]="'<app-custom-date></app-custom-date>'"></app-tree>

The sample code can be found here: https://stackblitz.com/edit/angular-ivy-xghj5f?file=src/app/app.component.html

Does anyone have an idea how to import Angular Components into other Components by specifying the component name as an input parameter? Or is there a better way to override default sub-components of a third-party component? Thanks a lot!


Solution

  • Here is your stackblitz demo working.

    Among the possible approaches, there is a portal (and the utilities available from @angular/cdk/portal).

    Basically, this is what you need:

    1 - A placeholder (in this case, cdkPortalOutlet setup) for the received component:

    <ng-template [cdkPortalOutlet]="_compPortal"></ng-template>
    

    2 - A portal (_compPortal, above) to plug into your portal outlet:

    _compPortal = new ComponentPortal<any>(MyCustomComponent);
    

    That's all.

    In your case (the stackblitz you've linked in your question) you can do:

    0 - Import @angular/cdk/portal module:

    import {PortalModule} from '@angular/cdk/portal';
    
    @NgModule({imports: [ ... PortalModule ] ...}) export class AppModule { }
    

    1 - tree.component.html

    <ng-container *ngIf="!_compPortal">
     <app-date></app-date>
    </ng-container>
    <ng-container *ngIf="_compPortal">
      <ng-template [cdkPortalOutlet]="_compPortal"></ng-template>
    </ng-container>
    

    2 - tree.compoenent.ts

    @Input() 
    set customDateComp(customComp: any){
      this._compPortal = customComp ? new ComponentPortal(customComp) : null;
    }
    _compPortal: ComponentPortal<any>;
    

    3 - app.component.ts

    // Notice this is not `_comp: CustomDateComponent`.
    // I'm setting up an attribute that receives the type so I can 
    // make it available on the template
    _comp = CustomDateComponent;
    

    4 - app.component.html

    <app-tree [customDateComp]="_comp"></app-tree>