227

I want to dynamically create a template. This should be used to build a ComponentType at runtime and place (even replace) it somewhere inside of the hosting Component.

Until RC4 I was using ComponentResolver, but with RC5 I get the following message:

ComponentResolver is deprecated for dynamic compilation.
Use ComponentFactoryResolver together with @NgModule/@Component.entryComponents or ANALYZE_FOR_ENTRY_COMPONENTS provider instead.
For runtime compile only, you can also use Compiler.compileComponentSync/Async.

I found this document (Angular 2 Synchronous Dynamic Component Creation)

And understand that I can use either

  • Kind of dynamic ngIf with ComponentFactoryResolver. If I pass known components inside of @Component({entryComponents: [comp1, comp2], ...}) - I can use .resolveComponentFactory(componentToRender);
  • Real runtime compilation, with Compiler...

But the question is how to use that Compiler? The note above says that I should call: Compiler.compileComponentSync/Async - so how?

For example. I want to create (based on some configuration conditions) this kind of template for one kind of settings

<form>
   <string-editor
     [propertyName]="'code'"
     [entity]="entity"
   ></string-editor>
   <string-editor
     [propertyName]="'description'"
     [entity]="entity"
   ></string-editor>
   ...

and in another case this one (string-editor is replaced with text-editor)

<form>
   <text-editor
     [propertyName]="'code'"
     [entity]="entity"
   ></text-editor>
   ...

And so on (different number/date/reference editors by property types, skipped some properties for some users...). i.e. this is an example, the real configuration could generate much more different and complex templates.

The template is changing, so I cannot use ComponentFactoryResolver and pass existing ones... I need a solution with the Compiler.

4
  • SInce the solution I found was so nice I want everybody finding this question to have a look at my answer which is very far down at the very bottom at the moment. :) Commented Apr 26, 2017 at 18:37
  • The article Here is what you need to know about dynamic components in Angular has great explanation of the dynamic components. Commented Jul 29, 2017 at 17:23
  • 1
    Here's the problem with every single answer out there and what $compile could actually do that these methods can't -- I'm creating an application where I just want to compile the HTML as it comes in through a 3rd party's page and ajax calls. I can't remove the HTML from the page and place it in my own template. Sigh Commented Dec 1, 2017 at 7:19
  • @AugieGardner There is a reason why this is not possible by design. Angular is not at fault for bad architectural decisions or legacy systems that some people have. If you want to parse existing HTML-code you are free to use another framework as Angular works perfectly fine with WebComponents. Setting clear boundaries to guide the hordes of inexperienced programmers is more important than allowing dirty hacks for few legacy systems.
    – Phil
    Commented Jun 4, 2018 at 14:33

16 Answers 16

174

EDIT - related to 2.3.0 (2016-12-07)

NOTE: to get solution for previous version, check the history of this post

Similar topic is discussed here Equivalent of $compile in Angular 2. We need to use JitCompiler and NgModule. Read more about NgModule in Angular2 here:

In a Nutshell

There is a working plunker/example (dynamic template, dynamic component type, dynamic module,JitCompiler, ... in action)

The principal is:
1) create Template
2) find ComponentFactory in cache - go to 7)
3) - create Component
4) - create Module
5) - compile Module
6) - return (and cache for later use) ComponentFactory
7) use Target and ComponentFactory to create an Instance of dynamic Component

Here is a code snippet (more of it here) - Our custom Builder is returning just built/cached ComponentFactory and the view Target placeholder consume to create an instance of the DynamicComponent

  // here we get a TEMPLATE with dynamic content === TODO
  var template = this.templateBuilder.prepareTemplate(this.entity, useTextarea);

  // here we get Factory (just compiled or from cache)
  this.typeBuilder
      .createComponentFactory(template)
      .then((factory: ComponentFactory<IHaveDynamicData>) =>
    {
        // Target will instantiate and inject component (we'll keep reference to it)
        this.componentRef = this
            .dynamicComponentTarget
            .createComponent(factory);

        // let's inject @Inputs to component instance
        let component = this.componentRef.instance;

        component.entity = this.entity;
        //...
    });

This is it - in nutshell it. To get more details.. read below

.

TL&DR

Observe a plunker and come back to read details in case some snippet requires more explanation

.

Detailed explanation - Angular2 RC6++ & runtime components

Below description of this scenario, we will

  1. create a module PartsModule:NgModule (holder of small pieces)
  2. create another module DynamicModule:NgModule, which will contain our dynamic component (and reference PartsModule dynamically)
  3. create dynamic Template (simple approach)
  4. create new Component type (only if template has changed)
  5. create new RuntimeModule:NgModule. This module will contain the previously created Component type
  6. call JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule) to get ComponentFactory
  7. create an Instance of the DynamicComponent - job of the View Target placeholder and ComponentFactory
  8. assign @Inputs to new instance (switch from INPUT to TEXTAREA editing), consume @Outputs

NgModule

We need an NgModules.

While I would like to show a very simple example, in this case, I would need three modules (in fact 4 - but I do not count the AppModule). Please, take this rather than a simple snippet as a basis for a really solid dynamic component generator.

There will be one module for all small components, e.g. string-editor, text-editor (date-editor, number-editor...)

@NgModule({
  imports:      [ 
      CommonModule,
      FormsModule
  ],
  declarations: [
      DYNAMIC_DIRECTIVES
  ],
  exports: [
      DYNAMIC_DIRECTIVES,
      CommonModule,
      FormsModule
  ]
})
export class PartsModule { }

Where DYNAMIC_DIRECTIVES are extensible and are intended to hold all small parts used for our dynamic Component template/type. Check app/parts/parts.module.ts

The second will be module for our Dynamic stuff handling. It will contain hosting components and some providers.. which will be singletons. Therefor we will publish them standard way - with forRoot()

