编辑-与2.3.0有关(2016-12-07)
注意:要获取以前版本的解决方案,请查看此帖子的历史记录
这里讨论了类似的话题Angular 2中的$ compile等效项。我们需要使用JitCompiler
和NgModule
。NgModule
在此处阅读有关Angular2的更多信息:
简而言之
有一个有用的插件/示例 (动态模板,动态组件类型,动态模块JitCompiler
,...正在运行)
原理是:
1)创建模板
2)ComponentFactory
在缓存中查找- 转到7)
3)-创建Component
4)-创建Module
5)-编译Module
6)-返回(以及缓存供以后使用)ComponentFactory
7)使用Target并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
Ng模块
我们需要一个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
是可扩展的,旨在保持用于我们的动态组件模板/类型的所有的小零件。检查app / parts / 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
最后,我们将需要一个临时的运行时模块..但稍后会在工作中创建它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
。这样的属性必须是动态组件的一部分,我们接下来将创建它。
为了使操作更简单,我们可以使用一个界面来定义属性,模板构建器可以使用这些属性。这将通过我们的动态Component类型实现。
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相关),请查看历史记录