2 Ways To Dynamically Load Angular Components
March 14, 2021 • Angular • Published By Josh • 5 minute read
Most of the time, you know which Angular component you want to use and where it should appear in the template. What if you needed to load components programmatically? Maybe the components and the order they appear should be based on some data returned by an API? Let’s learn 2 ways to dynamically load Angular components!
Table of Contents
Component Selector
When you create a component, you have to define a selector. The below example’s selector would be my-widget-a.
@Component({
selector: 'my-widget-a',
templateUrl: './widget-a.component.html',
styleUrls: [ './widget-a.component.css' ]
})
export class WidgetA {}
The component selector is what you use as the HTML tag in the template. In most cases, this is what you’re used to doing. You know what the component is and you know where to place it in the template.
<my-widget-a></my-widget-a>
Let’s say that the app allows a user to define which components are in use and what order they appear. Maybe the data looks like the following:
componentOrder = ['widget-b', 'widget-a', 'widget-c']
Based on the above data, how would we load the components programmatically? Let’s learn two different approaches!
NgComponentOutlet
The first approach is to use the NgComponentOutlet directive, which will be defined in your template exactly where you want your components to load. It needs the component type (the component class) to be passed to it. We don’t technically have that from the data being returned to us, but we can create a variable that represents that information for us. You could do something like the following:
import { WidgetA } from '/path/to/widgetA/component';
import { WidgetB } from '/path/to/widgetB/component';
import { WidgetC } from '/path/to/widgetC/component';
...
componentTypes = [];
componentOrder.forEach(entry => {
switch (entry) {
case 'widget-a':
componentTypes.push(WidgetA);
break;
case 'widget-b':
componentTypes.push(WidgetB);
break;
case 'widget-c':
componentTypes.push(WidgetC);
break;
}
});
Now that we have a variable that represents an array of component types, we can use that in the template to load them dynamically!
<ng-container *ngFor="let type of componentTypes">
<ng-container *ngComponentOutlet="type"></ng-container>
</ng-container>
The NgComponentOutlet also has the following optional attributes:
- ngComponentOutletInjector: Optional custom Injector that will be used as parent for the Component. Defaults to the injector of the current view container.
- ngComponentOutletContent: Optional list of projectable nodes to insert into the content section of the component, if exists.
- ngComponentOutletNgModuleFactory: Optional module factory to allow dynamically loading other module, then load a component from that module.
There doesn’t appear to be a way to pass Inputs and Outputs to the NgComponentOutlet. The second approach makes that easier.
ComponentFactoryResolver
The second approach is to use the ComponentFactoryResolver class, which will help us to create components programmatically. But first, we need to define a location in the template where we want the components to load, specifically using a view container reference. An easy way to do this is by creating a directive. Don’t forget to declare the directive in whatever module you’re using it in.
import { Directive, ViewContainerRef } from '@angular/core';
@Directive({
selector: 'appContainer'
})
export class ContainerDirective {
constructor(public viewContainerRef: ViewContainerRef) {}
}
Now, let’s use the directive in our template at the location where we want the components to load.
<ng-container appContainer></ng-container>
In the component where you want to create and load the components programmatically, you’ll want to import ComponentFactoryResolver and ViewChild, each component type like we did in the first approach, as well as importing the directive. Then define the componentFactoryResolver in the constructor, which will automatically create it as a variable.
import { ComponentFactoryResolver, ViewChild } from '@angular/core';
import { WidgetA } from '/path/to/widgetA/component';
import { WidgetB } from '/path/to/widgetB/component';
import { WidgetC } from '/path/to/widgetC/component';
import { ContainerDirective } from '/path/to/container/directive';
constructor(private componentFactoryResolver: componentFactoryResolver) {}
Create a variable for the container directive using ViewChild. By the way, if you’re using Angular 8, you’ll need to include a second argument of { static: false } to ViewChild. It isn’t required in newer versions.
@ViewChild(ContainerDirective) containerDirective: ContainerDirective;
// If you're using Angular 8.
@ViewChild(ContainerDirective, { static: false }) containerDirective: ContainerDirective;
Create a variable for the viewContainerRef that the directive exposes.
const container = this.containerDirective.viewContainerRef;
Now we’re ready to loop through the component order and programmatically create the components and place them into the template! Using the componentFactoryResolver, you create a factory for the component first. Then you create the component in the container using its factory.
componentOrder.forEach(entry => {
switch (entry) {
case 'widget-a':
const widgetAFactory = this.componentFactoryResolver.resolveComponent(WidgetA);
container.createComponent(widgetAFactory);
break;
case 'widget-b':
const widgetBFactory = this.componentFactoryResolver.resolveComponent(WidgetB);
container.createComponent(widgetBFactory);
break;
case 'widget-c':
const widgetCFactory = this.componentFactoryResolver.resolveComponent(WidgetC);
container.createComponent(widgetCFactory);
break;
}
});
The nice thing about this approach is that you gain access to things like Inputs and Outputs. Let’s say WidgetA has an Input called user. You can do the following:
const widgetAFactory = this.componentFactoryResolver.resolveComponent(WidgetA);
const widgetAComponent = container.createComponent(widgetAFactory);
widgetAComponent.instance.user = user;
entryComponents
If you’re getting an error about entryComponents, it’s because you’re using Angular 8 or lower. Newer versions of Angular won’t need this next step. When you want to load components dynamically, you have to define them as entryComponents in the module you’re loading them.
import { WidgetA } from '/path/to/widgetA/component';
import { WidgetB } from '/path/to/widgetB/component';
import { WidgetC } from '/path/to/widgetC/component';
@NgModule({
...
entryComponents: [
WidgetA,
WidgetB,
WidgetC
]
})
You now have 2 approaches to dynamically load Angular components!