EDIT - terkait dengan 2.3.0 (2016-12-07)
CATATAN: untuk mendapatkan solusi untuk versi sebelumnya, periksa riwayat posting ini
Topik serupa didiskusikan di sini Setara dengan $ compile di Angular 2 . Kita perlu menggunakan JitCompiler
dan NgModule
. Baca lebih lanjut tentang NgModule
di Angular2 di sini:
Pendeknya
Ada plunker / contoh yang berfungsi (template dinamis, tipe komponen dinamis, modul dinamis,JitCompiler
,, ... beraksi)
Prinsipnya adalah:
1) buat Template
2) temukan ComponentFactory
di cache - pergi ke 7)
3) - buat Component
4) - buat Module
5) - kompilasi Module
6) - kembali (dan cache untuk digunakan nanti) ComponentFactory
7) gunakan Target danComponentFactory
untuk membuat Instance dinamisComponent
Berikut ini cuplikan kode (selengkapnya di sini ) - Pembuat kustom kami kembali hanya dibangun / di-cache ComponentFactory
dan tampilan yang dikonsumsi placeholder target untuk membuat turunan dariDynamicComponent
// 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;
//...
});
Ini dia - singkatnya. Untuk mendapatkan detail lebih lanjut .. baca di bawah ini
.
TL&DR
Amati plunker dan kembali untuk membaca detail seandainya beberapa cuplikan membutuhkan lebih banyak penjelasan
.
Penjelasan terperinci - Angular2 RC6 ++ & komponen runtime
Di bawah ini deskripsi skenario ini , kami akan
- buat modul
PartsModule:NgModule
(tempat potongan kecil)
- buat modul lain
DynamicModule:NgModule
, yang akan berisi komponen dinamis kami (dan referensiPartsModule
secara dinamis)
- buat Template dinamis (pendekatan sederhana)
- buat
Component
tipe baru (hanya jika templat telah berubah)
- membuat baru
RuntimeModule:NgModule
. Modul ini akan berisi Component
jenis yang dibuat sebelumnya
- telepon
JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule)
untuk mendapatkanComponentFactory
- membuat Mesin Virtual dari
DynamicComponent
- pekerjaan placeholder View Target danComponentFactory
- menetapkan
@Inputs
ke contoh baru (beralih dari INPUT
ke TEXTAREA
editing) , mengkonsumsi@Outputs
NgModule
Kami membutuhkan sebuah NgModule
.
Walaupun saya ingin menunjukkan contoh yang sangat sederhana, dalam hal ini, saya akan membutuhkan tiga modul (sebenarnya 4 - tetapi saya tidak menghitung AppModule) . Tolong, ambil ini daripada potongan sederhana sebagai dasar untuk generator komponen dinamis yang sangat solid.
Akan ada satu modul untuk semua komponen kecil, misalnya string-editor
, text-editor
( date-editor
, number-editor
...)
@NgModule({
imports: [
CommonModule,
FormsModule
],
declarations: [
DYNAMIC_DIRECTIVES
],
exports: [
DYNAMIC_DIRECTIVES,
CommonModule,
FormsModule
]
})
export class PartsModule { }
Di mana DYNAMIC_DIRECTIVES
extensible dan dimaksudkan untuk menampung semua bagian kecil yang digunakan untuk templat / tipe Komponen dinamis kami. Periksa app / parts / parts.module.ts
Yang kedua akan menjadi modul untuk penanganan barang Dinamis kami. Ini akan berisi komponen hosting dan beberapa penyedia .. yang akan menjadi lajang. Untuk itu kami akan mempublikasikannya dengan cara standar - denganforRoot()
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
],
};
}
}
Periksa penggunaan forRoot()
diAppModule
Akhirnya, kita membutuhkan modul adhoc, runtime .. tetapi itu akan dibuat nanti, sebagai bagian dari DynamicTypeBuilder
pekerjaan.
Modul keempat, modul aplikasi, adalah orang yang terus menyatakan penyedia kompiler:
...
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
],
Baca (baca) lebih banyak tentang NgModule di sana:
Sebuah Template builder
Dalam contoh kita, kita akan memproses detail entitas semacam ini
entity = {
code: "ABC123",
description: "A description of this Entity"
};
Untuk membuat template
, di plunker ini kami menggunakan pembangun sederhana / naif ini.
Solusi nyata, pembuat template nyata, adalah tempat di mana aplikasi Anda dapat melakukan banyak hal
// 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>";
}
}
Trik di sini adalah - itu membangun template yang menggunakan beberapa set properti yang diketahui, misalnya entity
. Properti seperti itu (-ies) harus menjadi bagian dari komponen dinamis, yang akan kita buat selanjutnya.
Untuk membuatnya lebih mudah, kita bisa menggunakan antarmuka untuk mendefinisikan properti, yang bisa digunakan oleh pembuat Template. Ini akan diterapkan oleh tipe Komponen dinamis kami.
export interface IHaveDynamicData {
public entity: any;
...
}
Seorang ComponentFactory
pembangun
Hal yang sangat penting di sini adalah untuk diingat:
tipe komponen kita, build with our DynamicTypeBuilder
, bisa berbeda - tetapi hanya dengan templatnya (dibuat di atas) . Properti komponen (input, output atau beberapa yang dilindungi) masih sama. Jika kita membutuhkan properti yang berbeda, kita harus mendefinisikan kombinasi yang berbeda dari Templat dan Pembuat Tipe
Jadi, kami menyentuh inti dari solusi kami. Builder, akan 1) membuat ComponentType
2) membuat NgModule
3) kompilasi ComponentFactory
4) cache itu untuk digunakan kembali nanti.
Ketergantungan yang perlu kami terima:
// plunker - app/dynamic/type.builder.ts
import { JitCompiler } from '@angular/compiler';
@Injectable()
export class DynamicTypeBuilder {
// wee need Dynamic component builder
constructor(
protected compiler: JitCompiler
) {}
Dan di sini adalah cuplikan cara mendapatkan 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);
});
});
}
Di atas kita buat dan cache keduanya Component
dan Module
. Karena jika templat (sebenarnya bagian dinamis yang sebenarnya dari semua itu) adalah sama .. kita dapat menggunakan kembali
Dan berikut adalah dua metode, yang mewakili cara yang sangat keren cara membuat kelas / tipe yang didekorasi dalam runtime. Tidak hanya @Component
tetapi juga@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;
}
Penting:
tipe dinamis komponen kami berbeda, tetapi hanya berdasarkan templat. Jadi kami menggunakan fakta itu untuk menyimpannya . Ini sangat penting. Angular2 juga akan melakukan cache ini .. berdasarkan jenisnya . Dan jika kita akan membuat ulang untuk string template yang sama tipe baru ... kita akan mulai menghasilkan kebocoran memori.
ComponentFactory
digunakan oleh komponen hosting
Bagian terakhir adalah komponen, yang menampung target untuk komponen dinamis kami, mis <div #dynamicContentPlaceHolder></div>
. Kami mendapatkan referensi untuk itu dan digunakan ComponentFactory
untuk membuat komponen. Singkatnya, dan ini semua bagian dari komponen itu (jika perlu, buka plunker di sini )
Mari pertama-tama meringkas pernyataan impor:
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
) {}
...
Kami baru saja menerima, pembuat template dan komponen. Berikutnya adalah properti yang diperlukan untuk contoh kita (lebih banyak di komentar)
// 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"
};
Dalam skenario sederhana ini, komponen hosting kami tidak memilikinya @Input
. Jadi itu tidak harus bereaksi terhadap perubahan. Namun terlepas dari kenyataan itu (dan harus siap untuk perubahan yang akan datang) - kita perlu memperkenalkan beberapa flag jika komponen sudah (pertama) diinisiasi. Dan hanya dengan begitu kita bisa memulai keajaiban.
Akhirnya kita akan menggunakan pembangun komponen kita, dan itu baru dikompilasi / di-cache ComponentFacotry
. Kami placeholder Sasaran akan diminta untuk instantiate yangComponent
dengan pabrik itu.
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;
//...
});
}
ekstensi kecil
Juga, kita perlu menyimpan referensi untuk template yang dikompilasi .. untuk dapat melakukannya dengan benar destroy()
, setiap kali kita akan mengubahnya.
// 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;
}
}
selesai
Cukup banyak. Jangan lupa Hancurkan apa pun yang dibangun secara dinamis (ngOnDestroy) . Juga, pastikan untuk melakukan cache dinamis types
dan modules
jika satu-satunya perbedaan adalah template mereka.
Periksa semuanya dalam aksi di sini
untuk melihat versi sebelumnya (misalnya terkait RC5) dari posting ini, periksa sejarahnya