angular2 – 我如何使用/创建动态模板来编译动态组件与Angular 2
我想动态创建模板。这应该用于在运行时构建ComponentType并将其放置(甚至替换)它在主机组件内部的某处。
直到RC4我使用ComponentResolver,但与RC5我得到消息:
我发现这个(offical angular2)文档 Angular 2 Synchronous Dynamic Component Creation 并且明白我可以使用 >使用ComponentFactoryResolver的动态ngIf类型。如果我将已知组件传递给@Component({entryComponents:[comp1,comp2],…}) – 我可以使用.resolveComponentFactory(componentToRender); 但问题是如何使用该编译器?上面的注意说,我应该调用:Compiler.compileComponentSync / Async – 那么如何? 例如。我想创建(基于一些配置条件)这种模板的一种设置 <form> <string-editor [propertyName]="'code'" [entity]="entity" ></string-editor> <string-editor [propertyName]="'description'" [entity]="entity" ></string-editor> ... 在另一种情况下,这一个(字符串编辑器替换为文本编辑器) <form> <text-editor [propertyName]="'code'" [entity]="entity" ></text-editor> ... 等等(不同的数字/日期/参考编辑器的属性类型,跳过一些属性为一些用户…)。也就是说这是一个例子,真正的配置可以生成更多不同和复杂的模板。 模板正在改变,所以我不能使用ComponentFactoryResolver和传递现有的…我需要解决方案与编译器 AOT和JitCompiler(前RuntimeCompiler) 您要使用此功能与AOT(提前编译)吗?你得到:
请留下您的评论,在这里投票: Could/would/will code using COMPILER_PROVIDERS be supported by AOT?
编辑 – 相关
2.3.0(2016-12-07)
类似的主题在这里讨论Equivalent of $compile in Angular 2.我们需要使用JitCompiler和NgModule。阅读更多关于NgModule在Angular2这里: > Angular 2 RC5 – NgModules,Lazy Loading and AoT compilation 简而言之 有a working plunker/example(动态模板,动态组件类型,动态模块,JitCompiler,…) 主体是: 这里是一个代码片段(更多的是here) – 我们的自定义Builder是返回刚构建/缓存的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 观察一个plunker,回来阅读细节,以防一些片段需要更多的解释 。 详细说明 – Angular2 RC6&运行时组件 下面的this scenario的描述,我们会 >创建一个模块PartModule:NgModule(小件的持有者) NgModule 我们需要一个NgModules。
将有一个模块用于所有小组件,例如。字符串编辑器,文本编辑器(日期编辑器,数字编辑器…) @NgModule({ imports: [ CommonModule,FormsModule ],declarations: [ DYNAMIC_DIRECTIVES ],exports: [ DYNAMIC_DIRECTIVES,CommonModule,FormsModule ] }) export class PartsModule { }
第二个将是我们的动态东西处理的模块。它将包含托管组件和一些提供程序..这将是单身。因此我们将发布它们的标准方式 – with for() 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 ],}; } }
最后,我们将需要一个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有: > Angular 2 RC5 – NgModules,Lazy Loading and AoT compilation 模板构建器 在我们的例子中,我们将处理这种实体的细节 entity = { code: "ABC123",description: "A description of this Entity" }; 要创建一个模板,在这个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>"; } } 这里的一个技巧是 – 它构建一个模板,使用一些已知的属性,例如。实体。这些属性(-ies)必须是动态组件的一部分,我们将在下面创建它们。 为了使它更容易一些,我们可以使用一个接口来定义属性,我们的模板构建器可以使用它。这将由我们的动态组件类型实现。 export interface IHaveDynamicData { public entity: any; ... } ComponentFactory生成器 这里非常重要的是要记住:
因此,我们正在触及我们的解决方案的核心。 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而且@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; } 重要:
主机组件使用ComponentFactory 最后一块是一个组件,它托管了我们的动态组件的目标,例如。 < div#dynamicContentPlaceHolder>< / div> ;.我们得到它的引用,并使用ComponentFactory创建一个组件。简而言之,这个组件的所有部分(如果需要,打开plunker here) 让我们先总结import语句: 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,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。我们的目标占位符将被要求与该工厂实例化组件。 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)。此外,如果唯一的区别是它们的模板,请确保缓存动态类型和模块。 检查所有在行动here
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |