แก้ไข - เกี่ยวข้องกับ2.3.0 (2016-12-07)
หมายเหตุ: เพื่อรับวิธีแก้ปัญหาสำหรับรุ่นก่อนหน้าตรวจสอบประวัติของโพสต์นี้
หัวข้อที่คล้ายกันจะกล่าวถึงที่นี่เทียบเท่าของ $ รวบรวมในเชิงมุม 2 เราจำเป็นต้องใช้และJitCompiler
NgModule
อ่านเพิ่มเติมเกี่ยวกับNgModule
ใน Angular2 ที่นี่:
โดยสังเขป
มีพลั่วเกอร์ / ตัวอย่างการทำงาน (แม่แบบไดนามิก, ประเภทองค์ประกอบแบบไดนามิก, โมดูลแบบไดนามิก,JitCompiler
,, ... กำลังดำเนินการ)
หลักการคือ:
1)สร้างเทมเพลต
2)ค้นหาComponentFactory
ในแคช - ไปที่7)
3) - สร้างComponent
4) - สร้างModule
5) - รวบรวม 5) - รวบรวมModule
6) - ส่งคืน (และแคชเพื่อใช้ในภายหลัง) ComponentFactory
7)ใช้เป้าหมายและComponentFactory
สร้างอินสแตนซ์ ของแบบไดนามิกComponent
นี่คือข้อมูลโค้ด(เพิ่มเติมจากที่นี่ ) - เครื่องมือสร้างแบบกำหนดเองของเราจะส่งคืนเพียงสร้าง / แคชComponentFactory
และมุมมองตัวยึดตำแหน่งเป้าหมายใช้เพื่อสร้างอินสแตนซ์ของ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;
//...
});
นี่มัน - สรุปมัน เพื่อรับรายละเอียดเพิ่มเติม .. อ่านด้านล่าง
.
TL & DR
สังเกตผู้สังเกตการณ์และกลับมาอ่านรายละเอียดในกรณีที่ข้อมูลบางส่วนต้องการคำอธิบายเพิ่มเติม
.
คำอธิบายโดยละเอียด - Angular2 RC6 ++ และส่วนประกอบรันไทม์
ด้านล่างคำอธิบายของสถานการณ์นี้เราจะ
- สร้างโมดูล
PartsModule:NgModule
(ผู้ถือชิ้นเล็ก ๆ )
- สร้างโมดูลอื่น
DynamicModule:NgModule
ซึ่งจะมีองค์ประกอบแบบไดนามิกของเรา(และการอ้างอิงPartsModule
แบบไดนามิก)
- สร้างเทมเพลตแบบไดนามิก(วิธีการง่าย ๆ )
- สร้าง
Component
ประเภทใหม่ (เฉพาะในกรณีที่มีการเปลี่ยนแปลงแม่แบบ)
RuntimeModule:NgModule
สร้างใหม่ โมดูลนี้จะมีComponent
ประเภทที่สร้างไว้ก่อนหน้านี้
- โทร
JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule)
ไปรับComponentFactory
- สร้างอินสแตนซ์ของ
DynamicComponent
- งานของตัวแทนเป้าหมายดูและComponentFactory
- กำหนดให้
@Inputs
กับอินสแตนซ์ใหม่ (เปลี่ยนจากINPUT
เป็นTEXTAREA
แก้ไข)กิน@Outputs
NgModule
เราต้องการNgModule
เอส
ในขณะที่ฉันต้องการจะแสดงตัวอย่างที่ง่ายมากในกรณีนี้ผมจะต้องสามโมดูล(ในความเป็นจริง 4 - แต่ฉันไม่นับ AppModule) โปรดใช้สิ่งนี้แทนที่จะเป็นตัวอย่างง่ายๆเป็นพื้นฐานสำหรับเครื่องกำเนิดส่วนประกอบแบบไดนามิกที่แข็งแกร่งจริงๆ
จะมีหนึ่งโมดูลสำหรับส่วนประกอบเล็ก ๆ เช่นstring-editor
, text-editor
( date-editor
, number-editor
... )
@NgModule({
imports: [
CommonModule,
FormsModule
],
declarations: [
DYNAMIC_DIRECTIVES
],
exports: [
DYNAMIC_DIRECTIVES,
CommonModule,
FormsModule
]
})
export class PartsModule { }
ที่ไหนDYNAMIC_DIRECTIVES
มีขยายและมีจุดมุ่งหมายที่จะถือชิ้นส่วนขนาดเล็กทั้งหมดใช้สำหรับแบบไดนามิกของเราตัวแทนแม่แบบ / ประเภท ตรวจสอบแอพ / ชิ้นส่วน / parts.module.ts
ที่สองจะเป็นโมดูลสำหรับการจัดการสิ่งแบบไดนามิกของเรา มันจะมีส่วนประกอบของโฮสต์และผู้ให้บริการบางราย .. ซึ่งจะเป็นซิงเกิล ดังนั้นเราจะเผยแพร่วิธีมาตรฐาน - ด้วยforRoot()
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
],
};
}
}
ตรวจสอบการใช้งานของforRoot()
ในAppModule
สุดท้ายเราจะต้องใช้ adhoc โมดูลรันไทม์ .. แต่มันจะถูกสร้างขึ้นในภายหลังเป็นส่วนหนึ่งของDynamicTypeBuilder
งาน
โมดูลที่สี่คือโมดูลที่เป็นผู้ประกาศผู้ให้บริการคอมไพเลอร์:
...
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
],
อ่าน(อ่าน)เพิ่มเติมเกี่ยวกับNgModule ที่นั่น:
เครื่องมือสร้างเทมเพลต
ในตัวอย่างของเราเราจะประมวลผลรายละเอียดของเอนทิตีประเภทนี้
entity = {
code: "ABC123",
description: "A description of this Entity"
};
ในการสร้างtemplate
ในการนี้plunkerเราใช้แบบนี้ / สร้างที่ไร้เดียงสา
โซลูชันที่แท้จริงซึ่งเป็นตัวสร้างเทมเพลตจริงเป็นสถานที่ที่แอปพลิเคชันของคุณสามารถทำได้มาก
// 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>";
}
}
เคล็ดลับที่นี่คือ - entity
มันสร้างแม่แบบซึ่งใช้ชุดของคุณสมบัติที่รู้จักกันบางอย่างเช่น คุณสมบัติดังกล่าว (-ies) จะต้องเป็นส่วนหนึ่งขององค์ประกอบแบบไดนามิกซึ่งเราจะสร้างต่อไป
เพื่อให้ง่ายขึ้นอีกเล็กน้อยเราสามารถใช้อินเทอร์เฟซเพื่อกำหนดคุณสมบัติซึ่งตัวสร้างเทมเพลตของเราสามารถใช้ได้ สิ่งนี้จะถูกนำไปใช้โดยประเภทส่วนประกอบแบบไดนามิกของเรา
export interface IHaveDynamicData {
public entity: any;
...
}
ComponentFactory
สร้าง
สิ่งที่สำคัญมากที่นี่คือการจำ:
ประเภทส่วนประกอบของเราสร้างกับเราDynamicTypeBuilder
อาจจะแตกต่างกัน - แต่โดยแม่แบบของมัน(ที่สร้างขึ้นด้านบน) คุณสมบัติของส่วนประกอบ(อินพุตเอาต์พุตหรือการป้องกันบางอย่าง ) ยังคงเหมือนเดิม หากเราต้องการคุณสมบัติที่แตกต่างเราควรกำหนดชุดค่าผสมที่แตกต่างกันของแม่แบบและตัวสร้างประเภท
ดังนั้นเรากำลังสัมผัสกับแกนหลักของการแก้ปัญหาของเรา ตัวสร้างจะ 1) สร้างComponentType
2) สร้างNgModule
3) คอมไพล์ComponentFactory
4) แคชเพื่อใช้ในภายหลัง
การพึ่งพาที่เราต้องได้รับ:
// plunker - app/dynamic/type.builder.ts
import { JitCompiler } from '@angular/compiler';
@Injectable()
export class DynamicTypeBuilder {
// wee need Dynamic component builder
constructor(
protected compiler: JitCompiler
) {}
และนี่คือตัวอย่างข้อมูลวิธีรับ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);
});
});
}
ดังกล่าวข้างต้นที่เราสร้างและแคชทั้งสองและComponent
Module
เพราะถ้าแม่แบบ(อันที่จริงส่วนที่แท้จริงของทั้งหมดนั้น)เหมือนกัน .. เราสามารถนำกลับมาใช้ใหม่ได้
และนี่คือสองวิธีซึ่งเป็นวิธีที่ยอดเยี่ยมจริงๆในการสร้างคลาส / ประเภทการตกแต่งในรันไทม์ ไม่เพียง@Component
แต่ยัง@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;
}
สำคัญ:
ชนิดไดนามิกองค์ประกอบของเราแตกต่างกัน แต่เพียงแค่เทมเพลต ดังนั้นเราจึงใช้ข้อเท็จจริงนั้นเพื่อแคชพวกเขา สิ่งนี้สำคัญมากจริงๆ Angular2 ยังจะแคชเหล่านี้ .. โดยประเภท และถ้าเราจะสร้างเทมเพลตสตริงชนิดใหม่ขึ้นมาใหม่ ... เราจะเริ่มสร้างหน่วยความจำรั่ว
ComponentFactory
ใช้โดยองค์ประกอบโฮสต์
<div #dynamicContentPlaceHolder></div>
ชิ้นสุดท้ายเป็นส่วนประกอบซึ่งเป็นเจ้าภาพเป้าหมายสำหรับองค์ประกอบแบบไดนามิกของเราเช่น เราได้รับการอ้างอิงถึงมันและใช้ComponentFactory
ในการสร้างองค์ประกอบ สั้นและนี่คือชิ้นส่วนทั้งหมดของชิ้นส่วนนั้น(ถ้าจำเป็นให้เปิดพลั่วเกอร์ที่นี่ )
ก่อนอื่นเรามาสรุปคำสั่งการนำเข้า:
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
) {}
...
เราเพิ่งได้รับผู้สร้างเทมเพลตและส่วนประกอบ ถัดไปคือคุณสมบัติที่จำเป็นสำหรับตัวอย่างของเรา(เพิ่มเติมในความคิดเห็น)
// 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"
};
@Input
ในสถานการณ์สมมตินี้ง่ายส่วนประกอบโฮสติ้งของเราไม่ได้มี ดังนั้นจึงไม่ต้องตอบสนองต่อการเปลี่ยนแปลง แต่อย่างไรก็ตามข้อเท็จจริงนั้น(และเพื่อเตรียมพร้อมสำหรับการเปลี่ยนแปลงที่กำลังจะมา) - เราจำเป็นต้องแนะนำการตั้งค่าสถานะบางอย่างถ้าองค์ประกอบนั้นมีอยู่แล้ว(ตอนแรก)เริ่มต้นแล้ว และหลังจากนั้นเราก็สามารถเริ่มเวทมนตร์ได้
ในที่สุดเราจะใช้สร้างองค์ประกอบของเราและมันเป็นเพียงแค่การรวบรวม / แคช ComponentFacotry
เรายึดเป้าหมายจะถูกขอให้ยกตัวอย่างกับโรงงานที่Component
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;
//...
});
}
นามสกุลเล็ก
นอกจากนี้เราจำเป็นต้องอ้างอิงถึงเทมเพลตที่รวบรวม .. เพื่อให้สามารถใช้งานได้อย่างถูกต้องdestroy()
ทุกครั้งที่เราจะทำการเปลี่ยนแปลง
// 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;
}
}
เสร็จแล้ว
นั่นมันสวยมาก อย่าลืมที่จะทำลายอะไรสิ่งที่ถูกสร้างขึ้นแบบไดนามิก(ngOnDestroy) นอกจากนี้ตรวจสอบให้แน่ใจว่าแคชแบบไดนามิกtypes
และmodules
ความแตกต่างเพียงอย่างเดียวคือแม่แบบของพวกเขา
ตรวจสอบการทำงานทั้งหมดที่นี่
หากต้องการดูรุ่นก่อนหน้า(เช่น RC5 ที่เกี่ยวข้อง)ของโพสต์นี้ให้ตรวจสอบประวัติ