I want to manually compile some HTML containing directives. What is the equivalent of $compile in Angular 2?

For example, in Angular 1, I could dynamically compile a fragment of HTML and append it to the DOM:

var e = angular.element('<div directive></div>');
element.append(e);
$compile(e)($scope);
4 upvote
  flag
Most these answers (except 1 now deprecated answer) are NOT the equivalent of angular 1 $compile. $compile takes an HTML string and compiles components and expressions contained there. These answers simply creates pre-defined components (that are not yet instantiated) dynamically and CANNOT take a string argument. This is NOT the same thing. Does anyone know of the real answer to this question? – danday74
1 upvote
  flag
upvote
  flag
Angular 4 came up with ComponentFactoryResolver which equivalent to $ compile in Angular 1.0 .See my answer //allinonescript.com/questions/34784778/… – JEMI

8 Answers 11

If you want to inject html code use directive

<div [innerHtml]="htmlVar"></div>

If you want to load whole component in some place, use DynamicComponentLoader:

https://angular.io/docs/ts/latest/api/core/DynamicComponentLoader-class.html

2 upvote
  flag
I want to inject a fragment of HTML as a string and pass it to a component compiler, and then append that component in my DOM. Can you give an example of how either of your solutions can work? – pixelbits
1 upvote
  flag
using innerHtml doesn't compile any components inside htmlVar – Juanín

Note: As @BennyBottema mentions in a comment, DynamicComponentLoader is now deprecated, hence so is this answer.


Angular2 doesn't have any $compile equivalent. You can use DynamicComoponentLoader and hack with ES6 classes to compile your code dynamically (see this plunk):

import {Component, DynamicComponentLoader, ElementRef, OnInit} from 'angular2/core'

function compileToComponent(template, directives) {
  @Component({ 
    selector: 'fake', 
    template , directives
  })
  class FakeComponent {};
  return FakeComponent;
}

@Component({
  selector: 'hello',
  template: '<h1>Hello, Angular!</h1>'
})
class Hello {}

@Component({
  selector: 'my-app',
  template: '<div #container></div>',
})
export class App implements OnInit {
  constructor(
    private loader: DynamicComponentLoader, 
    private elementRef: ElementRef,
  ) {}

  ngOnInit() {} {
    const someDynamicHtml = `<hello></hello><h2>${Date.now()}</h2>`;

    this.loader.loadIntoLocation(
      compileToComponent(someDynamicHtml, [Hello])
      this.elementRef,
      'container'
    );
  }
}

But it will work only until html parser is inside angular2 core.

upvote
  flag
Perfect! exactly what I was looking for. – pixelbits
upvote
  flag
Awesome trick! but in case my dynamic component has some inputs is it possible to bind dynamic data as well ? – Eugene Gluhotorenko
2 upvote
  flag
answering to my own question: it is possible through passing data to the compiling function. here the plunk plnkr.co/edit/dK6l7jiWt535jOw1Htct?p=preview – Eugene Gluhotorenko
upvote
  flag
This solution is only working with beta-0. From beta 1 to 15 the example code returns an error. Error: There is no component directive at element [object Object] – Nicolas Forney
upvote
  flag
Since beta-1 the loadIntoLocation function must be called during the OnInit cycle. @alexpods, if you can update your answer it will be great. – Nicolas Forney
upvote
  flag
As of the latest Angular beta, loadIntoLocation() no longer exists. You now want loadNextToLocation() - the signature is different, but actually documented in the DynamicComponentLoader documentation on the Angular 2 site. – James
12 upvote
  flag
Since rc1 DynamicComponentLoader has become deprecated – Benny Bottema
1 upvote
  flag
@BennyBottema since DynamicComponentLoader is deprecated, how do we do the same sort of thing in Angular 2? Say I have a modal dialog and I want to dynamically load a new component as it's contents – Luke T O'Brien
1 upvote
  flag
Could someone please update the answer. – Ilker Cat
up vote 104 down vote accepted

Angular 2.3.0 (2016-12-07)

To get all the details check:

To see that in action:

The principals:

1) Create Template
2) Create Component
3) Create Module
4) Compile Module
6) Create (and cache) ComponentFactory 5) use Target to create an Instance of it

A quick overview how to create a Component

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;
}

