有時候須要根據URL來渲染不一樣組件,我所指的是在同一個URL地址中根據參數的變化顯示不一樣的組件;這是利用Angular動態加載組件完成的,同時也會設法讓這部分動態組件也支持AOT。typescript
下面以一個Step組件爲示例,完成一個3個步驟的示例展現,而且能夠經過URL user?step=step-one
的變化顯示第N個步驟的內容。ide
首先,仍是須要先建立動態加載組件模塊。ui
import { Component, Input, ViewContainerRef, ComponentFactoryResolver, OnDestroy, ComponentRef } from '@angular/core'; @Component({ selector: 'step', template: `` }) export class Step implements OnDestroy { private currentComponent: ComponentRef<any>; constructor(private vcr: ViewContainerRef, private cfr: ComponentFactoryResolver) {} @Input() set data(data: { component: any, inputs?: { [key: string]: any } } ) { const compFactory = this.cfr.resolveComponentFactory(data.component); const component = this.vcr.createComponent(compFactory); if (data.inputs) { for (let key in data.inputs) { component.instance[key] = data.inputs[key]; } } this.destroy(); this.currentComponent = component; } destroy() { if (this.currentComponent) { this.currentComponent.destroy(); this.currentComponent = null; } } ngOnDestroy(): void { this.destroy(); } }
拋開一銷燬動做不談的話,實際就兩行代碼:this
let compFactory = this.cfr.resolveComponentFactory(this.comp);
利用 ComponentFactoryResolver
查找提供組件的 ComponentFactory
,然後利用這個工廠來建立實際的組件。code
this.compInstance = this.vcr.createComponent(compFactory);
這一切都很是簡單。component
而對於一些基本的參數,是直接對組件實例進行賦值。ip
for (let key in data.inputs) { component.instance[key] = data.inputs[key]; }
最後,還須要告訴Angular AOT編譯器爲用戶動態組件提供工廠註冊,不然 ComponentFactoryResolver
會找不到它們,最簡單就是利用 NgModule.entryComponents
進行註冊。開發
@NgModule({ entryComponents: [ UserOneComponent, UserTwoComponent, UserThirdComponent ] }) export class AppModule { }
但這樣其實仍是挺奇怪的,entryComponents
自己可能還會存在其餘組件。而動態加載組件自己是一個通用性很是強,所以,把它封裝成名曰 StepModule
挺有必要的,這樣的話,就能夠建立一種看起來更舒服的方式。get
@NgModule({ declarations: [ Step ], exports: [ Step ] }) export class StepModule { static withComponents(components: any) { return { ngModule: StepModule, providers: [ { provide: ANALYZE_FOR_ENTRY_COMPONENTS, useValue: components, multi: true } ] } } }
經過利用 ANALYZE_FOR_ENTRY_COMPONENTS
將多個組件以更友好的方式動態註冊至 entryComponents
。input
const COMPONENTS = [ ]; @NgModule({ declarations: [ ...COMPONENTS ], imports: [ StepModule.withComponents([ ...COMPONENTS ]) ] }) export class AppModule { }
有3個Step步驟的組件,分別爲:
// user-one.component.ts import { Component, OnDestroy, Input, Injector, EventEmitter, Output } from '@angular/core'; @Component({ selector: 'step-one', template: `<h2>Step One Component:params value: {{step}}</h2>` }) export class UserOneComponent implements OnDestroy { private _step: string; @Input() set step(str: string) { console.log('@Input step: ' + str); this._step = str; } get step() { return this._step; } ngOnInit() { console.log('step one init'); } ngOnDestroy(): void { console.log('step one destroy'); } }
user-two、user-third 略同,這裏組件還須要進行註冊:
const STEPCOMPONENTS = [ UserOneComponent, UserTwoComponent, UserThirdComponent ]; @NgModule({ declarations: [ ...STEPCOMPONENTS ], imports: [ StepModule.withComponents([ ...STEPCOMPONENTS ]) ] }) export class AppModule { }
這裏沒有 entryComponents
字眼,而是爲 StepModule
模塊幫助咱們動態註冊。這樣至少看起來更內聚一點,並且並不會與其餘 entryComponents
在一塊兒,待東西越多越不舒服。
最後,還須要 UserComponent
組件來維護步驟容器,會根據 URL 參數的變化,利用 StepComponent
組件動態加載相應組件。
@Component({ selector: 'user', template: `<step [comp]="stepComp"></step>` }) export class UserComponent { constructor(private route: ActivatedRoute) {} stepComp: any; ngOnInit() { this.route.queryParams.subscribe(params => { const step = params['step'] || 'step-one'; // 組件與參數對應表 const compMaps = { 'step-one': { component: UserOneComponent, inputs: { step: step } }, 'step-two': { component: UserTwoComponent }, 'step-third': { component: UserThirdComponent }, }; this.stepComp = compMaps[step]; }); } }
很是簡單的使用,並且又對AOT比較友好。
文章裏面一直都在提AOT,其實AOT是Angular爲了提供速度與包大小而生的,按咱們項目的經驗來看至少在包的大小能夠減小到 40% 以上。
固然,若是你是用angular cli開發,那麼,當你進行 ng build --prod
的時候,默認就已經開啓 AOT 編譯模式。