2 Ways To Dynamically Load Angular Components

nightwolfdev - Mar 15 '21 - - Dev Community

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!

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 {}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

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;
  }
});
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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) {}
}
Enter fullscreen mode Exit fullscreen mode

Now, let’s use the directive in our template at the location where we want the components to load.

<ng-container appContainer></ng-container>
Enter fullscreen mode Exit fullscreen mode

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) {}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Create a variable for the viewContainerRef that the directive exposes.

const container = this.containerDirective.viewContainerRef;
Enter fullscreen mode Exit fullscreen mode

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;
  }
});
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
  ]
})
Enter fullscreen mode Exit fullscreen mode

You now have 2 approaches to dynamically load Angular components!


Visit our website at https://nightwolf.dev and follow us on Facebook and Twitter!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .