Equivalente di $ compilare in Angular 2


134

Voglio compilare manualmente alcune direttive contenenti HTML. Qual è l'equivalente di$compile in Angular 2?

Ad esempio, in Angular 1, potrei compilare dinamicamente un frammento di HTML e aggiungerlo al DOM:

var e = angular.element('<div directive></div>');
element.append(e);
$compile(e)($scope);

8
La maggior parte di queste risposte (eccetto 1 risposta ora deprecata) NON è l'equivalente della compilazione angolare di 1 $. $ compile prende una stringa HTML e compila i componenti e le espressioni ivi contenute. Queste risposte creano semplicemente componenti predefiniti (che non sono ancora istanziati) in modo dinamico e NON POSSONO accettare un argomento stringa. Questa NON è la stessa cosa. Qualcuno sa della vera risposta a questa domanda?
danday74,


Angular 4 è uscito con ComponentFactoryResolver che equivale a $ compila in Angular 1.0. Vedi la mia risposta stackoverflow.com/questions/34784778/…
Code-EZ

1
@ danday74 - Sono d'accordo sul fatto che nessuna di queste risposte offre la possibilità di compilare modelli HTML arbitrari, invece selezionano semplicemente da una serie di componenti preesistenti. Ho trovato la vera risposta qui, che funziona almeno in Angular 8: stackoverflow.com/questions/61137899/… . Vedi l'unica risposta, che fornisce uno StackBlitz funzionante che compila un modello HTML generato in fase di esecuzione arbitraria.
EbenHH

Risposte:


132

Angolare 2.3.0 (2016-12-07)

Per ottenere tutti i dettagli controlla:

Per vederlo in azione:

I presidi:

1) Crea modello
2) Crea componente
3) Crea modulo
4) Compila modulo
5) Crea (e memorizza nella cache) ComponentFactory
6) usa Target per crearne un'istanza

Una rapida panoramica su come creare 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;
}

Un modo per iniettare componenti in 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;
}

Uno snippet di codice su come creare un ComponentFactory (e memorizzarlo nella cache)

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

Uno snippet di codice su come utilizzare il risultato sopra

  // 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 descrizione completa con tutti i dettagli leggi qui o osserva esempio funzionante

.

.

OBSOLETE - Relativo a Angular 2.0 RC5 (solo RC5)

per vedere le soluzioni precedenti per le versioni precedenti di RC, per favore, cerca nella cronologia di questo post


2
Grazie mille, stavo cercando un esempio funzionante di ComponentFactorye ViewContainerRefper sostituire il DynamicComponentLoader ormai deprecato.
Andre Loker,

1
@maxou Questo è il riferimento del trattino nell'indice.html basta aggiungere quel riferimento e tutto funzionerà
Radim Köhler,

62
È davvero così difficile? Ero abituato a fare qualcosa del genere: $compile($element.contents())($scope.$new());e ora sono centinaia di righe di codice, complete della creazione di NgModule ... Questo è il tipo di cosa che mi fa venire voglia di evitare NG2 e passare a qualcosa di meglio.
Karvapallo,

2
Che cosa è vantaggio di usare JitCompilerse il vostro esempio può lavorare Compilerda @angular/core? plnkr.co/edit/UxgkiT?p=preview
yurzui

4
Oh mio Dio, quante righe di codice dovrei scrivere solo per compilare un piccolo elemento. Non ho capito bene
Mr_Perfect il

35

Nota: come accenna @BennyBottema in un commento, DynamicComponentLoader è ora deprecato, quindi anche questa risposta.


Angular2 non ha equivalenti $ compilati . Puoi usare DynamicComoponentLoadere hackerare con le classi ES6 per compilare il tuo codice in modo dinamico (vedi questo 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'
    );
  }
}

Funzionerà solo fino a quando il parser html non sarà all'interno del core angular2.


Trucco fantastico! ma nel caso in cui il mio componente dinamico abbia degli input è possibile anche associare dati dinamici?
Eugene Gluhotorenko,

2
rispondendo alla mia domanda: è possibile passando i dati alla funzione di compilazione. qui plunk plnkr.co/edit/dK6l7jiWt535jOw1Htct?p=preview
Eugene Gluhotorenko

Questa soluzione funziona solo con beta-0. Dalla beta 1 alla 15 il codice di esempio restituisce un errore. Errore: non esiste una direttiva componente sull'elemento [oggetto Oggetto]
Nicolas Forney,

13
Da quando rc1 DynamicComponentLoader è diventato obsoleto
Benny Bottema,

1
@BennyBottema poiché DynamicComponentLoaderè deprecato, come possiamo fare lo stesso tipo di cose in Angular 2? Supponiamo che io abbia una finestra di dialogo modale e che voglio caricare dinamicamente un nuovo componente in quanto contenuto
Luke T O'Brien

16

Versione angolare che ho usato - Angolare 4.2.0

Angular 4 ha creato ComponentFactoryResolver per caricare i componenti in fase di esecuzione. Questa è una specie della stessa implementazione di $ compile in Angular 1.0 che soddisfa le tue necessità

In questo esempio di seguito sto caricando dinamicamente il componente ImageWidget in un DashboardTileComponent

Per caricare un componente è necessaria una direttiva che è possibile applicare a ng-template che aiuterà a posizionare il componente dinamico

WidgetHostDirective

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

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


      }
    }

questa direttiva inietta ViewContainerRef per ottenere l'accesso al contenitore della vista dell'elemento che ospiterà il componente aggiunto dinamicamente.

DashboardTileComponent (Posiziona il componente del supporto per renderizzare il componente dinamico)

Questo componente accetta un input proveniente da componenti principali oppure è possibile caricare dal servizio in base all'implementazione. Questo componente sta svolgendo il ruolo principale per risolvere i componenti in fase di esecuzione. In questo metodo puoi anche vedere un metodo chiamato renderComponent () che alla fine carica il nome del componente da un servizio e risolve con ComponentFactoryResolver e infine imposta i dati sul componente dinamico.

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

Questa è una fabbrica di servizi per registrare tutti i componenti che si desidera risolvere in modo dinamico

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 che stiamo caricando in fase di esecuzione)

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

Aggiungi Infine aggiungi questo componente ImageTextWidgetComponent al modulo dell'app come 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;
    }

Riferimento originale dal mio blog

Documentazione ufficiale

Scarica il codice sorgente di esempio


1
Hai dimenticato di menzionarlo entryComponents. Senza di essa la tua soluzione non funzionerà
yurzui il

ComponentFactoryResolver era in angolare2. E penso che non equivalga a $ compilare
yurzui il

@yurzui. Ma serve la necessità di $ compilare giusto ??
Codice EZ,

@yurzui Mi è stato usato lo stesso tipo di implementazione usando $ compile. Quando rimuoviamo i componenti di entrata dal modulo, verrà generato un errore ImageTextWidgetComponent non caricato. Ma l'applicazione funziona ancora
Code-EZ,

1
@BecarioSenior se non si esegue il cast in nessuna classe di modello, sarà la dinamica predefinita. In questo esempio il tipo di dati di proprietà è any, il che significa che è possibile passare qualsiasi dato al componente dinamico come input. Dà più leggibilità al tuo codice.
Codice EZ

9

questo pacchetto npm mi ha reso più semplice: https://www.npmjs.com/package/ngx-dynamic-template

utilizzo:

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

3

Per creare dinamicamente un'istanza di un componente e collegarlo al DOM è possibile utilizzare il seguente script e dovrebbe funzionare in Angular RC :

modello html:

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

Componente caricatore

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
DynamicComponentLoader non è più: '(Dopo che è stato deprecato, c'era ComponentResolver. E ora c'è ComponentFactoryResolver ( blog.rangle.io/dynamically-creating-components-with-angular-2 )
11mb

3

Angular TypeScript / ES6 (Angular 2+)

Funziona con AOT + JIT contemporaneamente.

Ho creato come usarlo qui: https://github.com/patrikx3/angular-compile

npm install p3x-angular-compile

Componente: dovrebbe avere un contesto e alcuni dati HTML ...

html:

<div [p3x-compile]="data" [p3x-compile-context]="ctx">loading ...</div>

1
Non è ovvio che cosa significhi "Angular TypeScript". La soluzione è inutile per ES5 ed ES6? Sarebbe utile fornire l'esempio dell'uso programmatico di questo pacchetto, una controparte diretta di $compile(...)($scope). Non c'è nulla su di esso nemmeno nel readme repo.
Estus Flask


0

So che questo problema è vecchio, ma ho trascorso settimane cercando di capire come farlo funzionare con AOT abilitato. Sono stato in grado di compilare un oggetto ma non sono mai stato in grado di eseguire componenti esistenti. Bene, ho finalmente deciso di cambiare tatto, dato che non stavo cercando di compilare il codice tanto quanto eseguire un modello personalizzato. Il mio pensiero era quello di aggiungere l'html che chiunque può fare e fare il giro delle fabbriche esistenti. In tal modo posso cercare l'elemento / attributo / ecc. nomina ed esegue il componente su quel HTMLElement. Sono stato in grado di farlo funzionare e ho pensato che avrei dovuto condividerlo per salvare qualcun altro l'enorme quantità di tempo che ho perso su di esso.

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

-5

Se si desidera iniettare il codice HTML utilizzare la direttiva

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

Se vuoi caricare l'intero componente in qualche luogo, usa DynamicComponentLoader:

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


2
Voglio iniettare un frammento di HTML come stringa e passarlo a un compilatore di componenti, quindi aggiungere quel componente nel mio DOM. Puoi fare un esempio di come una delle tue soluzioni può funzionare?
pixelbits

3
l'utilizzo di innerHtml non compila alcun componente all'interno di htmlVar
Juanín
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.