最近接手了一個項目,客戶提出了一個高大上的需求:要求只有一個主界面,全部組件經過Tab來顯示。其實這個需求並不詭異,不喜歡界面跳轉的客戶都很是熱衷於這種展示形式。html
好吧,客戶至上,搞定它!這種實現方式在傳統的HTML應用中,很是簡單,只是在這Angular4(如下簡稱ng)中,咋個弄呢?前端
咱們先來了解下ng中動態加載組件的兩種方式:git
根據咱們的需求,各個組件是事先開發好的,須要在同一個組件上顯示出來。因此第一種方式符合咱們的要求。github
使用ComponentFactoryResolver動態加載組件,須要先了解以下概念:數組
搞明白了概念,看看代碼吧:ide
//// HTML代碼 <dynamic-container [componentName]="'RoleComponent'" ></dynamic-container>
//// ts代碼 import {Component, Input, ViewContainerRef, ViewChild, ComponentFactoryResolver,ComponentRef,OnDestroy,OnInit} from '@angular/core'; import {RoleComponent} from "./role/role.component"; @Component({ selector: 'dynamic-container', entryComponents: [RoleComponent,....], //須要動態加載的組件名,這裏必定要指定,不然報錯 template: "<ng-template #container></ng-template>" }) export class DynamicComponent implements OnDestroy,OnInit { @ViewChild('container', { read: ViewContainerRef }) container: ViewContainerRef; @Input() componentName //須要加載的組件名 compRef: ComponentRef<any>; // 加載的組件實例 constructor(private resolver: ComponentFactoryResolver) {} loadComponent() { let factory = this.resolver.resolveComponentFactory(this.componentName); if (this.compRef) { this.compRef.destroy(); } this.compRef = this.container.createComponent(factory) //建立組件 } ngAfterContentInit() { this.loadComponent() } ngOnDestroy() { if(this.compRef){ this.compRef.destroy(); } } }
代碼的確不復雜!測試
但是,若是加載的組件有傳入的參數,好比修改角色組件,須要傳入角色id,該怎麼辦呢?有辦法解決,使用ReflectiveInjector(依賴注入),在加載組件時將須要傳入的參數注入到組件中。代碼調整以下:ui
//// HTML代碼,增長了inputs參數,其值爲參數值對 <dynamic-container [componentName]="'RoleComponent'" [inputs]="{'myName':'dynamic'}" ></dynamic-container>
//// ts代碼 import { ReflectiveInjector} from '@angular/core'; ...... export class DynamicComponent implements OnDestroy,OnInit { @Input() inputs:any //加載組件須要傳入的參數組 ....... loadComponent() { let factory = this.resolver.resolveComponentFactory(this.componentName); if(!this.inputs) this.inputs={} let inputProviders = Object.keys(this.inputs).map((inputName) => { return {provide: inputName, useValue: this.inputs[inputName]};}); let resolvedInputs = ReflectiveInjector.resolve(inputProviders); let injector = ReflectiveInjector.fromResolvedProviders(resolvedInputs, this.container.parentInjector); if (this.compRef) { this.compRef.destroy(); } this.compRef = factory.create(injector) //建立帶參數的組件 this.container.insert(this.compRef.hostView);//呈現組件的視圖 } ngAfterContentInit() { this.loadComponent() } ...... } ////RoleComponent代碼以下 export class RoleComponent implements OnInit { myName:string ........ constructor(){ //this.myName的值爲dynamic } }
到此,動態加載組件的界面驕傲滴顯示在界面上。等等,貌似哪裏不對!爲何界面上從後臺獲取的數據沒有加載?this
獲取數據的代碼以下:code
export class RoleComponent implements OnInit { roleList=[]; ...... constructor(private _roleService.list:RoleService) { this._roleService.list().subscribe(res=>{ this.roleList=res.roleList; }); } ...... }
通過反覆測試,得出結論以下:從後臺經過HTTP獲取的數據已經得到,只是沒有觸發ng進行變動檢測,因此界面沒有渲染出數據。
抱着「遇坑填坑」的信念,研習ng的文檔,發現ng支持手動觸發變動檢測,只要在適當的位置調用變動檢測便可。同時,ng提供了不一樣級別的變動檢測:
變動檢測策略:
Default :ng提供的Default的檢測策略,只要組件的input發生改變,就觸發檢測; OnPush :OnPush檢測策略是input發生改變,並不當即觸發檢測,而是輸入的引用發生變化時,纔會觸發檢測。
根據文檔顯示,ng應用缺省就在使用NgZone來檢測變動,這對於正常加載的組件是沒有問題的,可是對於動態加載的組件卻不起做用。幾回試驗下來,惟有第二種方法起做用:顯式調用ChangeDetectorRef.detectChanges()
因而修改ts代碼:
interval:any loadComponent() { ...... this.interval=setInterval(() => { this.compRef.changeDetectorRef.detectChanges(); }, 50); //50毫秒檢測一次變動 } ngOnDestroy() { ...... clearInterval(this.interval) }
鑑於本人的ng技能尚淺,就用這種笨拙的方法解決了數據加載問題,可是如鯁在喉,總覺應該還有更優雅的解決方法,待我再花時日研究下。
囉嗦至此,文中若有不妥之處,歡迎各位看官指正。
補充一句,強烈推薦PrimeNG,它提供了豐富的前端組件,能夠方便取用,大大節省了界面的開發速度。
參考文獻: