I want to dynamically create 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 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 (offical angular2) document

Angular 2 Synchronous Dynamic Component Creation

And understand that I can use either

  • Kind of dynamic ngIf with ComponentFactoryResolver. If I will pass known components into hosting one 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, 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 solution with the Compiler


AOT and JitCompiler (former RuntimeCompiler)

Would you like to use this features with AOT (ahead of time compilation)? Are you getting:

Error: Error encountered resolving symbol values statically. Function calls are not supported. Consider replacing the function or lambda with a reference to an exported function (position 65:17 in the original .ts file), resolving symbol COMPILER_PROVIDERS in .../node_modules/@angular/compiler/src/compiler.d.ts,

Please, leave your comment, vote here:

Could/would/will code using COMPILER_PROVIDERS be supported by AOT?

upvote
  flag
there is still no answer :/ – Erkan Buelbuel
upvote
  flag
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. :) – Richard Houltz
upvote
  flag
The article Here is what you need to know about dynamic components in Angular has great explanation of the dynamic components. – AngularInDepth.com
upvote
  flag
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 – Augie Gardner

9 Answers 11

up vote 117 down vote accepted

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

1 upvote
  flag
I see you still use the directives keyword in @component ( type.builder.ts). Is that not depreciated since RC5? If yes, is there a solution that avoids this? I am thinking of putting @ngmodule's into the app/parts/*.ts, but have not been succesful yet... – ootwch
upvote
  flag
Thanks, I think I have solved it, will try to post a plunker here as well... – ootwch
upvote
  flag
Adapted @Radim's plunker to use dynamic module generation. It would probably be prettier to create separate modules for the two directives and import only one of them selectively... what do you think? plnkr.co/edit/Mrh3CL5qBABkUGl9DU8H?p=preview – ootwch
31 upvote
  flag
this look like such a complicated solution, the deprecated one was very simple and clear, is there other way to do this ? – tibbus
upvote
  flag
Great examples! – yurzui
upvote
  flag
@yurzui great if that could help a bit anyhow to anyone ;) – Radim Köhler
upvote
  flag
i wish i could upvote twice for the consistent updates. – nograde
upvote
  flag
@cwyers glad to see it could help someone, sir ;) – Radim Köhler
2 upvote
  flag
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. – Lucio Mollinedo
1 upvote
  flag
@LucioMollinedo I agree, that steps we have to do to make it happen are complex. No doubt... I just tried to show how ;) – Radim Köhler
1 upvote
  flag
To all of those concerned about the complexity of this solution, I would urge you to actually look at the plunker example. There is maybe one extra block of code for this method compared to pre rc.5. There is only one file that is actally important in the plunkr type.builder.ts. Everything else is only useful for the specific example he was using to write this. It is a lot simpler than he wrote it out to be. – ribsies
4 upvote
  flag
@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 ;) – Radim Köhler
upvote
  flag
@RadimKöhler: it doesn't work for me (anymore?) with TypeScript 2 und Angular 2. The createNewComponent doesn't compile with this error: Return type of public method from exported class has or is using private name 'CustomDynamicComponent'. Any ideas? – MiB
upvote
  flag
@MiB could you reproduce that in plunker? I am running angular 2.0.1 and TS 2.0.3 (Visual Studio).. and it is working. - I just upgraded to 2.0.2 - and can confirm that this is really working for me.. and on pretty large application with complex dynamic stuff – Radim Köhler
upvote
  flag
