Dynamically Load Angular Components

Infragistics Team / Friday, June 15, 2018

Article by Jared Fineman

Static is the force that makes things cling to your clothes; it’s also the type of component that is traditionally created in an Angular html file. While static components get the job done most of the time, when it comes to certain complex scenarios, we need our components to act a bit more dynamically. In this article, I hope to explore how to dynamically load components, as well as the rationale behind it.

Let’s begin with the problem. We have various types of components that we would like to display based on a selection the user makes within the UI. Choices are dynamic, however, Angular components are not; so, how would we be able to toggle between components in our view? The basic solution would be to annotate our static components with ngIf directives, thereby, circumventing the issue completely.

<container-element>
<some-element *ngIf="myChoice === filterComponent">...</some-element>
<some-element *ngIf="myChoice === sortingComponent">...</some-element>
      <some-element *ngIf="myChoice === editingComponent">...</some-element>
<ng-container *ngIf="myChoice === movableComponent">
      </ng-container>
</container-element>

We could even kick it up a notch and use an ngSwitch directive for a cleaner view of our DOM.

<container-element [ngSwitch]="myChoice">
<some-element *ngSwitchCase="filterComponent">...</some-element>
      <some-element *ngSwitchCase="sortingComponent">...</some-element>
      <some-element *ngSwitchCase="editingComponent">...</some-element>
<ng-container *ngSwitchCase="movableComponent">
      </ng-container>
      	<some-element *ngSwitchDefault>...</some-element>
</container-element>

All this is fine and well when our choices are somewhere near four to five components, but what if we are dealing with something like a dashboard that has fifty components that need to be instantiated dynamically? Maintaining that type of switch statement might mean lights out for the quality of our code.

This leads us to our second approach, creating a directive that will dynamically load our components. We start like any other directive, making sure we include an input property that will contain all the information we need in order to create our component.

@Directive({ selector: '[loader]' })
export class LoaderDirective {
    component: myComponent;
@Input()
private componentContext: ComponentContext;

constructor(private _elRef: ElementRef,
      	private _viewContainerRef: ViewContainerRef, 
private _componentFactoryResolver: ComponentFactoryResolver) {
    }
}

Take notice of the ComponentFactoryResolver and ViewContainerRef classes being injected into our constructor, these classes are going to be responsible for the creation of our component and its integration into our directive’s view.

Our directive begins by creating our component factory, a fancy way of saying, the object that knows how to build our component.

let componentFactory = this._componentFactoryResolver
.resolveComponentFactory(this.componentContext.component);

If we look at the source code, we see that the resolveComponentFactory method takes a parameter of type…well Type. The Type class is the base class of every component, in other words, insert your component’s class name here in order to create its factory.

abstract resolveComponentFactory<T>(component: Type<T>): ComponentFactory<T>;

We then proceed by clearing the view of our directive, to avoid possible duplicity, followed by the creation of our component.

this._viewContainerRef.clear();

let componentRef = 
this._viewContainerRef.createComponent(componentFactory);

this.component = (<IComponent>componentRef.instance);
      this.component.componentContext = this.componentContext;

While the createComponent function call of the viewContainerRef object is enough to instantiate our component in the DOM, we create a variable to hold reference to our component in order to pass in any values needed for its configuration. What’s neat is that this variable acts as a live reference to our component, any values changed will trigger the component’s detection system and bind values in its view.

Finally, we place our loader directive on an ­ng-template tag and pass in values to the component via the componentContext input attribute.

<ng-template 
loader 
[componentContext]=
"{component: component, name: name, color: color, parentId: parentId}">
</ng-template>

One additional thing to note about components loaded dynamically is that since their selectors are not found in the DOM the Angular compiler does not generate a ComponentFactory for them. Therefore, in order to by-pass this issue we need to register our component in our module’s entryComponents collection.

As you can imagine, being able to dynamically load components is quite the powerful tool and could even be leveraged in a data driven model where the view weaves the underlying data together with its corresponding component.

If you like this post, please like it and share it. In addition, if you haven’t checked out Infragistics Ignite UI for Angular, be sure to do so! They’ve got 50+ Material-based Angular components to help you code speedy web apps faster.