我在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
对象获取),从而导致更新父组件模板。这有效但角度不会处理通过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:在最近的角度版本中,需要动态创建/注入的所有组件都需要在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)都需要预先定义。
有没有办法使用一个组件并更改模板运行时的组件(路径参数更改)?
答案 0 :(得分:14)
您可以使用以下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
对于AOT编译,请参阅这些主题
另请参阅 github Webpack AOT示例 https://github.com/alexzuza/angular2-build-examples/tree/master/ngc-webpack