EDIT - пов’язано з 2.3.0 (2016-12-07)
ПРИМІТКА: щоб отримати рішення для попередньої версії, перевірте історію цієї публікації
Аналогічна тема обговорюється тут Еквівалент компіляції $ у куті 2 . Нам потрібно використовувати JitCompiler
і NgModule
. Детальніше про NgModule
Angular2 читайте тут:
Коротко
Є робочий планкер / приклад (динамічний шаблон, тип динамічного компонента, динамічний модуль ,, ... в дії)JitCompiler
Головне:
1) створити шаблон
2) знайти ComponentFactory
в кеші - перейти до 7)
3) - створити Component
4) - створити Module
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
- заповнювача View Target таComponentFactory
- призначити
@Inputs
на новий екземпляр (перемикач від INPUT
до TEXTAREA
редагування) , споживають@Outputs
NgModule
Нам потрібна NgModule
s.
Хоча я хотів би показати дуже простий приклад, у цьому випадку мені знадобляться три модулі (насправді 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 - 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
, може відрізнятися, але лише за його шаблоном (створеним вище) . Властивості компонентів (входи, виходи або деякі захищені) залишаються однаковими. Якщо нам потрібні різні властивості, нам слід визначити різну комбінацію шаблона та типу Builder
Отже, ми торкаємося ядра нашого рішення. Будівельник, 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) цієї публікації, перевірте історію