Equivalente de $ compilar en Angular 2

134

Quiero compilar manualmente algunas directivas que contienen HTML. ¿Cuál es el equivalente de $compileen Angular 2?

Por ejemplo, en Angular 1, podría compilar dinámicamente un fragmento de HTML y agregarlo al DOM:

var e = angular.element('<div directive></div>');
element.append(e);
$compile(e)($scope);
bits de píxel
fuente
8
La mayoría de estas respuestas (excepto 1 respuesta ahora obsoleta) NO son el equivalente de la compilación angular 1 $. $ compile toma una cadena HTML y compila componentes y expresiones contenidas allí. Estas respuestas simplemente crean componentes predefinidos (que aún no se han instanciado) dinámicamente y NO PUEDEN tomar un argumento de cadena. Esto NO es lo mismo. ¿Alguien sabe de la verdadera respuesta a esta pregunta?
danday74
Angular 4 presentó ComponentFactoryResolver que equivale a $ compile en Angular 1.0. Vea mi respuesta stackoverflow.com/questions/34784778/…
Code-EZ
1
@ danday74 - Estoy de acuerdo en que ninguna de estas respuestas proporciona la capacidad de compilar plantillas HTML arbitrarias, sino que solo seleccionan de un conjunto de componentes preexistentes. Encontré la respuesta real aquí, que funciona en Angular 8 al menos: stackoverflow.com/questions/61137899/… . Consulte la única respuesta, que proporciona un StackBlitz que funciona y que compila una plantilla HTML generada en tiempo de ejecución arbitraria.
EbenH

Respuestas:

132

Angular 2.3.0 (2016-12-07)

Para obtener todos los detalles, consulte:

Para ver eso en acción:

Los principales:

1) Crear plantilla
2) Crear componente
3) Crear módulo
4) Compilar módulo
5) Crear (y almacenar en caché) ComponentFactory
6) usar Target para crear una instancia del mismo

Una descripción rápida de cómo crear un componente

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

Una forma de inyectar componentes en 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;
}

Un fragmento de código sobre cómo crear un ComponentFactory (y almacenarlo en caché)

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

Un fragmento de código sobre cómo usar el resultado anterior

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

La descripción completa con todos los detalles leídos aquí , u observe un ejemplo de trabajo

.

.

OBSOLETO - Angular 2.0 RC5 relacionado (solo RC5)

para ver soluciones anteriores para versiones anteriores de RC, busque en el historial de esta publicación

Radim Köhler
fuente
2
Muchas gracias, estaba buscando un ejemplo de trabajo ComponentFactoryy ViewContainerRefreemplazar el ahora obsoleto DynamicComponentLoader.
Andre Loker
1
@maxou Esa es la referencia lo-dash en index.html solo agregue esa referencia y todo funcionará
Radim Köhler
62
¿Es esto realmente tan difícil? Solía ​​ser capaz de hacer algo como esto: $compile($element.contents())($scope.$new());y ahora son cientos de líneas de código, completas con la creación de NgModule ... Este es el tipo de cosas que me dan ganas de alejarme de NG2 y pasar a algo mejor.
Karvapallo
2
¿Cuál es la ventaja de usar JitCompilersi su ejemplo podría funcionar Compilerdesde @angular/core? plnkr.co/edit/UxgkiT?p=preview
yurzui
44
Dios mío, cuántas líneas de código debería escribir solo para compilar un elemento pequeño. No entendí bien
Mr_Perfect
35

Nota: Como @BennyBottema menciona en un comentario, DynamicComponentLoader ahora está en desuso, por lo tanto, esta respuesta también.


Angular2 no tiene ningún equivalente de $ compilación . Puede usar DynamicComoponentLoadery piratear con las clases de ES6 para compilar su código dinámicamente (consulte este 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'
    );
  }
}

Pero solo funcionará hasta que el analizador html esté dentro del núcleo angular2.