A way how to inject component into NgModule

createComponentModule (componentType: any) {
  @NgModule({
    imports: [
      PartsModule, // there are 'text-editor', 'string-editor'...
    ],
    declarations: [
      componentType
    ],
  })
  class RuntimeComponentModule
  {
  }
  // a module for just this Type
  return RuntimeComponentModule;
}

A code snippet how to create a ComponentFactory (and cache it)

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);
            });
    });
}

A code snippet how to use the above result

  // 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;
        //...
    });

The full description with all the details read here, or observe working example

.

.

OBSOLETE - Angular 2.0 RC5 related (RC5 only)

to see previous solutions for previous RC versions, please, search through the history of this post

2 upvote
  flag
Thanks a lot, I was looking for a working example of ComponentFactory and ViewContainerRef to replace the now deprecated DynamicComponentLoader. – Andre Loker
upvote
  flag
This solution should supersede the first one, it's clearly a better one. The ng.ComponentFactory gave a TypeScript error, all I had to do was to import the ComponentFactory object and update the offending line: js import {ComponentFactory} from '@angular/core'; ... .then((factory: ComponentFactory<IHaveDynamicData>) => – metaprogrammer
upvote
  flag
Interesting. I have tried implementing this for creating on-the-fly modals, creating a 'confirm you wish you delete this item' kinda of thing. While I can create the modal in the DOM just fine, I'm finding that making that modal appear is more difficult. – freethebees
upvote
  flag
need to update plnkr's zone <script> for rc7 per (//allinonescript.com/questions/39474702/…) – nograde
upvote
  flag
@cwyers - thanks so much sir, thanks. The referenced plunker is updated and using suggested <script src="https://unpkg.com/zone.js@0.6.21/dist/zone.js"></script‌​> – Radim Köhler
upvote
  flag
@lqbweb Well, we are using this approach go generate pretty complex and dynamic UI (based on lot of custom settings, security..) all is ok. Have you tested the plunker? It should show that the concept is vital... or? – Radim Köhler
upvote
  flag
any possibilities to compile the jquery element with dynamic data specific to grid cell in angular 2.? – Karthick
upvote
  flag
After copy-pasting, my app breaks because it can not find the underscore _ at line 43 of file type.builder.ts. What is that variable supposed to be? – maxou
1 upvote
  flag
@maxou That is lo-dash reference in the index.html just add that reference and all will work – Radim Köhler
upvote
  flag
(And not a big deal, but in Plunker your DynamicDetail does not use OnInit - no ngOnInit method) – maxou
upvote
  flag
@maxou ... correct ;) that statement implements OnInit without implementing it won't work locally ;) because TS compiler would blame ;) .. that is a typo (imperfection) in the plunker ;) – Radim Köhler
upvote
  flag
thanks a lot @RadimKöhler, I needed your solution quite badly, it's perfect! While you're here, how do you suppress the error in the compiler Cannot find name '_'.? It does not break the app but is annoying – maxou
upvote
  flag
@maxou ... sorry not sure about that error. the underscore symbol is simply lodash library.. so once you will add <script src="https://cdn.jsdelivr.net/lodash/4.11.1/lodash.min.js"><‌​/script> all should work – Radim Köhler
upvote
  flag
I added it, it does work, the compiler just complains about not finding the name since I guess it does not know about the content of lodash.min.js so it does not know about any variable named _. That error is also displayed in the navigator – maxou
1 upvote
  flag
37 upvote
  flag
Is this really this difficult? I used to be able to just do something like this: $compile($element.contents())($scope.$new()); and now it's hundreds of lines of code, complete with NgModule creation... This is the kind of thing that makes me want to steer clear of NG2 and move on to something better. – Karvapallo
1 upvote
  flag
What is advantage of using JitCompiler if your example might work with Compiler from @angular/core? plnkr.co/edit/UxgkiT?p=preview – yurzui
upvote
  flag
Is it possible to pass some data like object or array to child component? – amansoni211
upvote
  flag
@amansoni211 Check the plunker... app/dynamic/detail.view.ts ... line 65... where we can assign any kind of property to just built component... hope it helps a bit ;) – Radim Köhler
upvote
  flag
This does not even work when you use simple expressions like {{1+1}} – danday74
upvote
  flag
This is not the equivalent of $compile, $compile can compile a string, this cannot – danday74
upvote
  flag
npmjs.com/package/p3x-angular-compile, now you can use JIT + AOT at the same time. – Patrik Laszlo
1 upvote
  flag
Oh my god, how many lines of code I should write just to compile a small element. I didn't understand well – Mr_Perfect

In order to dinamically create an instance of a component and attach it to your DOM you can use the following script and should work in Angular RC:

html template:

<div>
  <div id="container"></div>
  <button (click)="viewMeteo()">Meteo</button>
  <button (click)="viewStats()">Stats</button>
</div>

Loader component

import { Component, DynamicComponentLoader, ElementRef, Injector } from '@angular/core';
import { WidgetMeteoComponent } from './widget-meteo';
import { WidgetStatComponent } from './widget-stat';

@Component({
  moduleId: module.id,
  selector: 'widget-loader',
  templateUrl: 'widget-loader.html',
})
export class WidgetLoaderComponent  {

  constructor( elementRef: ElementRef,
               public dcl:DynamicComponentLoader,
               public injector: Injector) { }

  viewMeteo() {
    this.dcl.loadAsRoot(WidgetMeteoComponent, '#container', this.injector);
  }

  viewStats() {
    this.dcl.loadAsRoot(WidgetStatComponent, '#container', this.injector);
  }

}
1 upvote
  flag
The DynamicComponentLoader is no more :'( After that was deprecated, there was ComponentResolver. And now there is the ComponentFactoryResolver (blog.rangle.io/dynamically-creating-components-with-angular‌​-2) – 11mb

Angular TypeScript/ES6 (Angular 2+)

Works with AOT + JIT at once together.

I created how to use it here: https://github.com/patrikx3/angular-compile

npm install p3x-angular-compile

Component: Should have a context and some html data...

Html:

<div [p3x-compile]="data" [p3x-compile-context]="ctx">loading ...</div>
1 upvote
  flag
It' s not obvious what 'Angular TypeScript' title means. Is the solution useless for ES5 and ES6? It would be helpful to provide the example of programmatic use of this package, a direct counterpart to $compile(...)($scope). There's nothing on it even in repo readme. – estus
upvote
  flag
i added info, it works with es6 as well. – Patrik Laszlo

Angular Version I have Used - Angular 4.2.0

Angular 4 is came up with ComponentFactoryResolver to load components at runtime. This is a kind of same implementation of $compile in Angular 1.0 which serves your need

In this below example I am loading ImageWidget component dynamically in to a DashboardTileComponent

In order to load a component you need a directive that you can apply to ng-template which will helps to place the dynamic component

WidgetHostDirective

 import { Directive, ViewContainerRef } from '@angular/core';

    @Directive({
      selector: '[widget-host]',
    })
    export class DashboardTileWidgetHostDirective {
      constructor(public viewContainerRef: ViewContainerRef) { 


      }
    }

this directive injects ViewContainerRef to gain access to the view container of the element that will host the dynamically added component.

DashboardTileComponent(Place holder component to render the dynamic component)

This component accepts an input which is coming from a parent components or you can load from your service based on your implementation. This component is doing the major role to resolve the components at runtime. In this method you can also see a method named renderComponent() which ultimately loads the component name from a service and resolve with ComponentFactoryResolver and finally setting data to the dynamic component.

import { Component, Input, OnInit, AfterViewInit, ViewChild, ComponentFactoryResolver, OnDestroy } from '@angular/core';
import { DashboardTileWidgetHostDirective } from './DashbardWidgetHost.Directive';
import { TileModel } from './Tile.Model';
import { WidgetComponentService } from "./WidgetComponent.Service";


@Component({
    selector: 'dashboard-tile',
    templateUrl: 'app/tile/DashboardTile.Template.html'
})

export class DashboardTileComponent implements OnInit {
    @Input() tile: any;
    @ViewChild(DashboardTileWidgetHostDirective) widgetHost: DashboardTileWidgetHostDirective;
    constructor(private _componentFactoryResolver: ComponentFactoryResolver,private widgetComponentService:WidgetComponentService) {

    }

    ngOnInit() {

    }
    ngAfterViewInit() {
        this.renderComponents();
    }
    renderComponents() {
        let component=this.widgetComponentService.getComponent(this.tile.componentName);
        let componentFactory = this._componentFactoryResolver.resolveComponentFactory(component);
        let viewContainerRef = this.widgetHost.viewContainerRef;
        let componentRef = viewContainerRef.createComponent(componentFactory);
        (<TileModel>componentRef.instance).data = this.tile;

    }
}

DashboardTileComponent.html

 <div class="col-md-2 col-lg-2 col-sm-2 col-default-margin col-default">        
                        <ng-template widget-host></ng-template>

          </div>

WidgetComponentService

This is a service factory to register all the components that you want to resolve dynamically

import { Injectable }           from '@angular/core';
import { ImageTextWidgetComponent } from "../templates/ImageTextWidget.Component";
@Injectable()
export class WidgetComponentService {
  getComponent(componentName:string) {
          if(componentName==="ImageTextWidgetComponent"){
              return ImageTextWidgetComponent
          }
  }
}

ImageTextWidgetComponent(component we are loading at runtime)

import { Component, OnInit, Input } from '@angular/core';


@Component({
    selector: 'dashboard-imagetextwidget',
    templateUrl: 'app/templates/ImageTextWidget.html'
})

export class ImageTextWidgetComponent implements OnInit {
     @Input() data: any;
    constructor() { }

    ngOnInit() { }
}

Add Finally add this ImageTextWidgetComponent in to your app module as entryComponent

@NgModule({
    imports: [BrowserModule],
    providers: [WidgetComponentService],
    declarations: [
        MainApplicationComponent,
        DashboardHostComponent,
        DashboardGroupComponent,
        DashboardTileComponent,
        DashboardTileWidgetHostDirective,
        ImageTextWidgetComponent
        ],
    exports: [],
    entryComponents: [ImageTextWidgetComponent],
    bootstrap: [MainApplicationComponent]
})
export class DashboardModule {
    constructor() {

    }
}

TileModel

 export interface TileModel {
      data: any;
    }

Orginal Reference from my blog

Official Documentation

1 upvote
  flag
You forgot to mention about entryComponents. Without it your solution won't work – yurzui
upvote
  flag
ComponentFactoryResolver was in angular2. And i think it is not equivalent of $compile – yurzui
upvote
  flag
@yurzui . But it serves the need of $compile right?? – JEMI
upvote
  flag
@yurzui I was used the same kind of implementation using $compile. When we remove entry components from the module it will throw an error ImageTextWidgetComponent is not loaded. But application still works – JEMI
upvote
  flag
$compile takes an HTML string and compiles components and expressions contained there but in your implementation you're creating static component – yurzui
upvote
  flag
@yurzui I am 100% agree on that but we can wrap that component with the html and load dynamically – JEMI
upvote
  flag
I agree that your answer is good solution for many cases and i use the same thing because it's the right angular way. But again we have to use JitCompiler to be able to get the equivalent of $compile – yurzui
upvote
  flag
I think this is not an equivalent to $compile, because you bind your dynamically created component to ViewContainerRef, which you have set up in some other component's template. What I need as a result is a pure HTML element, which I can add anywhere to DOM without knowing it is an Angular component - to make it working with non-Angular libraries, Leaflet in my case. – Michal Moravcik
1 upvote
  flag
@JEMI, this is a very good answer, it has helped me a lot. – tomislav_t
upvote
  flag
Question about this line: (<TileModel>componentRef.instance).data = this.tile; HOW can you specify type <TitleModel>, dynamically? – Becario Senior
1 upvote
  flag
@BecarioSenior if you are not cast to any model class , it will be default dynamic. In this example the type of property data is any , which means you can pass any data to the dynamic component as input. It's give more readability to your code. – JEMI

this npm package made it easier for me: https://www.npmjs.com/package/ngx-dynamic-template

usage:

<ng-template dynamic-template
             [template]="'some value:{{param1}}, and some component <lazy-component></lazy-component>'"
             [context]="{param1:'value1'}"
             [extraModules]="[someDynamicModule]"></ng-template>

Angular v5.0.2 (19/11/2017)

Angular has documented this very well for its current version:
https://angular.io/guide/dynamic-component-loader

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