Angular 2.1.0 动态创建子组件
Posted
技术标签:
【中文标题】Angular 2.1.0 动态创建子组件【英文标题】:Angular 2.1.0 create child component on the fly, dynamically 【发布时间】:2017-02-24 21:49:08 【问题描述】:我在angular 2.1.0
中尝试做的是动态创建子组件,这些子组件应该被注入到父组件中。例如父组件是lessonDetails
,其中包含所有课程的共享内容,例如Go to previous lesson
、Go to next lesson
等按钮。根据路由参数,应该是子组件的课程内容需要动态注入到父组件中。子组件(课程内容)的 html 被定义为外部某处的纯字符串,它可以是如下对象:
export const LESSONS =
"lesson-1": `<p> lesson 1 </p>`,
"lesson-2": `<p> lesson 2 </p>`
问题可以通过innerHtml
在父组件模板中具有类似以下的内容轻松解决。
<div [innerHTML]="lessonContent"></div>
在每次更改路由参数时,父组件的属性lessonContent
会发生变化(内容(新模板)将从LESSON
对象中获取)导致父组件模板被更新。这可行,但 Angular 不会处理通过 innerHtml
注入的内容,因此无法使用 routerLink
和其他东西。
在新的角度发布之前,我使用http://blog.lacolaco.net/post/dynamic-component-creation-in-angular-2/ 的解决方案解决了这个问题,我一直使用ComponentMetadata
和ComponentResolver
来动态创建子组件,例如:
const metadata = new ComponentMetadata(
template: this.templateString,
);
其中templateString
被传递给子组件作为Input
属性传递给子组件。 MetaData
和 ComponentResolver
在 angular 2.1.0
中已弃用/删除。
所以问题不仅仅是关于动态组件的创建,就像在一些相关的 SO 问题中描述的那样,如果我为每个课程内容定义了组件,问题会更容易解决。这意味着我需要为 100 个不同的课程预先声明 100 个不同的组件。已弃用的元数据提供的行为类似于在单个组件的运行时更新模板(在路由参数更改时创建和销毁单个组件)。
更新 1: 在最近的 Angular 版本中,所有需要动态创建/注入的组件都需要在 entryComponents
中的 @NgModule
中进行预定义。所以在我看来,与上述问题相关,如果我需要 100 个课程(需要动态创建的组件),这意味着我需要预定义 100 个组件
更新2:在更新1的基础上,可以通过ViewContainerRef.createComponent()
通过以下方式完成:
// lessons.ts
@Component( template: html string loaded from somewhere )
class LESSON_1
@Component( template: html string loaded from somewhere )
class LESSON_2
// exported value to be used in entryComponents in @NgModule
export const LESSON_CONTENT_COMPONENTS = [ LESSON_1, LESSON_2 ]
现在在父组件中路由参数发生变化
const key = // determine lesson name from route params
/**
* class is just buzzword for function
* find Component by name (LESSON_1 for example)
* here name is property of function (class)
*/
const dynamicComponent = _.find(LESSON_CONTENT_COMPONENTS, name: key );
const lessonContentFactory = this.resolver.resolveComponentFactory(dynamicComponent);
this.componentRef = this.lessonContent.createComponent(lessonContentFactory);
父模板如下:
<div *ngIf="something" #lessonContentContainer></div>
其中lessonContentContainer
被修饰为@ViewChildren
属性,lessonContent
被修饰为@ViewChild
,并在ngAfterViewInit ()
中初始化为:
ngAfterViewInit ()
this.lessonContentContainer.changes.subscribe((items) =>
this.lessonContent = items.first;
this.subscription = this.activatedRoute.params.subscribe((params) =>
// logic that needs to show lessons
)
)
解决方案有一个缺点,即所有组件(LESSON_CONTENT_COMPONENTS)都需要预定义。有没有办法使用单个组件并更改该组件的模板在运行时(路由参数更改)?
【问题讨论】:
见hl7.org/fhir/StructureDefinition/…。添加 HTML 只是添加 HTML,如果您想要动态组件,您可以使用ViewContainerRef.createComponent()
。否则,只会为静态添加到组件模板的选择器创建组件和指令。
@GünterZöchbauer 感谢您的回复,实际上我正在使用ViewContainerRef.createComponent()
请检查有问题的更新 2 部分
您不能在运行时修改组件的模板。有一些方法可以在运行时创建新组件。我不知道这方面的细节,但在 SO 上有类似问题的答案。
这里有类似的问题How can I use/create dynamic template to compile dynamic Component with Angular 2.0?
【参考方案1】:
您可以使用以下HtmlOutlet
指令:
import
Component,
Directive,
NgModule,
Input,
ViewContainerRef,
Compiler,
ComponentFactory,
ModuleWithComponentFactories,
ComponentRef,
ReflectiveInjector
from '@angular/core';
import RouterModule from '@angular/router';
import CommonModule from '@angular/common';
export function createComponentFactory(compiler: Compiler, metadata: Component): Promise<ComponentFactory<any>>
const cmpClass = class DynamicComponent ;
const decoratedCmp = Component(metadata)(cmpClass);
@NgModule( imports: [CommonModule, RouterModule], declarations: [decoratedCmp] )
class DynamicHtmlModule
return compiler.compileModuleAndAllComponentsAsync(DynamicHtmlModule)
.then((moduleWithComponentFactory: ModuleWithComponentFactories<any>) =>
return moduleWithComponentFactory.componentFactories.find(x => x.componentType === decoratedCmp);
);
@Directive( selector: 'html-outlet' )
export class HtmlOutlet
@Input() html: string;
cmpRef: ComponentRef<any>;
constructor(private vcRef: ViewContainerRef, private compiler: Compiler)
ngOnChanges()
const html = this.html;
if (!html) return;
if(this.cmpRef)
this.cmpRef.destroy();
const compMetadata = new Component(
selector: 'dynamic-html',
template: this.html,
);
createComponentFactory(this.compiler, compMetadata)
.then(factory =>
const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
this.cmpRef = this.vcRef.createComponent(factory, 0, injector, []);
);
ngOnDestroy()
if(this.cmpRef)
this.cmpRef.destroy();
另请参阅Plunker Example
Example with custom component
对于 AOT 编译,请参阅这些线程
https://github.com/angular/angular/issues/15510 http://blog.assaf.co/angular-2-harmony-aot-compilation-with-lazy-jit-2/另见 github Webpack AOT 示例 https://github.com/alexzuza/angular2-build-examples/tree/master/ngc-webpack
【讨论】:
感谢您的回答。还有一个问题,我们不应该保存对当前cmpRef
的引用并在创建新的动态组件之前手动销毁它吗?在创建新组件 if (this.cmpRef) this.cmpRef.destroy();
之前,在 HtmlOutlet 指令中,private cmpRef: ComponentRef<any>
然后在 ngOnChanges
内部。还是会自动销毁?
是的,当然。我们必须手动完成。我不确定,但似乎this.vcRef.clear
做同样的事情。我更新了我的答案
再问一个问题,有点与问题无关。尝试在 html 出口指令上应用 safeHtml
管道作为 <html-outlet [html]="htmlString | safeHtml></html-outlet>"
得到错误 Cannot set property stack of [object Object] which has only a getter(…)
。 safeHtml 是一个非常简单的管道,将 transform
方法实现为 transform (html: string) return this.sanitizer.bypassSecurityTrustHtml(html);
相关回答***.com/questions/38037760/…
使用这个解决方案,但是由于新的 angular-cli 版本,它抛出了这个错误:没有为 'DynamicHtmlModule' 找到 NgModule 元数据。有什么建议吗?以上是关于Angular 2.1.0 动态创建子组件的主要内容,如果未能解决你的问题,请参考以下文章