alexpods
fuente
Truco impresionante! pero en caso de que mi componente dinámico tenga algunas entradas, ¿es posible vincular datos dinámicos también?
Eugene Gluhotorenko
2
respondiendo a mi propia pregunta: es posible al pasar datos a la función de compilación. aquí el plunk plnkr.co/edit/dK6l7jiWt535jOw1Htct?p=preview
Eugene Gluhotorenko
Esta solución solo funciona con beta-0. De beta 1 a 15, el código de ejemplo devuelve un error. Error: No hay directiva de componente en el elemento [objeto Objeto]
Nicolas Forney
13
Desde rc1 DynamicComponentLoader ha quedado en desuso
Benny Bottema
1
@BennyBottema ya que DynamicComponentLoaderestá en desuso, ¿cómo hacemos el mismo tipo de cosas en Angular 2? Digamos que tengo un cuadro de diálogo modal y quiero cargar dinámicamente un nuevo componente como su contenido
Luke T O'Brien
16

Versión angular que he usado - Angular 4.2.0

A Angular 4 se le ocurrió ComponentFactoryResolver para cargar componentes en tiempo de ejecución. Este es un tipo de implementación de $ compile en Angular 1.0 que satisface sus necesidades.

En el siguiente ejemplo, estoy cargando el componente ImageWidget dinámicamente en un DashboardTileComponent

Para cargar un componente, necesita una directiva que pueda aplicar a ng-template que le ayudará a colocar el componente dinámico

WidgetHostDirective

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

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


      }
    }

Esta directiva inyecta ViewContainerRef para obtener acceso al contenedor de vista del elemento que alojará el componente agregado dinámicamente.

DashboardTileComponent (Coloque el componente de soporte para representar el componente dinámico)

Este componente acepta una entrada que proviene de componentes principales o puede cargar desde su servicio en función de su implementación. Este componente cumple la función principal de resolver los componentes en tiempo de ejecución. En este método también puede ver un método llamado renderComponent () que finalmente carga el nombre del componente de un servicio y lo resuelve con ComponentFactoryResolver y finalmente establece los datos en el componente dinámico.

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

Esta es una fábrica de servicios para registrar todos los componentes que desea resolver dinámicamente.

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

ImageTextWidgetComponent (componente que estamos cargando en tiempo de ejecución)

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() { }
}

Agregar Finalmente, agregue este ImageTextWidgetComponent a su módulo de aplicación como 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;
    }

Referencia original de mi blog

Documentación oficial

Descargar código fuente de muestra

Code-EZ
fuente
1
Olvidaste mencionar sobre entryComponents. Sin ella, su solución no funcionará
yurzui
ComponentFactoryResolver estaba en angular2. Y creo que no es equivalente a $ compile
yurzui
@yurzui. Pero sirve a la necesidad de $ compilar ¿verdad?
Code-EZ
@yurzui Se usó el mismo tipo de implementación usando $ compile. Cuando eliminamos componentes de entrada del módulo, arrojará un error. ImageTextWidgetComponent no se carga. Pero la aplicación aún funciona
Code-EZ
1
@BecarioSenior si no se envía a ninguna clase de modelo, será dinámico por defecto. En este ejemplo, el tipo de datos de propiedad es cualquiera, lo que significa que puede pasar cualquier dato al componente dinámico como entrada. Le da más legibilidad a su código.
Code-EZ
9

este paquete npm me lo hizo más fácil: https://www.npmjs.com/package/ngx-dynamic-template

uso:

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

Para crear dinámicamente una instancia de un componente y adjuntarlo a su DOM, puede usar el siguiente script y debería funcionar en Angular RC :

plantilla html:

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

Componente del cargador

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

}
fabio_biondi
fuente
1
El DynamicComponentLoader ya no existe: '(Después de que quedó en desuso, hubo ComponentResolver. Y ahora está ComponentFactoryResolver ( blog.rangle.io/dynamically-creating-components-with-angular-2 )
11mb
3

Angular TypeScript / ES6 (Angular 2+)

Funciona con AOT + JIT a la vez juntos.

Creé cómo usarlo aquí: https://github.com/patrikx3/angular-compile

npm install p3x-angular-compile

Componente: debe tener un contexto y algunos datos html ...

HTML:

<div [p3x-compile]="data" [p3x-compile-context]="ctx">loading ...</div>
Patrik Laszlo
fuente
1
No es obvio lo que significa el título 'Angular TypeScript'. ¿Es inútil la solución para ES5 y ES6? Sería útil proporcionar el ejemplo del uso programático de este paquete, una contraparte directa de $compile(...)($scope). No hay nada en él, incluso en el archivo readme.
Estus Flask
0

Sé que este problema es antiguo, pero pasé semanas tratando de descubrir cómo hacer que esto funcione con AOT habilitado. Pude compilar un objeto pero nunca pude ejecutar componentes existentes. Bueno, finalmente decidí cambiar de tacto, ya que no estaba buscando compilar código tanto como ejecutar una plantilla personalizada. Pensé en agregar el html que cualquiera puede hacer y recorrer las fábricas existentes. Al hacerlo, puedo buscar el elemento / atributo / etc. nombres y ejecutar el componente en ese elemento HTMLE. Pude hacer que funcionara y pensé que debería compartir esto para ahorrarle a alguien la inmensa cantidad de tiempo que desperdicié.

@Component({
    selector: "compile",
    template: "",
    inputs: ["html"]
})
export class CompileHtmlComponent implements OnDestroy {
    constructor(
        private content: ViewContainerRef,
        private injector: Injector,
        private ngModRef: NgModuleRef<any>
    ) { }

    ngOnDestroy() {
        this.DestroyComponents();
    }

    private _ComponentRefCollection: any[] = null;
    private _Html: string;

    get Html(): string {
        return this._Html;
    }
    @Input("html") set Html(val: string) {
        // recompile when the html value is set
        this._Html = (val || "") + "";
        this.TemplateHTMLCompile(this._Html);
    }

    private DestroyComponents() { // we need to remove the components we compiled
        if (this._ComponentRefCollection) {
            this._ComponentRefCollection.forEach((c) => {
                c.destroy();
            });
        }
        this._ComponentRefCollection = new Array();
    }

    private TemplateHTMLCompile(html) {
        this.DestroyComponents();
        this.content.element.nativeElement.innerHTML = html;
        var ref = this.content.element.nativeElement;
        var factories = (this.ngModRef.componentFactoryResolver as any)._factories;
        // here we loop though the factories, find the element based on the selector
        factories.forEach((comp: ComponentFactory<unknown>) => {
            var list = ref.querySelectorAll(comp.selector);
            list.forEach((item) => {
                var parent = item.parentNode;
                var next = item.nextSibling;
                var ngContentNodes: any[][] = new Array(); // this is for the viewchild/viewchildren of this object

                comp.ngContentSelectors.forEach((sel) => {
                    var ngContentList: any[] = new Array();

                    if (sel == "*") // all children;
                    {
                        item.childNodes.forEach((c) => {
                            ngContentList.push(c);
                        });
                    }
                    else {
                        var selList = item.querySelectorAll(sel);

                        selList.forEach((l) => {
                            ngContentList.push(l);
                        });
                    }

                    ngContentNodes.push(ngContentList);
                });
                // here is where we compile the factory based on the node we have
                let component = comp.create(this.injector, ngContentNodes, item, this.ngModRef);

                this._ComponentRefCollection.push(component); // save for our destroy call
                // we need to move the newly compiled element, as it was appended to this components html
                if (next) parent.insertBefore(component.location.nativeElement, next);
                else parent.appendChild(component.location.nativeElement);

                component.hostView.detectChanges(); // tell the component to detectchanges
            });
        });
    }
}
Vince
fuente
-5

Si desea inyectar código html, use la directiva

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

Si desea cargar el componente completo en algún lugar, use DynamicComponentLoader:

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

Vlado Tesanovic
fuente
2
Quiero inyectar un fragmento de HTML como una cadena y pasarlo a un compilador de componentes, y luego agregar ese componente en mi DOM. ¿Puede dar un ejemplo de cómo puede funcionar cualquiera de sus soluciones?
pixelbits
3
El uso de innerHtml no compila ningún componente dentro de htmlVar
Juanín