@RadimKöhler: no, it does work in the plunker :( Too bad, I have no idea, really. I created an Ionic 2 project and copy&pasted the plunker there. In the meantime I found that someone else has the same problem but no working answer: //allinonescript.com/questions/35314114/… – MiB
upvote
  flag
@MiB without more detail, code, plunker it is really hard to help here. I would suggest.. issue new question and refer all the findings you have until now. Hope you'll find your solution, sir! – Radim Köhler
upvote
  flag
@RadimKöhler: thank you, and YES, I finally found the solution: in tsconfig.json I had "declaration": true. Setting that to false or deleting it fixes the error! – MiB
upvote
  flag
@MiB I dived into your issue and tried to explain it here: //allinonescript.com/a/40055623/1679310 – Radim Köhler
upvote
  flag
For those who use angular-cli version 1.0.0-beta.22 and above, a small but mean workaround is needed. For details please see my separate answer. – Sebastian
5 upvote
  flag
@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
3 upvote
  flag
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
upvote
  flag
same error as @Trusha since updating to Angular 4. I would really appreciate if someone found a solution – maxou
upvote
  flag
To me, Module are still not really dynamic since DYNAMIC_DIRECTIVES holds static reference (via import). Am I wrong ?. Real dynamic means that DYNAMIC_DIRECTIVES is set at runtime with Components/Modules that are not known at compilation time. How can we achieve this ? – Patrice
upvote
  flag
@RadimKöhler all solutions with dynamic component has a static Anchor point ( in your example #dynamicContentPlaceHolder). It's possible to make a several dynamic points? Could you change your solution for more flexibility. For instance in a dynamic table. Where the user render the cells – titusfx
upvote
  flag
could you please guide me with this //allinonescript.com/questions/46243651/… – mperle

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.

1 upvote
  flag
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. – Radim Köhler
upvote
  flag
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

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 upvote
  flag
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? – Radim Köhler
upvote
  flag
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
upvote
  flag
tx for the answer... – born2net

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.

3 upvote
  flag
I'd say, that it is an example how to write as less code as possible to do the same as in my answer //allinonescript.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 – Radim Köhler
upvote
  flag
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
5 upvote
  flag
Thank you sooooo much, Rene ! I was looking for the easiest working solution and you pretty much nailed it. I was about to give up, but you were there ! You have no idea how grateful I am. You're awesome ! – iuliust
upvote
  flag
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
upvote
  flag
Thank you for this simplified one page version. It's massively helped me to understand the more thorough explanation above. – tctc91
upvote
  flag
How do you remove the component and module after adding it? I have a case where I need to 'refresh' the module, but its giving me multiple declaration errors. – thouliha
upvote
  flag
Doesn't seem to be working - added "{{1+1}}" to template string and it crashes. – monnef
upvote
  flag
@monnef, I've added {{1 + 1}} to my plunk above and it works fine. – Rene Hamburger
upvote
  flag
@ReneHamburger Well, it shows 2 but also crashes on Uncaught (in promise): Error: Error in ./TemplateComponent class TemplateComponent - inline template:0:3 caused by: Expression has changed after it was checked. Previous value: 'CD_INIT_VALUE'. Current value: 'Example template: 2'. It seems like the view has been created after its parent and its children have been dirty checked. Has it been created in a change detection hook ? – monnef
1 upvote
  flag
@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.) – Rene Hamburger
upvote
  flag
I have a question regarding this answer, posted here. – J. Adam Connor
upvote
  flag
This is fantastic! I have one question though. How would I go about defining input properties on my TemplateComponent and passing the values to it? For instance, I want my template to contain {{row[columnDef.field]}}. How do I create an @Input for row and columnDef on my TemplateComponent? I think you can assume what I am trying to do... – WebWanderer
upvote
  flag
@WebWanderer, wouldn't it be sufficient to declare those as public variables on TemplateComponent and set them in the parent code after the component has been created? Or are you using the template component somewhere else through a selector you've assigned to it? – Rene Hamburger
upvote
  flag
@ReneHamburger Actually, you might be right. That is a really neat approach that I hadn't thought of taking. I was making a table that could be dynamically configured, but creating components for the cells wound up very memory intensive, as I expected. I am trying a different approach entirely now. But thanks for the suggestion! – WebWanderer
upvote
  flag
@ReneHamburger all solutions with dynamic component has a static Anchor point ( in your example #container). It's possible to make a dynamic point? Could you change your solution for more flexibility. For instance in a dynamic table. Where the user render the cells – titusfx
upvote
  flag
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
upvote
  flag
@ReneHamburger what about button operations like (click)="actions()" how could I make it and make use of Input and Output here. would be helpful to see these functionality in your plunker. – k11k2
upvote
  flag
@k11k2: see my update above. – Rene Hamburger

Angular 2.0 final solution

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 {}`

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

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.

1 upvote
  flag
Thanks for this suggestion, however, I'm running into "No NgModule metadata found for 'DynamicHtmlModule'". My implementation is based on //allinonescript.com/questions/40060498/… – Cybey
1 upvote
  flag
Anyone have working JitCompiletFactory with AOT sample? I have same error as @Cybey – user2771738
upvote
  flag

I must have arrived to the party late, non of the solutions here seemed helpfull to me - too messy and felt like too much of a work around.

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 as simple example which just receives text and places it in template, but obviously you can change to what you 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 injectable component to be used outside
    2. DynamicComponent - the component to be dynamically built

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

Hope this helps, good luck!

1 upvote
  flag
Thanks for the heads up on this, might upgrade and then give this a crack - doing it in Angular2 is bound to break in Angular4 anyway. – danday74
3 upvote
  flag
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. – Rahul Patel
upvote
  flag
Do you have a working plunkr or something like that? I can't seem to get this to work for me on Angular 4.0.0 – user2940226
4 upvote
  flag
Here's a quick example starting from the Angular Quickstart: embed.plnkr.co/9L72KpobVvY14uiQjo4p – Rahul Patel
upvote
  flag
this seems to work only if you use template for each component to be injected. templateUrl fails. Is there a way to use templateUrl? Templates can become unwieldy as a string – Tom Schreck
1 upvote
  flag
with ngComponentOutlet you can't pass data into the child component and from child component to parent , is that true? – Chris Tarasovs
upvote
  flag
@ChrisTarasovs. That's what I've found as well. I think there is a pull request pending to address this, but no idea when it will be implemented. – Paul Taylor
upvote
  flag
Thank you so much. This is the best and simplest solution. – b0bi
upvote
  flag
@RahulPatel , Ophir Please look at this plunkr. plnkr.co/edit/fW3jY6VyLCvuR0HBC8Gu?p=preview Is it possible to have 2 way binding between AppComponent and foo-bar component ? – user636525
upvote
  flag
Hi @RahulPatel, sorry but this post was placed a long time ago, I'll try to look into it, but I'm pretty sparse in time these days, perhaps someone else here can answer.... – Ophir Stern
upvote
  flag
Hi, this solution is the best and simple, but can anyone suggest a way not to implement DynamicComponent class inside a function because it maybe long class in my case – Maged Milad

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.

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