651.288.7000 info@intertech.com

Note: This post is part of an eleven-part series on Angular development. You can find the series overview here.

Overview

Most Angular components are created by adding a reference directly into a template, however there are times where the type of component won’t be known ahead of time and will need to be dynamically created. Often this is seen with modal windows, application level alerts, or other event driven messages, but could also occur when multiple copies or instances of a component need to be created. In order to create these components you will need to take some extra steps and precautions, but it can open the door to far more advanced functionality in an application.

To follow along with the articles there a two repositories created: a starting point and final solution.

Sample Environment Setup

In order to dynamically create a component, three items must be prepared.  First, a programmatic trigger is fired that start the process of creating the component.  For our example we will create a form that allows the user to specify the type of component and some options for display and where the component is created.  The code behind this is a simple form that we will interact with via an EventEmitter on the component.  The resulting form looks like the image below and emits an object of the interface shown:

export interface ComponentRequest {
    contextType: 'primary' | 'secondary' | 'success' | 'warning' | 'danger';
    componentType: 'button' | 'alert' ;
    viewPort: number;
}
The next thing we need is a component that we will create. In this instance, we will create two similar components to demonstrate how we can dynamically create different types. There are more steps needed as part of the setup that will be discussed later, but initially, we will just look at the component definition. We have a ButtonComponent and an AlertComponent – both will display the timestamp of when they are created, take an input for styling, and emit an event to request removal of the component.
button.compnent.ts
@Component({
    selector: 'app-button',
    template: `
	<button class="btn" [ngClass]="getClass()" (click)="remove.emit()">
  		Created: {{created | date:'medium'}}
        </button>`
})
export class ButtonComponent implements OnInit {
    @Input() type: 'primary' | 'secondary' | 'success' | 'warning' | 'danger';
    @Output() remove = new EventEmitter<any>();

    public created: Date;

    ngOnInit(): void {
        this.created = new Date();
    }
    getClass(): string {
        return !this.type ? null : `btn-${this.type}`;
    }
}
alert.component.ts
@Component({
	selector: 'app-alert',
	template: `
		<span class="alert d-flex align-items-center justify-content-between my-2 p-1" [ngClass]="getClass()">
			{{created | date:'medium'}}
			<button type="button" class="close" (click)="remove.emit()">
			  <span aria-hidden="true">×</span>
			</button>
		</span>`
})
export class AlertComponent implements OnInit {
	@Input() type: 'primary' | 'secondary' | 'success' | 'warning' | 'danger';
	@Output() remove = new EventEmitter<any>();

	public created: Date;

	ngOnInit(): void {
		this.created = new Date();
	}

	getClass(): string {
		return !this.type ? null : `badge-${this.type}`;
	}
}
The last item we need is a location where the component should be created. For this example, we will create an array of viewports within which we will create the components.
@Component({
    selector: 'app-dynamic-component-creation',
    template: `
        <div class="row">
            <app-create-request class="col" 
                [viewPorts]="viewPorts" 
                (createComponent)="addToViewport($event)">               
            </app-create-request>
        </div>
        <div class="row card-deck justify-content-between">
            <div class="col-6" *ngFor="let port of viewPorts">
                <div class="card mt-3">
                    <div class="card-header">
                        <h3>View Port #{{port}}</h3>
                    </div>
                    <div class="card-body">
                       <!-- TODO: Components go here -->
                    </div>
                </div>
            </div>
        </div>`
})
export class DynamicComponentCreationComponent{
    public viewPorts: number[] = [1, 2, 3, 4];

    public addToViewport(request: ComponentRequest): void {
        //TODO
    }
}
This component has a location in the template specified within each card body where we want to place the components and has a method bound to the form event to trigger the creation of a component.  Here is what our initial viewports look like:

Prepping for Component Creation

Entry Components

There are two types of components within an Angular application: declarative and entry components.  Declarative components are what you are most used to dealing with – they are generated by declaring a reference to the selector in a template.  Entry components are imperatively loaded by type.  There are two ways in which you previously have interacted with entry components, most likely without knowing it:

  • The root application bootstrapped component – generated during the bootstrap process and loaded into the DOM as the application starts
  • Routed components – dynamically created by the router and loaded into the DOM in the router-outlet

For a component to be declared as an entry component, it must be added to the entryComponents array in the module.  Below is the module definition for our sample where you can see the entryComponents definition:

@NgModule({
    declarations: [
        DynamicComponentCreationComponent,
        CreateRequestComponent,
        ButtonComponent,
        AlertComponent
    ],
    imports: [
        CommonModule,
        FormsModule
    ],
    entryComponents: [
        ButtonComponent,
        AlertComponent
    ],
    exports: [
        DynamicComponentCreationComponent
    ]
})
export class DynamicComponentCreationModule {}
Which begs the question: “I thought you said the app root and routed components are entry components, why don’t we need to declare them here?”  The answer is that you are without knowing it.  Angular automatically adds the bootstrap component and and components in the application routes to the entryComponents array for you.  This is only necessary for components that you dynamically create outside of those situations.

So what exactly is an entry component? For that we can reference the Angular documentation on entry components:

For production apps you want to load the smallest code possible. The code should contain only the classes that you actually need and exclude components that are never used. For this reason, the Angular compiler only generates code for components which are reachable from the entryComponents; This means that adding more references to @NgModule.declarations does not imply that they will necessarily be included in the final bundle.

