typescript - Motsvarigheten till $compile i Angular 2


Jag vill manuellt kompilera några HTML-direktiv som innehåller. Vad motsvarar $compile i Angular 2?

Till exempel, i Angular 1, kunde jag dynamiskt kompilera ett fragment av HTML och lägga till det i DOM:

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

Svar #1

Angular 2.3.0 (2016-12-07)

För att få alla detaljer, kolla:

  • Hur kan jag använda/skapa en dynamisk mall för att kompilera en dynamisk komponent med Angular 2.0?

För att se det i praktiken:

  • observera en fungerande plunker (fungerar med 2.3.0+)

Principalerna:

1) Skapa mall
2) Skapa komponent
3) Skapa modul
4) Kompilera modul
5) Skapa (och cache) ComponentFactory
6) Använd Target för att skapa en instans av den

En snabb översikt över hur man skapar en komponent

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

Ett sätt att injicera en komponent i 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;
}

Ett kodexempel på hur man skapar en ComponentFactory (och cachar den)

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

Ett kodexempel på hur man använder ovanstående resultat

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

Den fullständiga beskrivningen med alla detaljer läs här, eller observera fungerande exempel

.

.

UTDATERRAD - Angular 2.0 RC5-relaterad (endast RC5)

för att se tidigare lösningar för tidigare RC-versioner, vänligen, sök igenom historiken av detta inlägg

Svar #2

Obs: Som @BennyBottema nämner i en kommentar, är DynamicComponentLoader nu föråldrad, och det gäller därför också detta svar.

Angular2 har ingen motsvarighet till $compile. Du kan använda DynamicComoponentLoader och hacka med ES6-klasser för att kompilera din kod dynamiskt (se denna 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'
    );
  }
}

Men det kommer bara att fungera så länge html-parsern är inuti Angular2-kärnan.

Svar #3

Angular version jag har använt - Angular 4.2.0

Angular 4 har kommit på ComponentFactoryResolver för att ladda komponenter vid körning. Detta är ungefär samma implementation av $compile i Angular 1.0 som tillgodoser dina behov

I exemplet nedan laddar jag ImageWidget-komponenten dynamiskt i en DashboardTileComponent

För att ladda en komponent behöver du en direktiv som du kan applicera på ng-template som hjälper till att placera den dynamiska komponenten

WidgetHostDirektiv

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

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


      }
    }

denna direktiv injicerar ViewContainerRef för att få åtkomst till vybehållaren för elementet som kommer att hysa den dynamiskt tillagda komponenten.

DashboardTileComponent(Platshållarkomponent för att rendera den dynamiska komponenten)

Denna komponent accepterar ett input som kommer från en föräldrakomponent eller så kan du ladda från din tjänst baserat på din implementation. Denna komponent spelar huvudrollen för att lösa komponenterna vid körning. I denna metod kan du också se en metod som heter renderComponent() som slutligen laddar komponentnamnet från en tjänst och löser det med ComponentFactoryResolver och slutligen sätter data till den dynamiska komponenten.

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>

WidgetKomponentTjänst

Detta är en tjänstfabrik för att registrera alla komponenter som du vill lösa dynamiskt

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

ImageTextWidgetComponent (komponent som vi laddar vid körning)

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

Lägg slutligen till denna ImageTextWidgetComponent i din appmodul som 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;
    }

Originalreferens från min blogg

Officiell dokumentation

Ladda ner exempel på källkod

Relaterade artiklar