Chcę ręcznie skompilować niektóre dyrektywy zawierające HTML. Jaki jest odpowiednik $compile
w Angular 2?
Na przykład w Angular 1 mogłem dynamicznie skompilować fragment HTML i dołączyć go do DOM:
var e = angular.element('<div directive></div>');
element.append(e);
$compile(e)($scope);
angular
typescript
angular2-compiler
version-compatibility
bity pikseli
źródło
źródło
Odpowiedzi:
Angular 2.3.0 (07.12.2016)
Aby poznać szczegóły, sprawdź:
Aby zobaczyć to w akcji:
Dyrektorzy:
1) Utwórz szablon
2) Utwórz komponent
3) Utwórz moduł
4) Skompiluj moduł
5) Utwórz (i zapisz w pamięci podręcznej) ComponentFactory
6) użyj Target, aby utworzyć jego instancję
Krótkie omówienie sposobu tworzenia komponentu
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; }
Sposób na wprowadzenie komponentu do 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; }
Fragment kodu, jak utworzyć
ComponentFactory
(i zapisać w pamięci podręcznej)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); }); }); }
Fragment kodu, jak korzystać z powyższego wyniku
// 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; //... });
Pełny opis ze wszystkimi szczegółami przeczytaj tutaj lub zobacz przykład roboczy
.
.
źródło
ComponentFactory
iViewContainerRef
zastąpienia przestarzałego DynamicComponentLoader.$compile($element.contents())($scope.$new());
a teraz są to setki linii kodu, wraz z tworzeniem NgModule ... To jest rodzaj rzeczy, która sprawia, że chcę omijać NG2 i przejść do czegoś lepszego.JitCompiler
jeśli Twój przykład może działać zCompiler
from@angular/core
? plnkr.co/edit/UxgkiT?p=previewUwaga: jak @BennyBottema wspomina w komentarzu, DynamicComponentLoader jest teraz przestarzały, stąd też ta odpowiedź.
Angular2 nie ma żadnego odpowiednika $ compile . Możesz używać
DynamicComoponentLoader
i hakować z klasami ES6, aby dynamicznie kompilować swój kod (zobacz ten fragment ):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' ); } }
Ale będzie działać tylko do momentu, gdy parser html znajdzie się w rdzeniu angular2.
źródło
DynamicComponentLoader
jest przestarzały, jak zrobimy to samo w Angular 2? Powiedzmy, że mam modalne okno dialogowe i chcę dynamicznie załadować nowy komponent w jego zawartościWersja kątowa, której używałem - Angular 4.2.0
Angular 4 został wymyślony z ComponentFactoryResolver do ładowania komponentów w czasie wykonywania. Jest to taka sama implementacja $ compile w Angular 1.0, która spełnia twoje potrzeby
W poniższym przykładzie ładuję komponent ImageWidget dynamicznie do DashboardTileComponent
Aby załadować komponent, potrzebujesz dyrektywy, którą możesz zastosować do ng-template, która pomoże umieścić komponent dynamiczny
WidgetHostDirective
import { Directive, ViewContainerRef } from '@angular/core'; @Directive({ selector: '[widget-host]', }) export class DashboardTileWidgetHostDirective { constructor(public viewContainerRef: ViewContainerRef) { } }
ta dyrektywa wstrzykuje ViewContainerRef, aby uzyskać dostęp do kontenera widoku elementu, który będzie hostował dynamicznie dodawany składnik.
DashboardTileComponent (komponent zastępczy do renderowania komponentu dynamicznego)
Ten komponent akceptuje dane wejściowe pochodzące z komponentów nadrzędnych lub można je załadować z usługi na podstawie implementacji. Ten komponent pełni główną rolę w rozwiązywaniu komponentów w czasie wykonywania. W tej metodzie można również zobaczyć metodę o nazwie renderComponent (), która ostatecznie ładuje nazwę komponentu z usługi i rozwiązuje ją za pomocą ComponentFactoryResolver i ostatecznie ustawia dane na komponent dynamiczny.
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
Jest to fabryka usług, która rejestruje wszystkie komponenty, które chcesz rozwiązywać dynamicznie
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, który ładujemy w czasie wykonywania)
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() { } }
Dodaj Na koniec dodaj ten ImageTextWidgetComponent do modułu aplikacji jako 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; }
Oryginalne odniesienie z mojego bloga
Oficjalna dokumentacja
Pobierz przykładowy kod źródłowy
źródło
entryComponents
. Bez tego twoje rozwiązanie nie zadziałaten pakiet npm ułatwił mi to: https://www.npmjs.com/package/ngx-dynamic-template
stosowanie:
<ng-template dynamic-template [template]="'some value:{{param1}}, and some component <lazy-component></lazy-component>'" [context]="{param1:'value1'}" [extraModules]="[someDynamicModule]"></ng-template>
źródło
Aby dynamicznie stworzyć instancję komponentu i dołączyć ją do swojego DOM możesz użyć poniższego skryptu i powinien działać w Angular RC :
szablon html:
<div> <div id="container"></div> <button (click)="viewMeteo()">Meteo</button> <button (click)="viewStats()">Stats</button> </div>
Komponent ładujący
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); } }
źródło
Angular TypeScript / ES6 (Angular 2+)
Współpracuje z AOT + JIT jednocześnie.
Jak go używać stworzyłem tutaj: https://github.com/patrikx3/angular-compile
Komponent: Powinien mieć kontekst i pewne dane HTML ...
HTML:
<div [p3x-compile]="data" [p3x-compile-context]="ctx">loading ...</div>
źródło
$compile(...)($scope)
. Nie ma na nim nic, nawet w repozytorium readme.Możesz zobaczyć komponent, który pozwala skompilować proste dynamiczne komponenty Angular https://www.npmjs.com/package/@codehint-ng/html-compiler
źródło
Wiem, że ten problem jest stary, ale spędziłem tygodnie, próbując wymyślić, jak to działa z włączonym AOT. Udało mi się skompilować obiekt, ale nigdy nie byłem w stanie wykonać istniejących komponentów. W końcu zdecydowałem się zmienić taktykę, ponieważ nie szukałem kompilacji kodu, a raczej wykonania niestandardowego szablonu. Pomyślałem o dodaniu kodu HTML, który każdy może zrobić, i pętli po istniejących fabrykach. Robiąc to, mogę wyszukać element / atrybut / itp. nazwy i wykonać komponent na tym HTMLElement. Udało mi się to uruchomić i pomyślałem, że powinienem się tym podzielić, aby zaoszczędzić komuś ogromną ilość czasu, który na to zmarnowałem.
@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 }); }); } }
źródło
Jeśli chcesz wstrzyknąć dyrektywę html code use
<div [innerHtml]="htmlVar"></div>
Jeśli chcesz wczytać cały komponent w jakimś miejscu, użyj DynamicComponentLoader:
https://angular.io/docs/ts/latest/api/core/DynamicComponentLoader-class.html
źródło