Dynamically inject a component in another component

2,352 views
Skip to first unread message

Justin Coutarel

unread,
Jul 25, 2018, 10:12:57 AM7/25/18
to Angular and AngularJS discussion
I'm developping a little UI components framework for my personal needs and for fun. I'm developping a Tab component and for testing purpose, I need to inject dynamically a component (TabContainerComponent) inside another component (TabComponent). Below, the code of my two components:

tab.component.ts:

import {Component, ContentChildren} from "@angular/core";
import {TabContainerComponent} from "./tabContainer.component";

@Component({
    selector: 'tab',
    templateUrl: 'tab.component.html'
})
export class TabComponent {

    @ContentChildren(TabContainerComponent)
    tabs: TabContainerComponent[];
}


tab.component.html:


<ul>
    <li *ngFor="let tab of tabs">{{ tab.title }}</li>
</ul>
<div>
    <div *ngFor="let tab of tabs">
        <ng-container *ngTemplateOutlet="tab.template"></ng-container>
    </div>
    <ng-content></ng-content>
</div>


tabContainer.component.ts:


import {Component, Input} from "@angular/core";

@Component({
    selector: 'tab-container',
    template: '<ng-container></ng-container>'
})
export class TabContainerComponent {

    @Input()
    title: string;

    @Input()
    template;
}


I used a ComponentFactoryResolver and a ComponentFactory to create dynamically my new component (TabContainerComponent), and inject it in a placeholder inside my other component (TabContainer), in the addTab method:


app.component.ts:


import {
    Component, ViewChild, ComponentFactoryResolver, ComponentFactory,
    ComponentRef, TemplateRef, ViewContainerRef
} from '@angular/core';
import {TabContainerComponent} from "./tabContainer.component";
import {TabComponent} from "./tab.component";

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent {

    title = 'app';

    @ViewChild(TabComponent)
    tab: TabComponent;

    @ViewChild('tabsPlaceholder', {read: ViewContainerRef})
    public tabsPlaceholder: ViewContainerRef;

    @ViewChild('newTab')
    newTab: TemplateRef<any>;

    constructor(private resolver: ComponentFactoryResolver) {
    }

    addTab(): void {
        let factory: ComponentFactory<TabContainerComponent> = this.resolver.resolveComponentFactory(TabContainerComponent);
        let tab: ComponentRef<TabContainerComponent> = this.tabsPlaceholder.createComponent(factory);
        tab.instance.title = "New tab";
        tab.instance.template = this.newTab;
        console.log('addTab() triggered');
    }
}


The addMethod is triggered by clicking on the "Add tab" button:


app.component.html:


<button (click)="addTab()">Add tab</button>
<tab>
    <tab-container title="Tab 1" [template]="tab1"></tab-container>
    <tab-container title="Tab 2" [template]="tab2"></tab-container>
    <ng-container #tabsPlaceholder></ng-container>
</tab>
<ng-template #tab1>T1 template</ng-template>
<ng-template #tab2>T2 template</ng-template>
<ng-template #newTab>
    This is a new tab
</ng-template>


app.module.ts:


import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';


import { AppComponent } from './app.component';
import {TabContainerComponent} from "./tabContainer.component";
import {TabComponent} from "./tab.component";


@NgModule({
    declarations: [
        AppComponent,
        TabComponent,
        TabContainerComponent
    ],
    imports: [
        BrowserModule
    ],
    providers: [],
    bootstrap: [AppComponent],
    entryComponents: [
        TabContainerComponent
    ]
})
export class AppModule { }


When I click on the "Add tab" button, I'm able to see the console.log message and I'm able to see a new <tab-container> tag (but without any attribute, which is strange) inside the <tab> tag but Angular doesn't update the Tab component view (there is no <li> and <div> created). I tried also to check changes by implementing OnChanges interface in TabComponent class but without success.

Is anyone have an idea to solve my problem ?


P.S.: I don't want to use an array of TabContainer components in order to test the createComponent method.


Demo: 

https://stackblitz.com/edit/angular-imeh71?embed=1&file=src/app/app.component.ts

Justin Coutarel

unread,
Jul 30, 2018, 10:23:46 AM7/30/18
to Angular and AngularJS discussion
Hello Everybody,

I posted also my issue on Stackoverflow. Someone replied to me with a working solution. So I put a link to my SO post: https://stackoverflow.com/questions/51495787/angular-dynamically-inject-a-component-in-another-component#51582334

Zlatko Đurić

unread,
Jul 31, 2018, 2:53:14 AM7/31/18
to Angular and AngularJS discussion
Hi Justin,

I see you got your answer on Stackoverflow - how to dynamically inject a component into another.

But I have another proposal: why not have your tabs routable? That way you hand off a lot of the work to Angular. And in addition to that, you can load your components lazily.

It can go something like this. Put the tab titles inside the tabs:

<ul>
<li *ngFor="let tab of tabs">{{ tab.title }}</li>
</ul>
<div *ngFor="let tab of tabs">
<router-outlet></router-outlet>
</div>


Then your router does this:
const routes: Routes = [{
path: '',
component: TabComponent,
children: [{
path: ':tabName',
component: TabContainerComponent,
}]
}];

Finally, your tab container only needs to listen to the route change:
ngOnInit(
this.route.params.subsribe(params => {
if (params.tabName !== this.tabName) {
this.changeTab(params.tabName);
}
});
)

You'd have the component that renders your tab content in the container inside the changeTab function. Would that be appropriate to your use case?
Reply all
Reply to author
Forward
0 new messages