import { DynamicDetail }          from './detail.view';
import { DynamicTypeBuilder }     from './type.builder';
import { DynamicTemplateBuilder } from './template.builder';

@NgModule({
  imports:      [ PartsModule ],
  declarations: [ DynamicDetail ],
  exports:      [ DynamicDetail],
})

export class DynamicModule {

    static forRoot()
    {
        return {
            ngModule: DynamicModule,
            providers: [ // singletons accross the whole app
              DynamicTemplateBuilder,
              DynamicTypeBuilder
            ], 
        };
    }
}

Check the usage of the forRoot() in the AppModule

Finally, we will need an adhoc, runtime module.. but that will be created later, as a part of DynamicTypeBuilder job.

The forth module, application module, is the one who keeps declares compiler providers:

...
import { COMPILER_PROVIDERS } from '@angular/compiler';    
import { AppComponent }   from './app.component';
import { DynamicModule }    from './dynamic/dynamic.module';

@NgModule({
  imports:      [ 
    BrowserModule,
    DynamicModule.forRoot() // singletons
  ],
  declarations: [ AppComponent],
  providers: [
    COMPILER_PROVIDERS // this is an app singleton declaration
  ],

Read (do read) much more about NgModule there:

A template builder

In our example we will process detail of this kind of entity

entity = { 
    code: "ABC123",
    description: "A description of this Entity" 
};

To create a template, in this plunker we use this simple/naive builder.

The real solution, a real template builder, is the place where your application can do a lot

// plunker - app/dynamic/template.builder.ts
import {Injectable} from "@angular/core";

@Injectable()
export class DynamicTemplateBuilder {

    public prepareTemplate(entity: any, useTextarea: boolean){
      
      let properties = Object.keys(entity);
      let template = "<form >";
      let editorName = useTextarea 
        ? "text-editor"
        : "string-editor";
        
      properties.forEach((propertyName) =>{
        template += `
          <${editorName}
              [propertyName]="'${propertyName}'"
              [entity]="entity"
          ></${editorName}>`;
      });
  
      return template + "</form>";
    }
}

A trick here is - it builds a template which uses some set of known properties, e.g. entity. Such property(-ies) must be part of dynamic component, which we will create next.

To make it a bit more easier, we can use an interface to define properties, which our Template builder can use. This will be implemented by our dynamic Component type.

export interface IHaveDynamicData { 
    public entity: any;
    ...
}

A ComponentFactory builder

Very important thing here is to keep in mind:

our component type, build with our DynamicTypeBuilder, could differ - but only by its template (created above). Components' properties (inputs, outputs or some protected) are still same. If we need different properties, we should define different combination of Template and Type Builder

So, we are touching the core of our solution. The Builder, will 1) create ComponentType 2) create its NgModule 3) compile ComponentFactory 4) cache it for later reuse.

An dependency we need to receive:

// plunker - app/dynamic/type.builder.ts
import { JitCompiler } from '@angular/compiler';
    
@Injectable()
export class DynamicTypeBuilder {

