Angular.js

Dynamic component loading with Angular2: replace compile

Just a quick article to show how you can do dynamic compilation in Angular 2. In Angular 1 we had the $compile directive, which we could use to programmatically compile a string and resolve any directives and other variable interpolations. In angular 2 there isn’t really an alternative for this and we have to jump through some hoops to get the same behavior. Luckily though a couple of smart guys at stackoverflow found out how to do this with the newer Angular 2 releases (http://stackoverflow.com/questions/34784778/equivalent-of-compile-in-ang…). This article provides a summary of how to do this yourself, since I had a bit of trouble getting it to work just from the answer on stackoverflow. The solution shown here simplifies some steps from the stackoverflow answer.

I’ve tested this setup using Angular2 RC-4, and it should work with future versions as well. To get everything working we need to take the following steps:

  1. Create a component dynamically where we pass in our HTML Template, and any required directives.
  2. In the component where we want to use this, define a provider and inject the builder
  3. And add a reference in the template where we want to inject the content.
  4. Now we create our component dynamically and inject it.

Before we look at the individual steps, lets look at the final result. Below you can find an embedded plunkr, showing dynamic components in action:
Angular 2 Example – Dynamic Component Loading

<!DOCTYPE html>
<html>
  <head>
    <title>Angular 2 Dynamic Component Loading</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="styles.css">

    <!-- 1. Load libraries -->
     <!-- Polyfill(s) for older browsers -->
    <script src="https://npmcdn.com/core-js/client/shim.min.js"></script>

    <script src="https://npmcdn.com/zone.js@0.6.12?main=browser"></script>
    <script src="https://npmcdn.com/reflect-metadata@0.1.3"></script>
    <script src="https://npmcdn.com/systemjs@0.19.27/dist/system.src.js"></script>

    <!-- 2. Configure SystemJS -->
    <script src="systemjs.config.js"></script>
    <script>
      System.import('app').catch(function(err){ console.error(err); });
    </script>
  </head>

  <!-- 3. Display the application -->
  <body>
    <my-app>Loading...</my-app>
  </body>
</html>


<!-- 
Copyright 2016 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license
-->

Now lets look at the steps

Create a component dynamically

To dynamically add content and have angular compile it, we need to create a simple component dynamically which will contain our template and references any directives or pipes we’d like to use. Doing this is really suprisingly easy:

import { Component } from '@angular/core';
 
export class DynamicBuilder {
 
  public createComponent(template: String, directives: any[], pipes: any[]) : any {
 
    @Component({
      template: template,
      directives: directives,
      pipes: pipes
    })
    class CustomComponent {
 
      // The name of the root property we'll bind to. Can be
      // anything, just needs to be something we can expose.
      public props: any;
    };
 
    return CustomComponent;
  }
}

As you can see we just return an annotated class, which is configured through the createComponent function. So whenever we call this function, we get a component back, configured with the provided template, directives and pipes. Now that we’ve got a way to create dynamic components, lets inject this builder into our parent component.

In the component where we want to use this, define a provider and inject the builder

Before we fill in all the elements of our parent component, lets first make sure our parent component can access
the builder we defined in the previous step. To do this, we just add it as a provider in this component, and
inject it using the constructor.

import { Component, ComponentResolver } from '@angular/core';
import { DynamicBuilder } from 'app/dynamic.component'
 
@Component({
  selector: 'my-app',
  templateUrl: 'app/app.component.html',
  providers: [DynamicBuilder]
})
export class AppComponent {
 
  ...
  constructor(private builder: DynamicBuilder, private componentResolver: ComponentResolver) {};
  ...
}

Note that besides the dynamicBuilder, we also inject a ComponentResolver. We’ll use that in the last step together with the dynamicbuilder to inject content. Before we do that, though, lets look at how we can determine where we want to add content.

Add a reference in the template where we want to inject the content.

Our complete template for this example looks like this:

<h1>Dynamic Component Loading</h1>
 
<h2>Dynamic data:</h2>
<div #dynamicChild></div>

Very basic, the only thing we add here is that we add the #dynamicChild value to the div element. We’ll use this attribute to lookup the div and inject our dynamic component inside that div.

This lookup is done like this in our parent component:

import { Component, ViewChild, ViewContainerRef, ComponentFactory, ComponentResolver } from '@angular/core';
import { DynamicBuilder } from 'app/dynamic.component'
import { timer } from 'rxjs/observable/timer';
 
@Component({
  selector: 'my-app',
  templateUrl: 'app/app.component.html',
  providers: [DynamicBuilder]
})
export class AppComponent {
 
  // we need the viewcontainer ref, so explicitly define that, or we'll get back
  // an element ref.
  @ViewChild('dynamicChild', {read: ViewContainerRef})
  private target: ViewContainerRef;
 
  constructor(private builder: DynamicBuilder, private componentResolver: ComponentResolver) {};
}

As you can see in this code, we use the @ViewChild annotation to get a reference to the div element we defined in our template. Note that we need to explicitly tell this annotation that we want a ViewContainerRef by using the read property.

Now we create our component dynamically and inject it.

We now have all the parts in place to create a new dynamic component and attach it to div from our parent element’s template. The complete code for our parent component is shown below:

import { Component, ViewChild, ViewContainerRef, ComponentFactory, ComponentResolver } from '@angular/core';
import { DynamicBuilder } from 'app/dynamic.component'
import { timer } from 'rxjs/observable/timer';
 
@Component({
  selector: 'my-app',
  templateUrl: 'app/app.component.html',
  providers: [DynamicBuilder]
})
export class AppComponent {
 
  // we need the viewcontainer ref, so explicitly define that, or we'll get back
  // an element ref.
  @ViewChild('dynamicChild', {read: ViewContainerRef})
  private target: ViewContainerRef;
 
  constructor(private builder: DynamicBuilder, private componentResolver: ComponentResolver) {};
  private timer = timer(2000,1000);
 
  public ngOnInit() {
    let template = `
      <span>Dynamic property hello: {{props?.hello}}<span><br>
      <span>Dynamic property world: {{props?.world}}<span><br>
      <span>Dynamic async property: {{props?.obs | async}}<span><br>
    `;
 
    // the properties we want to bind
    this.props = {
      hello: 'hello',
      world: 'world',
      obs: this.timer
    };
 
    //create the component, and set the properties
    let component = this.builder.createComponent(template, [], []);
    this.componentResolver.resolveComponent(component)
      .then( (factory: ComponentFactory) => {
        // create a component and assign it to the first slot
        let dynComponent = this.target.createComponent(factory,0);
        let instance: any = dynComponent.instance;
        instance.props = this.props;
      });
  }
}

The interesting code here is in the ngOnInit function. In that function we first define the template we want to render, and the properties we want to bind (note that this can be pretty much anything). Next we create the dynamic component we want to add using the createComponent function we defined earlier, passing in the template and any directives or pipes that might be needed (none in this sample). The final thing to do is use the componentResolver to process our custom component, and when that is done, connect it to the target we retrieved through the @ViewChild annotation. Finally we set the properties and we’re done.

Conclusions

This might seem like a lot of steps, but this can be easily hidden besides a simple helper function or class. The main thing that needs to be done is getting a reference to the location where you want to add your custom component, and create a definition for your custom component.
Like I mentioned in the beginning of this article all credit goes to Radim Köhler who provided an answer on stackoverflow about this subject here: http://stackoverflow.com/questions/34784778/equivalent-of-compile-in-ang…

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button