fbpx Skip to content

Aquent | DEV6

Angular: Dynamic Component Creation

Written by: Alain Thibodeau

The introduction of components in Angular brought on a lot of welcomed changes.

Declaratively creating a component is pretty straightforward and easy enough. We do however run into cases where we need to dynamically create components at runtime. This is a little more involved, but is also straightforward.

To demonstrate we will generate two types of custom components. Each component will take a value as an input, and once initialized will send a value back to the parent.

(Angular 4+ introduced NgComponentOutlet, which at the time of this writing is marked as experimental. We will cover this in a future post.)

Ideally, we always want to type to the interface. So, before we create the components we will create a common interface for them to share. With this common interface we will also be able to type the components in our up coming loadComponents method properly.  

export interface CustomComponent {
    data: any;
    update(): void;
    updateEmitter: EventEmitter<number>;
}

Now, we will create a component that generates a list of items.

@Component({

    selector: 'details-component',

    template: `<h3>List items</h3>

        <ul>

            <li *ngFor="let item of data">{{ item.detail }}</li>

        </ul><hr>

    `

})

export class DetailsComponent implements OnInit, CustomComponent {

    @Input() data: any;

    @Output() updateEmitter: EventEmitter<number> = new EventEmitter();

 

    constructor() {

    }

 

    ngOnInit() {

        setTimeout(() => this.update(), 1000);

    }

 

    update(): void {

        this.updateEmitter.emit(10);

    }

}

And now we create a component that generates a [email protected]({

@Component({
 

    selector: 'table-component',

    template: `<h3>Table items:</h3>

        <table border=1>

            <tr>

                        <th>Name</th>

                        <th>Description</th>

            </tr>

            <tr *ngFor="let item of data">

                        <td>{{ item.name }}</td>

                        <td>{{ item.description }}</td>

            </tr>

    </table> <hr>`

})

export class TableComponent implements OnInit, CustomComponent {

    @Input() data: any;

    @Output() updateEmitter: EventEmitter<number> = new EventEmitter();

 

    constructor() {

    }

 

    ngOnInit() {

        setTimeout(() => this.update(), 2000);

    }

 

    update(): void {

        this.updateEmitter.emit(3);

    }

}

These components implement the CustomComponent interface. They also take an input value and output a number triggered by a timeout.

It is important to note that we must declare these components in our ngModule as declarations and entryComponents. When adding components to the entryComponents array Angular will know to create factories for [email protected]({

@NgModule({
 

  imports: [ BrowserModule ],

  declarations: [ App, DetailsComponent, TableComponent ],

  entryComponents: [TableComponent, DetailsComponent],

  bootstrap: [ App ]

We are now all set to create these components in our App class.

@Component({

  selector: 'my-app',

   template: `<div #container></div>

               <p>Result from the components: {{ results }}</p>`

})

export class App {

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

    results: number = 0;

 

    constructor(private factoryResolver: ComponentFactoryResolver) {

    }

 

    ngOnInit() {

        this.loadComponent(DetailsComponent, [

                { detail: 'List item 1' },

                { detail: 'List item 2' },

                { detail: 'List item 3' }

            ]

        )

        this.loadComponent(TableComponent, [

                { name: 'Item 1', description: 'Description 1' },

                { name: 'Item 2', description: 'Description 2' }

            ]

        )

    }

 

    private loadComponent(type: Type<CustomComponent>, data: any): void {

        const componentFactory = this.factoryResolver.resolveComponentFactory(type);

        const componentRef = this.container.createComponent(componentFactory);

        const inst = (<CustomComponent>componentRef.instance);

 

        inst.data = data;

        inst.updateEmitter.subscribe(data => {

            this.results += data;

 

        });

    }

}

There are a couple of things to consider here. First, we need a view container to put these components in. The div with the #container id is referenced with @ViewChild to get access to its ViewContainerRef.

Moving down we inject the ComponentFactoryResolver service which gives us the proper factory for the component type we give it. Once we have the factory, we can pass that along to the viewContainerRef and call its createComponent method. This returns the instance reference. Angular does not create bindings to components created this way.  But, since we have the reference to the component, we can now use this reference to hook into the @Input and @Ouput of the Component’s instance. And that is what we do; we pass it the data and subscribe to the event emitter to handle the data sent back to us from the component.

If you need more functionality, you can take a look at the documentation of ViewContainerRef. There are plenty more convenient methods in there.

As I mentioned earlier, Angular 4 has introduced the NgComponentOutlet. This makes things simpler, but it is currently marked as experimental and will be saved for future article.

In the meantime you can take a look at the plunker of the above code.

Sign up for Angular Course

$1,995 CAD – 4 Days

View Course Details