  // wee need Dynamic component builder
  constructor(
    protected compiler: JitCompiler
  ) {}

And here is a snippet how to get a ComponentFactory:

// plunker - app/dynamic/type.builder.ts
// this object is singleton - so we can use this as a cache
private _cacheOfFactories:
     {[templateKey: string]: ComponentFactory<IHaveDynamicData>} = {};
  
public createComponentFactory(template: string)
    : Promise<ComponentFactory<IHaveDynamicData>> {    
    let factory = this._cacheOfFactories[template];

    if (factory) {
        console.log("Module and Type are returned from cache")
       
        return new Promise((resolve) => {
            resolve(factory);
        });
    }
    
    // unknown template ... let's create a Type for it
    let type   = this.createNewComponent(template);
    let module = this.createComponentModule(type);
    
    return new Promise((resolve) => {
        this.compiler
            .compileModuleAndAllComponentsAsync(module)
            .then((moduleWithFactories) =>
            {
                factory = _.find(moduleWithFactories.componentFactories
                                , { componentType: type });

                this._cacheOfFactories[template] = factory;

                resolve(factory);
            });
    });
}

Above we create and cache both Component and Module. Because if the template (in fact the real dynamic part of that all) is the same.. we can reuse

And here are two methods, which represent the really cool way how to create a decorated classes/types in runtime. Not only @Component but also the @NgModule

protected createNewComponent (tmpl:string) {
  @Component({
      selector: 'dynamic-component',
      template: tmpl,
  })
  class CustomDynamicComponent  implements IHaveDynamicData {
      @Input()  public entity: any;
  };
  // a component for this particular template
  return CustomDynamicComponent;
}
protected createComponentModule (componentType: any) {
  @NgModule({
    imports: [
      PartsModule, // there are 'text-editor', 'string-editor'...
    ],
    declarations: [
      componentType
    ],
  })
  class RuntimeComponentModule
  {
  }
  // a module for just this Type
  return RuntimeComponentModule;
}

Important:

our component dynamic types differ, but just by template. So we use that fact to cache them. This is really very important. Angular2 will also cache these.. by the type. And if we would recreate for the same template strings new types... we will start to generate memory leaks.

ComponentFactory used by hosting component

Final piece is a component, which hosts the target for our dynamic component, e.g. <div #dynamicContentPlaceHolder></div>. We get a reference to it and use ComponentFactory to create a component. That is in a nutshell, and here are all the pieces of that component (if needed, open plunker here)

Let's firstly summarize import statements:

import {Component, ComponentRef,ViewChild,ViewContainerRef}   from '@angular/core';
import {AfterViewInit,OnInit,OnDestroy,OnChanges,SimpleChange} from '@angular/core';

import { IHaveDynamicData, DynamicTypeBuilder } from './type.builder';
import { DynamicTemplateBuilder }               from './template.builder';

@Component({
  selector: 'dynamic-detail',
  template: `
<div>
  check/uncheck to use INPUT vs TEXTAREA:
  <input type="checkbox" #val (click)="refreshContent(val.checked)" /><hr />
  <div #dynamicContentPlaceHolder></div>  <hr />
  entity: <pre>{{entity | json}}</pre>
</div>
`,
})
export class DynamicDetail implements AfterViewInit, OnChanges, OnDestroy, OnInit
{ 
    // wee need Dynamic component builder
    constructor(
        protected typeBuilder: DynamicTypeBuilder,
        protected templateBuilder: DynamicTemplateBuilder
    ) {}
    ...

We just receive, template and component builders. Next are properties which are needed for our example (more in comments)

// reference for a <div> with #dynamicContentPlaceHolder
@ViewChild('dynamicContentPlaceHolder', {read: ViewContainerRef}) 
protected dynamicComponentTarget: ViewContainerRef;
// this will be reference to dynamic content - to be able to destroy it
protected componentRef: ComponentRef<IHaveDynamicData>;

// until ngAfterViewInit, we cannot start (firstly) to process dynamic stuff
protected wasViewInitialized = false;

// example entity ... to be recieved from other app parts
// this is kind of candiate for @Input
protected entity = { 
    code: "ABC123",
    description: "A description of this Entity" 
  };

In this simple scenario, our hosting component does not have any @Input. So it does not have to react to changes. But despite of that fact (and to be ready for coming changes) - we need to introduce some flag if the component was already (firstly) initiated. And only then we can start the magic.

Finally we will use our component builder, and its just compiled/cached ComponentFacotry. Our Target placeholder will be asked to instantiate the Component with that factory.

protected refreshContent(useTextarea: boolean = false){
  
  if (this.componentRef) {
      this.componentRef.destroy();
  }
  
  // here we get a TEMPLATE with dynamic content === TODO
  var template = this.templateBuilder.prepareTemplate(this.entity, useTextarea);

  // here we get Factory (just compiled or from cache)
  this.typeBuilder
      .createComponentFactory(template)
      .then((factory: ComponentFactory<IHaveDynamicData>) =>
    {
        // Target will instantiate and inject component (we'll keep reference to it)
        this.componentRef = this
            .dynamicComponentTarget
            .createComponent(factory);

        // let's inject @Inputs to component instance
        let component = this.componentRef.instance;

        component.entity = this.entity;
        //...
    });
}

small extension

Also, we need to keep a reference to compiled template.. to be able properly destroy() it, whenever we will change it.

// this is the best moment where to start to process dynamic stuff
public ngAfterViewInit(): void
{
    this.wasViewInitialized = true;
    this.refreshContent();
}
// wasViewInitialized is an IMPORTANT switch 
// when this component would have its own changing @Input()
// - then we have to wait till view is intialized - first OnChange is too soon
public ngOnChanges(changes: {[key: string]: SimpleChange}): void
{
    if (this.wasViewInitialized) {
        return;
    }
    this.refreshContent();
}

public ngOnDestroy(){
  if (this.componentRef) {
      this.componentRef.destroy();
      this.componentRef = null;
  }
}

done

That is pretty much it. Do not forget to Destroy anything what was built dynamically (ngOnDestroy). Also, be sure to cache dynamic types and modules if the only difference is their template.

Check it all in action here

to see previous versions (e.g. RC5 related) of this post, check the history

22
  • 56
    this look like such a complicated solution, the deprecated one was very simple and clear, is there other way to do this ? Commented Aug 29, 2016 at 19:36
  • 3
    I think the same way as @tibbus: this got way more complicated than it used to be with the deprecated code. Thanks for your answer, though. Commented Sep 5, 2016 at 17:24
  • 6
    @ribsies thanks for your note. Let me clarify something. Many other answers try to make it simple. But I am trying to explain it and show it in a scenario, closed to real usage. We would need to cache stuff, we would have to call destroy on re-creation etc. So, while the magic of dynamic building is really in type.builder.ts as you've pointed, I wish, that any user would understand how to place that all into context... Hope it could be useful ;) Commented Oct 7, 2016 at 5:13
  • 7
    @Radim Köhler - I have tried this example. it's working without AOT. But when I tried to do run this with AOT it shows error "No NgModule metadata found for RuntimeComponentModule". can you plz help me to solve this error .
    – Trusha
    Commented Mar 2, 2017 at 7:54
  • 5
    The answer itself is perfect! But for real life applications not practicable. The angular team should provide a solution for this in the framework, as this is common requirement in business applications. If not, it has to be asked if Angular 2 is the right platform for business applications.
    – Karl
    Commented Mar 17, 2017 at 11:22
59

EDIT (26/08/2017): The solution below works well with Angular2 and 4. I've updated it to contain a template variable and click handler and tested it with Angular 4.3.
For Angular4, ngComponentOutlet as described in Ophir's answer is a much better solution. But right now it does not support inputs & outputs yet. If [this PR](https://github.com/angular/angular/pull/15362] is accepted, it would be possible through the component instance returned by the create event.
ng-dynamic-component may be the best and simplest solution altogether, but I haven't tested that yet.

@Long Field's answer is spot on! Here's another (synchronous) example:

import {Compiler, Component, NgModule, OnInit, ViewChild,
  ViewContainerRef} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'

@Component({
  selector: 'my-app',
  template: `<h1>Dynamic template:</h1>
             <div #container></div>`
})
export class App implements OnInit {
  @ViewChild('container', { read: ViewContainerRef }) container: ViewContainerRef;

  constructor(private compiler: Compiler) {}

  ngOnInit() {
    this.addComponent(
      `<h4 (click)="increaseCounter()">
        Click to increase: {{counter}}
      `enter code here` </h4>`,
      {
        counter: 1,
        increaseCounter: function () {
          this.counter++;
        }
      }
    );
  }

