• Dynamically Creating Components With Angular 2.0

    Summary
    **This post currently reflects an earlier version of Angular, and may not be up to date or reflect the current release. To learn more about about the latest version of Angular and migration, follow the link here **

    Reading time: 4 minutes
    Date published
    September 23, 2016

    Dynamically Creating Components With Angular 2.0

    **This post currently reflects an earlier version of Angular, and may not be up to date or reflect the current release. To learn more about about the latest version of Angular and migration, follow the link here **

    The Evolution

    Creating components has been a topic of interest at Rangle because with each RC version of Angular 2, it seemed like the way to do so had changed.

    Near the beginning there was the DynamicComponentLoader.

    After that was deprecated, there was ComponentResolver.

    And now that the dust has settled, there is the ComponentFactoryResolver!

    The (Main) Reason

    Why would I ever want to dynamically create a component?
    Modals

    • Imagine throughout your app you have buttons that trigger different modals to open
    • Some of these modals need to be initialized with some information for their state
    • And as we know, modals are best kept as children of the body tag to avoid issues

    A good solution to this problem would be a modal factory.
    The modal factory would:

    • Be given the component class, representing the modal we want, along with any information it needs
    • Use this information and create the modal on the fly
    • Keep reference to the created component and destroy it at some point

    The Code / Explanation

    Imagine we have a simple component called App that contains two buttons:

    • One to create a hello world modal component
    • One to create a world hello modal component

    In App we have another component with the selector dynamic-component. This component takes a property called componentData which gives reference to the component we want to create and any information we want to pass down.

    It could look something like this:

    @Component({
      selector: 'my-app',
      template: `
        <div>
          <h2>Lets dynamically create some components!</h2>
          <button (click)="createHelloWorldComponent()">Create Hello World</button>
          <button (click)="createWorldHelloComponent()">Create World Hello</button>
        </div>
        <dynamic-component [componentData]="componentData"></dynamic-component>
      `,
    })
    export class App {
      componentData = null;
    
      createHelloWorldComponent(){
        this.componentData = {
          component: HelloWorldComponent,
          inputs: {
            showNum: 9
          }
        };
      }
      
      createWorldHelloComponent(){
        this.componentData = {
          component: WorldHelloComponent,
          inputs: {
            showNum: 2
          }
        };
      }
    }
    

    By clicking on either of the Create Hello World or Create World Hello buttons, the appropriate event will fire, sending data into dynamic-component.
    The dynamic-component will now generate the appropriate component using the information given.

    Now let's look at how this DynamicComponent would be implemented.

    This component will need a few things:

    • Knowledge of all possible components it will be creating
    • A place to put a created component
    • Access to the ComponentFactoryResolver service
    • A variable to store a created component so we can remove it later
    import {Component, ViewContainerRef, ViewChild, ReflectiveInjector, ComponentFactoryResolver} from '@angular/core';
    import HelloWorldComponent from './hello-world-component';
    import WorldHelloComponent from './world-hello-component';
    
    @Component({
      selector: 'dynamic-component',
      entryComponents: [HelloWorldComponent, WorldHelloComponent], // Reference to the components must be here in order to dynamically create them
      template: `
        <div #dynamicComponentContainer></div>
      `,
    })
    export default class DynamicComponent {
      currentComponent = null;
      @ViewChild('dynamicComponentContainer', { read: ViewContainerRef }) dynamicComponentContainer: ViewContainerRef;
    
      constructor(private resolver: ComponentFactoryResolver) {
        
      }}
    

    As you can see above, entryComponents is where we inform DynamicComponent of the possible components we want it to create.

    We’ve attached a reference to a div in our class so that we can access it and place the component we create inside of it.

    We’ve injected the ComponentFactoryResolver into the class.

    And lastly, we've created a variable called currentComponent to store the created component.

    With all of the setup complete, we’re able to move on to the actual component creation!

    Let's implement an Input setter that takes a component class and an object with key/value pairs mapping to what we want access in the created component.

      // component: Class for the component you want to create
      // inputs: An object with key/value pairs mapped to input name/input value
      @Input() set componentData(data: {component: any, inputs: any }) {
        if (!data) {
          return;
        }
    
        // Inputs need to be in the following format to be resolved properly
        let inputProviders = Object.keys(data.inputs).map((inputName) => {return {provide: inputName, useValue: data.inputs[inputName]};});
        let resolvedInputs = ReflectiveInjector.resolve(inputProviders);
        
        // We create an injector out of the data we want to pass down and this components injector
        let injector = ReflectiveInjector.fromResolvedProviders(resolvedInputs, this.dynamicComponentContainer.parentInjector);
        
        // We create a factory out of the component we want to create
        let factory = this.resolver.resolveComponentFactory(data.component);
        
        // We create the component using the factory and the injector
        let component = factory.create(injector);
        
        // We insert the component into the dom container
        this.dynamicComponentContainer.insert(component.hostView);
    
        // Destroy the previously created component
        if (this.currentComponent) {
          this.currentComponent.destroy();
        }
        
        this.currentComponent = component;
      }
    

    The function above does the following:

    • Turns the inputs into a format Angular 2 can understand
    • Creates an injector out of the resolved inputs and DynamicComponent injector
    • Creates a factory out of the component we want to create
    • Creates a component out of the factory and injector
    • Inserts the component into the placeholder container we created earlier
    • Destroys the previously created component

    And there you have it, we’ve dynamically created a modal!

    The Example

    Here's a simple Plunker I made demonstrating what was explained above: http://plnkr.co/edit/ZXsIWykqKZi5r75VMtw2?p=preview

    More Resources

    Tags

    See what Ranglers are writing about on our blog