Entspricht $ compile in Angular 2

134

Ich möchte einige HTML-haltige Anweisungen manuell kompilieren. Was entspricht $compileAngular 2?

In Angular 1 könnte ich beispielsweise ein HTML-Fragment dynamisch kompilieren und an das DOM anhängen:

var e = angular.element('<div directive></div>');
element.append(e);
$compile(e)($scope);
Pixelbits
quelle
8
Die meisten dieser Antworten (mit Ausnahme von 1 jetzt veralteten Antworten) entsprechen NICHT der winkligen 1 $ -Kompilierung. $ compile verwendet eine HTML-Zeichenfolge und kompiliert dort enthaltene Komponenten und Ausdrücke. Diese Antworten erstellen einfach vordefinierte Komponenten (die noch nicht instanziiert sind) dynamisch und KÖNNEN KEIN String-Argument annehmen. Das ist NICHT dasselbe. Kennt jemand die wirkliche Antwort auf diese Frage?
danday74
Angular 4 hat ComponentFactoryResolver entwickelt, was $ compile in Angular 1.0 entspricht. Siehe meine Antwort stackoverflow.com/questions/34784778/…
Code-EZ
1
@ danday74 - Ich stimme zu, dass keine dieser Antworten die Möglichkeit bietet, beliebige HTML-Vorlagen zu kompilieren. Stattdessen wählen sie nur aus einer Reihe bereits vorhandener Komponenten aus. Ich habe hier die richtige Antwort gefunden, die zumindest in Angular 8 funktioniert: stackoverflow.com/questions/61137899/… . Siehe die eine Antwort, die einen funktionierenden StackBlitz bereitstellt, der eine beliebige zur Laufzeit generierte HTML-Vorlage kompiliert.
EbenH

Antworten:

132

Angular 2.3.0 (07.12.2016)

Um alle Details zu erhalten, überprüfen Sie:

Um das in Aktion zu sehen:

Die Auftraggeber:

1) Vorlage erstellen
2) Komponente erstellen
3) Modul erstellen
4) Modul kompilieren
5) ComponentFactory erstellen (und zwischenspeichern)
6) Mit Target eine Instanz davon erstellen

Ein kurzer Überblick über das Erstellen einer Komponente

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

Eine Möglichkeit, Komponenten in NgModule zu injizieren

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

Ein Code-Snippet zum Erstellen eines ComponentFactory (und Zwischenspeicherns)

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

Ein Code-Snippet zur Verwendung des obigen Ergebnisses

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

Die vollständige Beschreibung mit allen Details finden Sie hier oder beobachten Sie ein Arbeitsbeispiel

.

.

OBSOLETE - Angular 2.0 RC5 bezogen (nur RC5)

Um frühere Lösungen für frühere RC-Versionen zu sehen, durchsuchen Sie bitte den Verlauf dieses Beitrags

Radim Köhler
quelle
2
Vielen Dank, ich habe nach einem funktionierenden Beispiel gesucht ComponentFactoryund ViewContainerRefden jetzt veralteten DynamicComponentLoader ersetzt.
Andre Loker
1
@maxou Das ist Lo-Dash-Referenz in der index.html fügen Sie einfach diese Referenz hinzu und alles wird funktionieren
Radim Köhler
62
Ist das wirklich so schwierig? Früher war ich in der Lage, einfach so etwas zu tun: $compile($element.contents())($scope.$new());und jetzt sind es Hunderte von Codezeilen, einschließlich der Erstellung von NgModule ... Dies ist die Art von Dingen, die mich dazu bringen, mich von NG2 fernzuhalten und zu etwas Besserem überzugehen.
Karvapallo
2
Was ist der Vorteil der Verwendung, JitCompilerwenn Ihr Beispiel mit Compilerfrom funktioniert @angular/core? plnkr.co/edit/UxgkiT?p=preview
yurzui
4
Oh mein Gott, wie viele Codezeilen sollte ich schreiben, um ein kleines Element zu kompilieren. Ich habe es nicht gut verstanden
Mr_Perfect
35

Hinweis: Wie @BennyBottema in einem Kommentar erwähnt, ist DynamicComponentLoader jetzt veraltet, daher auch diese Antwort.


Angular2 hat kein $ compile- Äquivalent. Sie können DynamicComoponentLoaderES6-Klassen verwenden und mit ihnen hacken, um Ihren Code dynamisch zu kompilieren (siehe diesen 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'
    );
  }
}

Es funktioniert jedoch nur, bis sich der HTML-Parser im Winkel2-Kern befindet.

Alexpods
quelle
Super Trick! Aber falls meine dynamische Komponente einige Eingaben hat, ist es möglich, auch dynamische Daten zu binden?
Eugene Gluhotorenko
2
Beantwortung meiner eigenen Frage: Es ist möglich, Daten an die Kompilierungsfunktion zu übergeben. hier die plunk plnkr.co/edit/dK6l7jiWt535jOw1Htct?p=preview
Eugene Gluhotorenko
Diese Lösung funktioniert nur mit Beta-0. Von Beta 1 bis 15 gibt der Beispielcode einen Fehler zurück. Fehler: Es gibt keine Komponentenanweisung bei Element [Objekt Objekt]
Nicolas Forney
13
Seit rc1 ist DynamicComponentLoader veraltet
Benny Bottema
1
@BennyBottema da DynamicComponentLoaderist veraltet, wie machen wir das gleiche in Angular 2? Angenommen, ich habe einen modalen Dialog und möchte eine neue Komponente dynamisch als Inhalt laden
Luke T O'Brien,
16

Angular Version, die ich verwendet habe - Angular 4.2.0

Angular 4 wurde mit ComponentFactoryResolver entwickelt , um Komponenten zur Laufzeit zu laden. Dies ist eine Art der gleichen Implementierung von $ compile in Angular 1.0, die Ihren Anforderungen entspricht

In diesem Beispiel lade ich die ImageWidget- Komponente dynamisch in eine DashboardTileComponent

Um eine Komponente zu laden, benötigen Sie eine Direktive, die Sie auf ng-template anwenden können, um die dynamische Komponente zu platzieren

WidgetHostDirective

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

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


      }
    }

Diese Anweisung fügt ViewContainerRef ein , um Zugriff auf den Ansichtscontainer des Elements zu erhalten, in dem die dynamisch hinzugefügte Komponente gehostet wird.

DashboardTileComponent (Platzhalterkomponente zum Rendern der dynamischen Komponente platzieren)

Diese Komponente akzeptiert eine Eingabe, die von einer übergeordneten Komponente stammt, oder Sie können sie basierend auf Ihrer Implementierung von Ihrem Service laden. Diese Komponente übernimmt die Hauptrolle beim Auflösen der Komponenten zur Laufzeit. In dieser Methode sehen Sie auch eine Methode namens renderComponent (), die letztendlich den Komponentennamen von einem Dienst lädt und mit ComponentFactoryResolver auflöst und schließlich Daten für die dynamische Komponente festlegt .

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

Dies ist eine Service Factory, um alle Komponenten zu registrieren, die Sie dynamisch auflösen möchten

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

ImageTextWidgetComponent (Komponente, die wir zur Laufzeit laden)

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

Hinzufügen Fügen Sie diese ImageTextWidgetComponent schließlich als entryComponent zu Ihrem App-Modul hinzu

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

Originalreferenz aus meinem Blog

Offizielle Dokumentation

Laden Sie den Beispielquellcode herunter

Code-EZ
quelle
1
Sie haben vergessen zu erwähnen entryComponents. Ohne es wird Ihre Lösung nicht funktionieren
yurzui
ComponentFactoryResolver war in angle2. Und ich denke, es ist nicht gleichbedeutend mit $ compile
yurzui
@yurzui. Aber es dient der Notwendigkeit von $ compile, oder?
Code-EZ
@yurzui Ich habe die gleiche Art der Implementierung mit $ compile verwendet. Wenn wir Eintragskomponenten aus dem Modul entfernen, wird ein Fehler ausgegeben. ImageTextWidgetComponent wird nicht geladen. Aber die Anwendung funktioniert immer noch
Code-EZ
1
@BecarioSenior Wenn Sie nicht in eine Modellklasse umgewandelt werden, ist diese standardmäßig dynamisch. In diesem Beispiel ist der Typ der Eigenschaftsdaten beliebig. Dies bedeutet, dass Sie beliebige Daten als Eingabe an die dynamische Komponente übergeben können. Es gibt Ihrem Code mehr Lesbarkeit.
Code-EZ
9

Dieses npm-Paket hat es mir leichter gemacht: https://www.npmjs.com/package/ngx-dynamic-template

Verwendung:

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

Um eine Instanz einer Komponente zu erstellen und an Ihr DOM anzuhängen, können Sie das folgende Skript verwenden und sollten in Angular RC funktionieren :

HTML-Vorlage:

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

Ladekomponente

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
quelle
1
Der DynamicComponentLoader ist nicht mehr: '(Nachdem dies veraltet war, gab es ComponentResolver. Und jetzt gibt es den ComponentFactoryResolver ( blog.rangle.io/dynamically-creating-components-with-angular-2 )
11mb
3

Angular TypeScript / ES6 (Angular 2+)

Arbeitet sofort mit AOT + JIT zusammen.

Ich habe hier die Verwendung erstellt: https://github.com/patrikx3/angular-compile

npm install p3x-angular-compile

Komponente: Sollte einen Kontext und einige HTML-Daten haben ...

Html:

<div [p3x-compile]="data" [p3x-compile-context]="ctx">loading ...</div>
Patrik Laszlo
quelle
1
Es ist nicht offensichtlich, was der Titel "Angular TypeScript" bedeutet. Ist die Lösung für ES5 und ES6 unbrauchbar? Es wäre hilfreich, das Beispiel der programmatischen Verwendung dieses Pakets als direktes Gegenstück zu diesem Paket anzugeben $compile(...)($scope). Es gibt nichts drauf, auch nicht in Repo Readme.
Estus Flask
0

Ich weiß, dass dieses Problem alt ist, aber ich habe Wochen damit verbracht, herauszufinden, wie dies mit aktiviertem AOT funktioniert. Ich konnte ein Objekt kompilieren, aber niemals vorhandene Komponenten ausführen. Nun, ich habe mich schließlich entschlossen, den Takt zu ändern, da ich nicht nur Code kompilieren, sondern eine benutzerdefinierte Vorlage ausführen wollte. Mein Gedanke war, das HTML hinzuzufügen, das jeder tun kann, und die vorhandenen Fabriken zu durchlaufen. Dabei kann ich nach dem Element / Attribut / etc. Suchen. benennt und führt die Komponente auf diesem HTMLElement aus. Ich konnte es zum Laufen bringen und dachte mir, ich sollte dies teilen, um jemand anderem die immense Zeit zu ersparen, die ich damit verschwendete.

@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
quelle
-5

Wenn Sie HTML-Code einfügen möchten, verwenden Sie die Direktive

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

Wenn Sie die gesamte Komponente an einem bestimmten Ort laden möchten, verwenden Sie DynamicComponentLoader:

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

Vlado Tesanovic
quelle
2
Ich möchte ein HTML-Fragment als Zeichenfolge einfügen, an einen Komponenten-Compiler übergeben und diese Komponente dann in mein DOM anhängen. Können Sie ein Beispiel geben, wie eine Ihrer Lösungen funktionieren kann?
Pixelbits
3
Die Verwendung von innerHtml kompiliert keine Komponenten in htmlVar
Juanín