  private addComponent(template: string, properties?: any = {}) {
    @Component({template})
    class TemplateComponent {}

    @NgModule({declarations: [TemplateComponent]})
    class TemplateModule {}

    const mod = this.compiler.compileModuleAndAllComponentsSync(TemplateModule);
    const factory = mod.componentFactories.find((comp) =>
      comp.componentType === TemplateComponent
    );
    const component = this.container.createComponent(factory);
    Object.assign(component.instance, properties);
    // If properties are changed at a later stage, the change detection
    // may need to be triggered manually:
    // component.changeDetectorRef.detectChanges();
  }
}

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ App ],
  bootstrap: [ App ]
})
export class AppModule {}

Live at http://plnkr.co/edit/fdP9Oc.

17
  • 3
    I'd say, that it is an example how to write as less code as possible to do the same as in my answer stackoverflow.com/a/38888009/1679310. In case, that it should be useful-case (mostly RE-generating template) when condition changes... the simple ngAfterViewInit call with a const template won't work. But if your task was to reduce the above detailed described approach (create template, create component, create module, compile it, create factory.. create instance)... you probably did it Commented Sep 15, 2016 at 10:02
  • Thanks for the solution: I am having trouble loading templateUrl and styles though, I get the following error: No ResourceLoader implementation has been provided . Can't read the url localhost:3000/app/pages/pages_common.css, any idea what I am missing?
    – Gerardlamo
    Commented Oct 7, 2016 at 13:01
  • Could it possible to compile the html template with data specific for cell in grid like control.? plnkr.co/edit/vJHUCnsJB7cwNJr2cCwp?p=preview In this plunker, how can I compile the and show the image in last column.? Any help.?
    – Karthick
    Commented Oct 18, 2016 at 6:02
  • 1
    @monnef, you're right. I didn't check the console log. I've adjusted the code to add the component in the ngOnInit rather than the ngAfterViewInit hook, as the former is triggered before and the latter after the change detection. (See github.com/angular/angular/issues/10131 and similar threads.) Commented Feb 9, 2017 at 12:51
  • 1
    neat and simple. Worked as expected when serving over browser in dev. But does this work with AOT? When the app is run in PROD after compilation, I get an "Error: Runtime compiler is not loaded" at the moment the component compilation is attempted. (btw, I am using Ionic 3.5)
    – mymo
    Commented Jul 29, 2017 at 23:09
52

I must have arrived at the party late, none of the solutions here seemed helpful to me - too messy and felt like too much of a workaround.

What I ended up doing is using Angular 4.0.0-beta.6's ngComponentOutlet.

This gave me the shortest, simplest solution all written in the dynamic component's file.

  • Here is a simple example which just receives text and places it in a template, but obviously you can change according to your need:
import {
  Component, OnInit, Input, NgModule, NgModuleFactory, Compiler
} from '@angular/core';

@Component({
  selector: 'my-component',
  template: `<ng-container *ngComponentOutlet="dynamicComponent;
                            ngModuleFactory: dynamicModule;"></ng-container>`,
  styleUrls: ['my.component.css']
})
export class MyComponent implements OnInit {
  dynamicComponent;
  dynamicModule: NgModuleFactory<any>;

  @Input()
  text: string;

  constructor(private compiler: Compiler) {
  }

  ngOnInit() {
    this.dynamicComponent = this.createNewComponent(this.text);
    this.dynamicModule = this.compiler.compileModuleSync(this.createComponentModule(this.dynamicComponent));
  }

  protected createComponentModule (componentType: any) {
    @NgModule({
      imports: [],
      declarations: [
        componentType
      ],
      entryComponents: [componentType]
    })
    class RuntimeComponentModule
    {
    }
    // a module for just this Type
    return RuntimeComponentModule;
  }

  protected createNewComponent (text:string) {
    let template = `dynamically created template with text: ${text}`;

    @Component({
      selector: 'dynamic-component',
      template: template
    })
    class DynamicComponent implements OnInit{
       text: any;

       ngOnInit() {
       this.text = text;
       }
    }
    return DynamicComponent;
  }
}
  • Short explanation:
    1. my-component - the component in which a dynamic component is rendering
    2. DynamicComponent - the component to be dynamically built and it is rendering inside my-component

Don't forget to upgrade all the angular libraries to ^Angular 4.0.0

Hope this helps, good luck!

UPDATE

Also works for angular 5.

17
  • 3
    This worked great for me with Angular4. The only adjustment I had to make was to be able to specify import modules for the dynamically created RuntimeComponentModule. Commented Apr 22, 2017 at 6:06
  • 8
    Here's a quick example starting from the Angular Quickstart: embed.plnkr.co/9L72KpobVvY14uiQjo4p Commented Apr 24, 2017 at 17:11
  • 8
    Does this solution work with "ng build --prod"? It seems that the compiler class and AoT do not fit together atm. Commented Jan 23, 2018 at 20:52
  • 2
    @OphirStern I also discovered that is approach works well in Angular 5 but NOT with the --prod build flag.
    – TaeKwonJoe
    Commented Feb 26, 2018 at 22:16
  • 2
    I tested it with angular 5 (5.2.8) using the JitCompilerFactory and using the --prod flag does not work! Does anyone have a solution? (BTW JitCompilerFactory without the --prod flag works flawlessly)
    – Frank
    Commented Mar 9, 2018 at 8:36
26

2019 June answer

Great news! It seems that the @angular/cdk package now has first-class support for portals!

As of the time of writing, I didn't find the above official docs particularly helpful (particularly with regard to sending data into and receiving events from the dynamic components). In summary, you will need to:

Step 1) Update your AppModule

Import PortalModule from the @angular/cdk/portal package and register your dynamic component(s) inside entryComponents

@NgModule({
  declarations: [ ..., AppComponent, MyDynamicComponent, ... ]
  imports:      [ ..., PortalModule, ... ],
  entryComponents: [ ..., MyDynamicComponent, ... ]
})
export class AppModule { }

Step 2. Option A: If you do NOT need to pass data into and receive events from your dynamic components:

@Component({
  selector: 'my-app',
  template: `
    <button (click)="onClickAddChild()">Click to add child component</button>
    <ng-template [cdkPortalOutlet]="myPortal"></ng-template>
  `
})
export class AppComponent  {
  myPortal: ComponentPortal<any>;
  onClickAddChild() {
    this.myPortal = new ComponentPortal(MyDynamicComponent);
  }
}

@Component({
  selector: 'app-child',
  template: `<p>I am a child.</p>`
})
export class MyDynamicComponent{
}

See it in action

Step 2. Option B: If you DO need to pass data into and receive events from your dynamic components:

// A bit of boilerplate here. Recommend putting this function in a utils 
// file in order to keep your component code a little cleaner.
function createDomPortalHost(elRef: ElementRef, injector: Injector) {
  return new DomPortalHost(
    elRef.nativeElement,
    injector.get(ComponentFactoryResolver),
    injector.get(ApplicationRef),
    injector
  );
}

@Component({
  selector: 'my-app',
  template: `
    <button (click)="onClickAddChild()">Click to add random child component</button>
    <div #portalHost></div>
  `
})
export class AppComponent {

  portalHost: DomPortalHost;
  @ViewChild('portalHost') elRef: ElementRef;

  constructor(readonly injector: Injector) {
  }

  ngOnInit() {
    this.portalHost = createDomPortalHost(this.elRef, this.injector);
  }

  onClickAddChild() {
    const myPortal = new ComponentPortal(MyDynamicComponent);
    const componentRef = this.portalHost.attach(myPortal);
    setTimeout(() => componentRef.instance.myInput 
      = '> This is data passed from AppComponent <', 1000);
    // ... if we had an output called 'myOutput' in a child component, 
    // this is how we would receive events...
    // this.componentRef.instance.myOutput.subscribe(() => ...);
  }
}

@Component({
  selector: 'app-child',
  template: `<p>I am a child. <strong>{{myInput}}</strong></p>`
})
export class MyDynamicComponent {
  @Input() myInput = '';
}

See it in action

7
  • 1
    Dude, You just nailed. This one will get attention. I couldn't believe how damn difficult is to add a simple dynamic component in Angular until I needed to do one. It's like doing reset and go back to pre-JQuery times.
    – Gi1ber7
    Commented Feb 6, 2019 at 2:51
  • 2
    @Gi1ber7 I know right? Why did it take them this long? Commented Feb 6, 2019 at 3:14
  • 1
    Nice approach, but do you know how to pass parameters to ChildComponent ?
    – Snook
    Commented Apr 29, 2019 at 12:33
  • 4
    @StephenPaul How does this Portal approach differ from ngTemplateOutlet and ngComponentOutlet? 🤔 Commented Jun 17, 2019 at 12:06
  • 2
    I agree this addresses how to do dynamic components with portal but it's less clear to me how this allows the OP to do dynamic templates with those dynamic components. Seems like the template in MyDynamicComponent is compiled by AOT and the dynamic part of this is just the component / portal part. So it seems like half the answer but not all of the answer.
    – John Q
    Commented May 27, 2020 at 21:28
18

I decided to compact everything I learned into one file. There's a lot to take in here especially compared to before RC5. Note that this source file includes the AppModule and AppComponent.

import {
  Component, Input, ReflectiveInjector, ViewContainerRef, Compiler, NgModule, ModuleWithComponentFactories,
  OnInit, ViewChild
} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';

@Component({
  selector: 'app-dynamic',
  template: '<h4>Dynamic Components</h4><br>'
})
export class DynamicComponentRenderer implements OnInit {

  factory: ModuleWithComponentFactories<DynamicModule>;

  constructor(private vcRef: ViewContainerRef, private compiler: Compiler) { }

  ngOnInit() {
    if (!this.factory) {
      const dynamicComponents = {
        sayName1: {comp: SayNameComponent, inputs: {name: 'Andrew Wiles'}},
        sayAge1: {comp: SayAgeComponent, inputs: {age: 30}},
        sayName2: {comp: SayNameComponent, inputs: {name: 'Richard Taylor'}},
        sayAge2: {comp: SayAgeComponent, inputs: {age: 25}}};
      this.compiler.compileModuleAndAllComponentsAsync(DynamicModule)
        .then((moduleWithComponentFactories: ModuleWithComponentFactories<DynamicModule>) => {
          this.factory = moduleWithComponentFactories;
          Object.keys(dynamicComponents).forEach(k => {
            this.add(dynamicComponents[k]);
          })
        });
    }
  }

  addNewName(value: string) {
    this.add({comp: SayNameComponent, inputs: {name: value}})
  }

  addNewAge(value: number) {
    this.add({comp: SayAgeComponent, inputs: {age: value}})
  }

  add(comp: any) {
    const compFactory = this.factory.componentFactories.find(x => x.componentType === comp.comp);
    // If we don't want to hold a reference to the component type, we can also say: const compFactory = this.factory.componentFactories.find(x => x.selector === 'my-component-selector');
    const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
    const cmpRef = this.vcRef.createComponent(compFactory, this.vcRef.length, injector, []);
    Object.keys(comp.inputs).forEach(i => cmpRef.instance[i] = comp.inputs[i]);
  }
}

@Component({
  selector: 'app-age',
  template: '<div>My age is {{age}}!</div>'
})
class SayAgeComponent {
  @Input() public age: number;
};

@Component({
  selector: 'app-name',
  template: '<div>My name is {{name}}!</div>'
})
class SayNameComponent {
  @Input() public name: string;
};

@NgModule({
  imports: [BrowserModule],
  declarations: [SayAgeComponent, SayNameComponent]
})
class DynamicModule {}

@Component({
  selector: 'app-root',
  template: `
        <h3>{{message}}</h3>
        <app-dynamic #ad></app-dynamic>
        <br>
        <input #name type="text" placeholder="name">
        <button (click)="ad.addNewName(name.value)">Add Name</button>
        <br>
        <input #age type="number" placeholder="age">
        <button (click)="ad.addNewAge(age.value)">Add Age</button>
    `,
})
export class AppComponent {
  message = 'this is app component';
  @ViewChild(DynamicComponentRenderer) dcr;

}

@NgModule({
  imports: [BrowserModule],
  declarations: [AppComponent, DynamicComponentRenderer],
  bootstrap: [AppComponent]
})
export class AppModule {}`
10

I have a simple example to show how to do angular 2 rc6 dynamic component.

Say, you have a dynamic html template = template1 and want to dynamic load, firstly wrap into component

@Component({template: template1})
class DynamicComponent {}

here template1 as html, may be contains ng2 component

From rc6, have to have @NgModule wrap this component. @NgModule, just like module in anglarJS 1, it decouple different part of ng2 application, so:

@Component({
  template: template1,

})
class DynamicComponent {

}
@NgModule({
  imports: [BrowserModule,RouterModule],
  declarations: [DynamicComponent]
})
class DynamicModule { }

(Here import RouterModule as in my example there is some route components in my html as you can see later on)

Now you can compile DynamicModule as: this.compiler.compileModuleAndAllComponentsAsync(DynamicModule).then( factory => factory.componentFactories.find(x => x.componentType === DynamicComponent))

And we need put above in app.moudule.ts to load it, please see my app.moudle.ts. For more and full details check: https://github.com/Longfld/DynamicalRouter/blob/master/app/MyRouterLink.ts and app.moudle.ts

and see demo: http://plnkr.co/edit/1fdAYP5PAbiHdJfTKgWo?p=preview

2
  • 3
    So, you've declared module1, module2, module3. And if you would need another "dynamic" template content, you would need to create a defintion (file) form moudle4 (module4.ts), right? If yes, that does not seem to be dynamic. It is static, is not it? Or do I miss something? Commented Sep 6, 2016 at 6:16
  • In above " template1" is string of html , you can put anything in it and we call this dynamic template, as this question is asking
    – Long Field
    Commented Sep 6, 2016 at 7:05
6

In angular 7.x I used angular-elements for this.

  1. Install @angular-elements npm i @angular/elements -s

  2. Create accessory service.

import { Injectable, Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { IStringAnyMap } from 'src/app/core/models';
import { AppUserIconComponent } from 'src/app/shared';

const COMPONENTS = {
  'user-icon': AppUserIconComponent
};

@Injectable({
  providedIn: 'root'
})
export class DynamicComponentsService {
  constructor(private injector: Injector) {

  }

  public register(): void {
    Object.entries(COMPONENTS).forEach(([key, component]: [string, any]) => {
      const CustomElement = createCustomElement(component, { injector: this.injector });
      customElements.define(key, CustomElement);
    });
  }

  public create(tagName: string, data: IStringAnyMap = {}): HTMLElement {
    const customEl = document.createElement(tagName);

    Object.entries(data).forEach(([key, value]: [string, any]) => {
      customEl[key] = value;
    });

    return customEl;
  }
}

Note that you custom element tag must be different with angular component selector. in AppUserIconComponent:

...
selector: app-user-icon
...

and in this case custom tag name I used "user-icon".

  1. Then you must call register in AppComponent:
@Component({
  selector: 'app-root',
  template: '<router-outlet></router-outlet>'
})
export class AppComponent {
  constructor(   
    dynamicComponents: DynamicComponentsService,
  ) {
    dynamicComponents.register();
  }

}
  1. And now in any place of your code you can use it like this:
dynamicComponents.create('user-icon', {user:{...}});

or like this:

const html = `<div class="wrapper"><user-icon class="user-icon" user='${JSON.stringify(rec.user)}'></user-icon></div>`;

this.content = this.domSanitizer.bypassSecurityTrustHtml(html);

(in template):

<div class="comment-item d-flex" [innerHTML]="content"></div>

Note that in second case you must pass objects with JSON.stringify and after that parse it again. I can't find better solution.

2
  • Interresting approach, but you will need to target es2015 (so no support for IE11) in your tsconfig.json, othewise it will failed at document.createElement(tagName);
    – Snook
    Commented May 6, 2019 at 8:19
  • Hi, as you mentioned a way to handle inputs, so can outputs of child components can be handled like this as well?
    – Mustahsan
    Commented Oct 22, 2019 at 6:42
6

In 2021 there still NO WAY in Angular to create component using dynamic HTML (loading html template dynamically), just to save your time.

Even there are a lot of voted up solutions and accepted solution, but all of them will not work for recent versions in production/AOT at least for now.

Basically because Angular does not allow you to define component with : template: {variable}

As stated by Angular team they are not going to support this approach!! please find this for reference https://github.com/angular/angular/issues/15275

5

Following up on Radmin's excellent answer, there is a little tweak needed for everyone who is using angular-cli version 1.0.0-beta.22 and above.

COMPILER_PROVIDERScan no longer be imported (for details see angular-cli GitHub).

So the workaround there is to not use COMPILER_PROVIDERS and JitCompiler in the providers section at all, but use JitCompilerFactory from '@angular/compiler' instead like this inside the type builder class:

private compiler: Compiler = new JitCompilerFactory([{useDebug: false, useJit: true}]).createCompiler();

As you can see, it is not injectable and thus has no dependencies with the DI. This solution should also work for projects not using angular-cli.

3
5

Solved this in Angular 2 Final version simply by using the dynamicComponent directive from ng-dynamic.

Usage:

<div *dynamicComponent="template; context: {text: text};"></div>

Where template is your dynamic template and context can be set to any dynamic datamodel that you want your template to bind to.

2
  • At the time of writing Angular 5 with AOT does not support this since the JIT compiler is not included in the bundle. Without AOT it works like a charm :) Commented Apr 26, 2018 at 17:24
  • does this still apply to angular 7+ ?
    – CREM
    Commented Nov 5, 2019 at 17:24
4

I want to add a few details on top of this very excellent post by Radim.

I took this solution and worked on it for a bit and quickly ran into some limitations. I'll just outline those and then give the solution to that as well.

  • First of all I was unable to render dynamic-detail inside a dynamic-detail (basically nest dynamic UIs inside each other).
  • The next issue was that I wanted to render a dynamic-detail inside one of the parts that was made available in the solution. That was not possible with the initial solution either.
  • Lastly it was not possible to use template URLs on the dynamic parts like string-editor.

I made another question based on this post, on how to achieve these limitations, which can be found here:

recursive dynamic template compilation in angular2

I’ll just outline the answers to these limitations, should you run into the same issue as I, as that make the solution quite more flexible. It would be awesome to have the initial plunker updated with that as well.

To enable nesting dynamic-detail inside each other, You'll need to add DynamicModule.forRoot() in the import statement in the type.builder.ts

protected createComponentModule (componentType: any) {
    @NgModule({
    imports: [
        PartsModule, 
        DynamicModule.forRoot() //this line here
    ],
    declarations: [
        componentType
    ],
    })
    class RuntimeComponentModule
    {
    }
    // a module for just this Type
    return RuntimeComponentModule;
}

Besides that it was not possible to use <dynamic-detail> inside one of the parts being string-editor or text-editor.

To enable that you'll need to change parts.module.ts and dynamic.module.ts

Inside parts.module.ts You'll need to add DynamicDetail in the DYNAMIC_DIRECTIVES

export const DYNAMIC_DIRECTIVES = [
   forwardRef(() => StringEditor),
   forwardRef(() => TextEditor),
   DynamicDetail
];

Also in the dynamic.module.ts you'd have to remove the dynamicDetail as they are now part of the parts

@NgModule({
   imports:      [ PartsModule ],
   exports:      [ PartsModule],
})

A working modified plunker can be found here: http://plnkr.co/edit/UYnQHF?p=preview (I didn’t solve this issue, I’m just the messenger :-D)

Finally it was not possible to use templateurls in the parts created on the dynamic components. A solution (or workaround. I’m not sure whether it’s an angular bug or wrong use of the framework) was to create a compiler in the constructor instead of injecting it.

    private _compiler;

    constructor(protected compiler: RuntimeCompiler) {
        const compilerFactory : CompilerFactory =
        platformBrowserDynamic().injector.get(CompilerFactory);
        this._compiler = compilerFactory.createCompiler([]);
    }

Then use the _compiler to compile, then templateUrls are enabled as well.

return new Promise((resolve) => {
        this._compiler
            .compileModuleAndAllComponentsAsync(module)
            .then((moduleWithFactories) =>
            {
                let _ = window["_"];
                factory = _.find(moduleWithFactories.componentFactories, { componentType: type });

                this._cacheOfFactories[template] = factory;

                resolve(factory);
            });
    });

Hope this helps someone else!

Best regards Morten

3

This is the example of dynamic Form controls generated from server.

https://stackblitz.com/edit/angular-t3mmg6

This example is dynamic Form controls is in add component (This is where you can get the Formcontrols from the server). If you see addcomponent method you can see the Forms Controls. In this example I am not using angular material,but It works (I am using @ work). This is target to angular 6, but works in all previous version.

Need to add JITComplierFactory for AngularVersion 5 and above.

Thanks

Vijay

2

I myself am trying to see how can I update RC4 to RC5 and thus I stumbled upon this entry and new approach to dynamic component creation still holds a bit of mystery to me, so I wont suggest anything on component factory resolver.

But, what I can suggest is a bit clearer approach to component creation on this scenario - just use switch in template that would create string editor or text editor according to some condition, like this:

<form [ngSwitch]="useTextarea">
    <string-editor *ngSwitchCase="false" propertyName="'code'" 
                 [entity]="entity"></string-editor>
    <text-editor *ngSwitchCase="true" propertyName="'code'" 
                 [entity]="entity"></text-editor>
</form>

And by the way, "[" in [prop] expression have a meaning, this indicates one way data binding, hence you can and even should omit those in case if you know that you do not need to bind property to variable.

2
  • 1
    That would be a way to go.. if the switch/case contains few decisions. But imagine that the generated template could be really large... and differ for each entity, differ by security, differ by entity status, by each property type (number, date, reference... editors) ... In such case, solving this in html template with ngSwitch would create a large, very very large html file. Commented Aug 11, 2016 at 7:55
  • Oh I agree with you. I have this kind of scenario right here, right now as I'm trying to load a major components of application without knowing prior to compilation particular class to be displayed. Although this particular case do not need dynamic component creation.
    – zii
    Commented Aug 11, 2016 at 7:58
1

If you want to parse dynamic content like strings as templates and load Angular components into it, the Angular Dynamic Hooks library can help you out.

It supports basically any Angular version (including the newest ones, like v18+), supports AoT-compilation, supports SSR and is designed to be easy to use, as simple as:

// The content to parse
content = 'Load a component here: <app-example></app-example>';

// A list of components to look for
parsers = [ExampleComponent];
<ngx-dynamic-hooks [content]="content" [parsers]="parsers"></ngx-dynamic-hooks>

I created this some years back as I faced the same limitations with Angular and have been improving and maintaining it since.

Some tidbids:

  • Accepts "dynamic templates" like strings or even already-loaded HTML trees as input
  • Can load components either into HTML elements or replace text patterns with them
  • Inputs and outputs for the components can be used just like in a normal template
  • Components can be nested without restrictions
  • You can pass live data from the parent component into the dynamically loaded components (and even use it to bind inputs/outputs)
  • You can control which components can load in each outlet and even which inputs/outputs you can give them
  • The library uses Angular's built-in DOMSanitizer to be safe to use even with potentially unsafe input.

Notably, it does not rely on a runtime-compiler like some of the other responses here. Because of that, you can't use template syntax. On the flipside, this means it works in both JiT and AoT-modes as well as both Ivy and the old template engine, as well as being much more secure to use in general.

7
  • Thanks, your component did it exactly like I wanted! Just wondering. It's possible to have <tooltip text="Hello"><tooltip> And then read the "text" attribute without binding? It's not a big issue, just wondering. Thanks!
    – noisedan
    Commented Sep 1, 2020 at 21:21
  • It depends on the kind of HookParser responsible for <tooltip>. But if you are using the standard SelectorHookParser, unfortunatelyt not.
    – Mvin
    Commented Sep 6, 2020 at 22:06
  • Its important to understand that the library works by completely replacing a hook with its associated component. The only thing the SelectorHookParser takes away from the found hook are inputs and outputs (marked by []-brakets) to give to the component. Anything else is ignored. I'm not sure what your use case is, but if you just need a way to bind "normal" attributes to component elements, you could perhaps first pass them in as inputs and then apply them as attributes from inside the component via HostBindings: stackoverflow.com/a/46394255/3099523
    – Mvin
    Commented Sep 6, 2020 at 22:22
  • Hi Mvin, I created a HookParser and worked perfectly. Congrats, your library is amazing.
    – noisedan
    Commented Sep 8, 2020 at 19:16
  • Thank you! Always nice to hear it being useful to other people.
    – Mvin
    Commented Sep 9, 2020 at 7:00
0

Building on top of the answer by Ophir Stern, here is a variant which works with AoT in Angular 4. The only issue I have is I cant inject any services into the DynamicComponent, but I can live with that.

note: I haven't tested with Angular 5.

import { Component, OnInit, Input, NgModule, NgModuleFactory, Compiler, EventEmitter, Output } from '@angular/core';
import { JitCompilerFactory } from '@angular/compiler';

export function createJitCompiler() {
  return new JitCompilerFactory([{
    useDebug: false,
    useJit: true
  }]).createCompiler();
}

type Bindings = {
  [key: string]: any;
};

@Component({
  selector: 'app-compile',
  template: `
    <div *ngIf="dynamicComponent && dynamicModule">
      <ng-container *ngComponentOutlet="dynamicComponent; ngModuleFactory: dynamicModule;">
      </ng-container>
    </div>
  `,
  styleUrls: ['./compile.component.scss'],
  providers: [{provide: Compiler, useFactory: createJitCompiler}]
})
export class CompileComponent implements OnInit {

  public dynamicComponent: any;
  public dynamicModule: NgModuleFactory<any>;

  @Input()
  public bindings: Bindings = {};
  @Input()
  public template: string = '';

  constructor(private compiler: Compiler) { }

  public ngOnInit() {

    try {
      this.loadDynamicContent();
    } catch (err) {
      console.log('Error during template parsing: ', err);
    }

  }

  private loadDynamicContent(): void {

    this.dynamicComponent = this.createNewComponent(this.template, this.bindings);
    this.dynamicModule = this.compiler.compileModuleSync(this.createComponentModule(this.dynamicComponent));

  }

  private createComponentModule(componentType: any): any {

    const runtimeComponentModule = NgModule({
      imports: [],
      declarations: [
        componentType
      ],
      entryComponents: [componentType]
    })(class RuntimeComponentModule { });

    return runtimeComponentModule;

  }

  private createNewComponent(template: string, bindings: Bindings): any {

    const dynamicComponent = Component({
      selector: 'app-dynamic-component',
      template: template
    })(class DynamicComponent implements OnInit {

      public bindings: Bindings;

      constructor() { }

      public ngOnInit() {
        this.bindings = bindings;
      }

    });

    return dynamicComponent;

  }

}

Hope this helps.

Cheers!

0

For this particular case looks like using a directive to dynamically create the component would be a better option. Example:

In the HTML where you want to create the component

<ng-container dynamicComponentDirective [someConfig]="someConfig"></ng-container>

I would approach and design the directive in the following way.

const components: {[type: string]: Type<YourConfig>} = {
    text : TextEditorComponent,
    numeric: NumericComponent,
    string: StringEditorComponent,
    date: DateComponent,
    ........
    .........
};

@Directive({
    selector: '[dynamicComponentDirective]'
})
export class DynamicComponentDirective implements YourConfig, OnChanges, OnInit {
    @Input() yourConfig: Define your config here //;
    component: ComponentRef<YourConfig>;

    constructor(
        private resolver: ComponentFactoryResolver,
        private container: ViewContainerRef
    ) {}

    ngOnChanges() {
        if (this.component) {
            this.component.instance.config = this.config;
            // config is your config, what evermeta data you want to pass to the component created.
        }
    }

    ngOnInit() {
        if (!components[this.config.type]) {
            const supportedTypes = Object.keys(components).join(', ');
            console.error(`Trying to use an unsupported type ${this.config.type} Supported types: ${supportedTypes}`);
        }

        const component = this.resolver.resolveComponentFactory<yourConfig>(components[this.config.type]);
        this.component = this.container.createComponent(component);
        this.component.instance.config = this.config;
    }
}

So in your components text, string, date, whatever - whatever the config you have been passing in the HTML in the ng-container element would be available.

The config, yourConfig, can be the same and define your metadata.

Depending on your config or input type the directive should act accordingly and from the supported types, it would render the appropriate component. If not it will log an error.

1
  • great answer. Have you got this to work? And also will any event binding be in tact once this component arrives in the DOM? Commented Sep 10, 2020 at 1:22

Not the answer you're looking for? Browse other questions tagged or ask your own question.