最近接手了一个项目,客户提出了一个高大上的需求:要求只有一个主界面,全部组件经过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,它提供了丰富的前端组件,能够方便取用,大大节省了界面的开发速度。
参考文献: