EDYCJA - powiązane z 2.3.0 (2016-12-07)
UWAGA: aby uzyskać rozwiązanie dla poprzedniej wersji, sprawdź historię tego postu
Podobny temat omówiono tutaj Odpowiednik kompilacji $ w Angular 2 . Musimy użyć JitCompiler
i NgModule
. Przeczytaj więcej o NgModule
Angular2 tutaj:
W skrócie
Istnieje działający plunker / przykład (szablon dynamiczny, typ komponentu dynamicznego, moduł dynamiczny JitCompiler
, ... w akcji)
Podstawowa zasada to:
1) utwórz szablon
2) znajdź ComponentFactory
w pamięci podręcznej - przejdź do 7)
3) - utwórz Component
4) - utwórz Module
5) - skompiluj Module
6) - zwróć (i pamięć podręczną do późniejszego wykorzystania) ComponentFactory
7) użyj obiektu docelowego i ComponentFactory
utwórz instancję dynamicznyComponent
Oto fragment kodu (więcej tutaj ) - nasz niestandardowy konstruktor zwraca właśnie zbudowany / buforowany, ComponentFactory
a widok Docelowy symbol zastępczy konsumuje się, aby utworzyć instancjęDynamicComponent
// here we get a TEMPLATE with dynamic content === TODO
var template = this.templateBuilder.prepareTemplate(this.entity, useTextarea);
// 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;
//...
});
To jest to - w skrócie. Aby uzyskać więcej informacji .. przeczytaj poniżej
.
TL&DR
Obserwuj plunkera i wróć, by przeczytać szczegóły, na wypadek gdyby jakiś fragment kodu wymagał więcej wyjaśnień
.
Szczegółowe objaśnienie - Angular2 RC6 ++ i komponenty środowiska wykonawczego
Poniżej opis tego scenariusza , będziemy
- utwórz moduł
PartsModule:NgModule
(uchwyt małych elementów)
- stwórz kolejny moduł
DynamicModule:NgModule
, który będzie zawierał nasz komponent dynamiczny (i referencje PartsModule
dynamicznie)
- utwórz szablon dynamiczny (proste podejście)
- utwórz nowy
Component
typ (tylko jeśli szablon się zmienił)
- stwórz nowy
RuntimeModule:NgModule
. Ten moduł będzie zawierał wcześniej utworzony Component
typ
- zadzwoń,
JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule)
aby uzyskaćComponentFactory
- utwórz wystąpienie
DynamicComponent
zadania zastępczego View Target iComponentFactory
- przypisać
@Inputs
do nowej instancji (z przełącznikiem INPUT
do TEXTAREA
edycji) , zużywają@Outputs
NgModule
Potrzebujemy NgModule
s.
Chociaż chciałbym pokazać bardzo prosty przykład, w tym przypadku potrzebowałbym trzech modułów (w rzeczywistości 4 - ale nie liczę AppModule) . Proszę, weź to zamiast prostego fragmentu jako podstawy do naprawdę solidnego generatora komponentów dynamicznych.
Nie będzie jeden moduł dla wszystkich małych elementów, na przykład string-editor
, text-editor
( date-editor
, number-editor
...)
@NgModule({
imports: [
CommonModule,
FormsModule
],
declarations: [
DYNAMIC_DIRECTIVES
],
exports: [
DYNAMIC_DIRECTIVES,
CommonModule,
FormsModule
]
})
export class PartsModule { }
Gdzie DYNAMIC_DIRECTIVES
są rozszerzalne i mają pomieścić wszystkie małe części używane w naszym dynamicznym szablonie / typie komponentu. Sprawdź app / parts / parts.module.ts
Drugi będzie modułem do naszej dynamicznej obsługi rzeczy. Będzie zawierał komponenty hostingowe i niektórych dostawców .. które będą singletonami. Dlatego opublikujemy je w standardowy sposób - zforRoot()
import { DynamicDetail } from './detail.view';
import { DynamicTypeBuilder } from './type.builder';
import { DynamicTemplateBuilder } from './template.builder';
@NgModule({
imports: [ PartsModule ],
declarations: [ DynamicDetail ],
exports: [ DynamicDetail],
})
export class DynamicModule {
static forRoot()
{
return {
ngModule: DynamicModule,
providers: [ // singletons accross the whole app
DynamicTemplateBuilder,
DynamicTypeBuilder
],
};
}
}
Sprawdź użycie forRoot()
wAppModule
Na koniec będziemy potrzebować adhoc, modułu wykonawczego .. ale zostanie on utworzony później, jako część DynamicTypeBuilder
zadania.
Czwarty moduł, moduł aplikacji, jest tym, który deklaruje dostawców kompilatora:
...
import { COMPILER_PROVIDERS } from '@angular/compiler';
import { AppComponent } from './app.component';
import { DynamicModule } from './dynamic/dynamic.module';
@NgModule({
imports: [
BrowserModule,
DynamicModule.forRoot() // singletons
],
declarations: [ AppComponent],
providers: [
COMPILER_PROVIDERS // this is an app singleton declaration
],
Przeczytaj (czytaj) dużo więcej o NgModule tam:
Szablon budowniczy
W naszym przykładzie przetworzymy szczegóły tego rodzaju bytu
entity = {
code: "ABC123",
description: "A description of this Entity"
};
Aby stworzyć template
, w tym plunkerze używamy tego prostego / naiwnego budowniczego.
Prawdziwe rozwiązanie, prawdziwy konstruktor szablonów, jest miejscem, w którym Twoja aplikacja może wiele zrobić
// plunker - app/dynamic/template.builder.ts
import {Injectable} from "@angular/core";
@Injectable()
export class DynamicTemplateBuilder {
public prepareTemplate(entity: any, useTextarea: boolean){
let properties = Object.keys(entity);
let template = "<form >";
let editorName = useTextarea
? "text-editor"
: "string-editor";
properties.forEach((propertyName) =>{
template += `
<${editorName}
[propertyName]="'${propertyName}'"
[entity]="entity"
></${editorName}>`;
});
return template + "</form>";
}
}
Sztuką jest tutaj - buduje szablon, który wykorzystuje pewien zestaw znanych właściwości, np entity
. Taka właściwość (-y) musi być częścią komponentu dynamicznego, który utworzymy następnie.
Aby było trochę łatwiej, możemy użyć interfejsu do zdefiniowania właściwości, których może użyć nasz konstruktor szablonów. Zostanie to zaimplementowane przez nasz dynamiczny typ komponentu.
export interface IHaveDynamicData {
public entity: any;
...
}
ComponentFactory
budowniczy
Bardzo ważne jest, aby pamiętać:
nasz typ komponentu, zbudowany z naszym DynamicTypeBuilder
, może się różnić - ale tylko jego szablonem (utworzonym powyżej) . Właściwości komponentów (wejścia, wyjścia lub niektóre chronione) są nadal takie same. Jeśli potrzebujemy różnych właściwości, powinniśmy zdefiniować inną kombinację szablonu i konstruktora typów
Dotykamy więc rdzenia naszego rozwiązania. Konstruktor: 1) utworzy ComponentType
2) utworzy NgModule
3) skompiluje ComponentFactory
4) buforuje go w celu późniejszego ponownego użycia.
Zależność, którą musimy otrzymać:
// plunker - app/dynamic/type.builder.ts
import { JitCompiler } from '@angular/compiler';
@Injectable()
export class DynamicTypeBuilder {
// wee need Dynamic component builder
constructor(
protected compiler: JitCompiler
) {}
A oto fragment, w jaki sposób uzyskać ComponentFactory
:
// plunker - app/dynamic/type.builder.ts
// this object is singleton - so we can use this as a cache
private _cacheOfFactories:
{[templateKey: string]: ComponentFactory<IHaveDynamicData>} = {};
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);
});
});
}
Powyżej tworzymy i buforujemy zarówno Component
i Module
. Ponieważ jeśli szablon (w rzeczywistości prawdziwa dynamiczna część tego wszystkiego) jest taki sam ... możemy użyć ponownie
A oto dwie metody, które reprezentują naprawdę fajny sposób tworzenia udekorowanych klas / typów w środowisku wykonawczym. Nie tylko, @Component
ale także@NgModule
protected 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;
}
protected createComponentModule (componentType: any) {
@NgModule({
imports: [
PartsModule, // there are 'text-editor', 'string-editor'...
],
declarations: [
componentType
],
})
class RuntimeComponentModule
{
}
// a module for just this Type
return RuntimeComponentModule;
}
Ważny:
nasze typy dynamiczne komponentów różnią się, ale tylko według szablonu. Wykorzystujemy ten fakt do buforowania ich. To jest naprawdę bardzo ważne. Angular2 również buforuje je .. według typu . A jeśli odtworzymy dla tych samych ciągów szablonów nowe typy ... zaczniemy generować wycieki pamięci.
ComponentFactory
używany przez komponent hostingowy
Ostatnim elementem jest komponent, który hostuje cel dla naszego komponentu dynamicznego, np <div #dynamicContentPlaceHolder></div>
. Otrzymujemy odniesienie do niego i używamy go ComponentFactory
do utworzenia komponentu. To w skrócie, a oto wszystkie elementy tego komponentu (w razie potrzeby otwórz tutaj plunker )
Podsumujmy najpierw instrukcje importu:
import {Component, ComponentRef,ViewChild,ViewContainerRef} from '@angular/core';
import {AfterViewInit,OnInit,OnDestroy,OnChanges,SimpleChange} from '@angular/core';
import { IHaveDynamicData, DynamicTypeBuilder } from './type.builder';
import { DynamicTemplateBuilder } from './template.builder';
@Component({
selector: 'dynamic-detail',
template: `
<div>
check/uncheck to use INPUT vs TEXTAREA:
<input type="checkbox" #val (click)="refreshContent(val.checked)" /><hr />
<div #dynamicContentPlaceHolder></div> <hr />
entity: <pre>{{entity | json}}</pre>
</div>
`,
})
export class DynamicDetail implements AfterViewInit, OnChanges, OnDestroy, OnInit
{
// wee need Dynamic component builder
constructor(
protected typeBuilder: DynamicTypeBuilder,
protected templateBuilder: DynamicTemplateBuilder
) {}
...
Właśnie otrzymujemy konstruktorów szablonów i komponentów. Dalej są właściwości, które są potrzebne w naszym przykładzie (więcej w komentarzach)
// reference for a <div> with #dynamicContentPlaceHolder
@ViewChild('dynamicContentPlaceHolder', {read: ViewContainerRef})
protected dynamicComponentTarget: ViewContainerRef;
// this will be reference to dynamic content - to be able to destroy it
protected componentRef: ComponentRef<IHaveDynamicData>;
// until ngAfterViewInit, we cannot start (firstly) to process dynamic stuff
protected wasViewInitialized = false;
// example entity ... to be recieved from other app parts
// this is kind of candiate for @Input
protected entity = {
code: "ABC123",
description: "A description of this Entity"
};
W tym prostym scenariuszu nasz komponent hostingowy nie ma żadnego @Input
. Nie musi więc reagować na zmiany. Ale pomimo tego (i aby być gotowym na nadchodzące zmiany) - musimy wprowadzić flagę, jeśli składnik został już (po raz pierwszy) zainicjowany. I dopiero wtedy możemy rozpocząć magię.
Wreszcie użyjemy naszego konstruktora komponentów, który jest po prostu skompilowany / buforowany ComponentFacotry
. Nasz obiekt zastępczy Target zostanie poproszony o utworzenie instancji wComponent
tej fabryce.
protected refreshContent(useTextarea: boolean = false){
if (this.componentRef) {
this.componentRef.destroy();
}
// here we get a TEMPLATE with dynamic content === TODO
var template = this.templateBuilder.prepareTemplate(this.entity, useTextarea);
// 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;
//...
});
}
małe rozszerzenie
Musimy również zachować odniesienie do skompilowanego szablonu .., aby móc destroy()
go poprawnie , za każdym razem, gdy go zmienimy.
// this is the best moment where to start to process dynamic stuff
public ngAfterViewInit(): void
{
this.wasViewInitialized = true;
this.refreshContent();
}
// wasViewInitialized is an IMPORTANT switch
// when this component would have its own changing @Input()
// - then we have to wait till view is intialized - first OnChange is too soon
public ngOnChanges(changes: {[key: string]: SimpleChange}): void
{
if (this.wasViewInitialized) {
return;
}
this.refreshContent();
}
public ngOnDestroy(){
if (this.componentRef) {
this.componentRef.destroy();
this.componentRef = null;
}
}
Gotowe
To właściwie wszystko. Nie zapomnij zniszczyć czegokolwiek, co zostało zbudowane dynamicznie (ngOnDestroy) . Pamiętaj też o buforowaniu dynamicznym types
i modules
jeśli jedyną różnicą jest ich szablon.
Sprawdź to wszystko tutaj
aby zobaczyć poprzednie wersje tego postu (np. związane z RC5) , sprawdź historię