In fact, many libraries declare and export components you’ll never use. For example, a material design library will export all components because it doesn’t know which ones you will use. However, it is unlikely that you will use them all. For the ones you don’t reference, the tree shaker drops these components from the final code package.

If a component isn’t an entry component and isn’t found in a template, the tree shaker will throw it away. So, it’s best to add only the components that are truly entry components to help keep your app as trim as possible.

So when an Angular application is built, Angular will start at all entry components and walk down the template tree to see what is used in order to build the application bundle.

There are some complexities when using entry components in a lazy loaded module that can cause some unexpected headaches. The simplest solution is to declare all entry components in non-lazy loaded modules, however there are workarounds available when needed. For more detail see the related issue #14324

NOTE: With all that being said, the Ivy rendering engine aims to remove the need declaring entry components as this will automatically be handled by the @Component decorator.

ViewContainerRef

The next step in dynamically creating a component is to define a handle to the ViewContainerRef where the component should be inserted.  The most common and easiest way to get a reference to ViewContainerRef is by using ViewChild.  If you are not familiar with ViewChild see the article ViewChild and ContentChild.  In our situation, we have multiple locations so we will use ViewChildren to get a list of generated template references.  First we add the template reference variable to a template where we want to component to go:

SNIP...
<div class="card-body">
    <ng-template #componentTarget></ng-template>
</div>
SNIP..
Next, we can use the ViewChildren decorator to get a reference to the list of #componentTarget templates:
@ViewChildren('componentTarget', {read: ViewContainerRef}) targets: QueryList<ViewContainerRef>;
As you can see, we are using the read property to target the ViewContainerRef on the target.

Another pattern is to use a directive. In the directive, you can get access to the ViewContainerRef for the directive’s element using dependency injection and then access that through the ViewChild instance.

@Directive({
   selector: '[app-target]',
})
export class AppTargetDirective {
   constructor(public viewContainerRef: ViewContainerRef) { }
}

Creating the Component

We now have everything we need to implement the method to generate the component.  Components are generated using a ComponentFactory instance.  We can easily create on by injecting and using the Angular provider ComponentFactoryResolver.  We can add this to the initialization of our component where we will create the new child components.  Extraneous properties and methods removed for clarity:

export class DynamicComponentCreationComponent implements OnInit {
   private buttonFactory: ComponentFactory<ButtonComponent>;
   private alertFactory: ComponentFactory<AlertComponent>;

   constructor(private componentFactoryResolver: ComponentFactoryResolver) { }

   ngOnInit(): void {

      this.buttonFactory = this.componentFactoryResolver.resolveComponentFactory(ButtonComponent);
      this.alertFactory = this.componentFactoryResolver.resolveComponentFactory(AlertComponent);
   }
}
We now have two handles to ComponentFactories to create the components on demand. The last step is to create the component in the addToViewport method we stubbed out initially:
private components: ComponentRef<any>[] = [];

public addToViewport(request: ComponentRequest): void {
    const factory = request.componentType === 'button' ? this.buttonFactory : this.alertFactory;
    const target = this.targets.toArray()[request.viewPort];
    const componentRef = target.createComponent(factory);
    componentRef.instance.type = request.contextType;
    componentRef.instance.remove.subscribe(() => componentRef.destroy());
    this.components.push(componentRef);
}
Let’s walk through what is happening here to discuss each step:

  1. Create the factory variable to hold the correct factory depending on the data in the request.  This demonstrates how we can programmatically determine which type of component to create.  We can select between as many components as we want as long as the component is defined as an entry component.
  2.  Create a target variable to hold the correct ViewContainerRef where the component should go.  Here we are selecting by using the index value passed in by the request object.
  3. Create the component by passing the factory to the ViewContainerRef instance.  The reference to the resulting component is returned from this method so we can access that later to modify the component.
  4. Set input values on the component by interacting with the instance on the ComponentRef handle created in step 3.
  5. Subscribe to output events using the same instance property.  Here you can see that when the remove event is fired we call the destroy method on the ComponentRef.  This triggers Angular to remove the component from the DOM and fire all the destroy life-cycle hooks.
  6. Keep a reference to all the components we create.  We don’t further interact with them in this example but it is often helpful to keep this reference for further interaction (generally destroying the component from some other interaction).

Demonstration

Now that all the parts are in place, let’s test and see how this can work.

Here you can see the components getting created in different locations and styles dynamically, they also function as independent components by tracking the creation time and closing individually.

Conclusion

Dynamically creating components is a powerful technique in Angular that can greatly improve the functionality of an application.  A key item to note is that the creation of an item is not limited to a component, services can generate components as long as they are provided a ViewContainerRef to determine where the component should go.  Placing a root level component subscribing to a service is an excellent way to provide global error or notification messages to an application by simply calling a centralized messaging service.

About Intertech

Founded in 1991, Intertech delivers software development consulting to Fortune 500, Government, and Leading Technology institutions, along with real-world based corporate education services. Whether you are a company looking to partner with a team of technology leaders who provide solutions, mentor staff and add true business value, or a developer interested in working for a company that invests in its employees, we’d like to meet you. Learn more about us.