最近接手了一个项目,客户提出了一个高大上的需求:要求只有一个主界面,所有组件通过 Tab 来显示。其实这个需求并不诡异,不喜欢界面跳转的客户都非常热衷于这种展现形式。
好吧,客户至上,搞定它!这种实现方式在传统的 HTML 应用中,非常简单,只是在这 Angular4(以下简称 ng)中,咋个弄呢?
我们先来了解下 ng 中动态加载组件的两种方式:
根据我们的需求,各个组件是事先开发好的,需要在同一个组件上显示出来。所以第一种方式符合我们的要求。
使用 ComponentFactoryResolver 动态加载组件,需要先了解如下概念:
搞明白了概念,看看代码吧!
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(依赖注入),在加载组件时将需要传入的参数注入到组件中。代码调整如下:
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
}
}
到此,动态加载组件的界面骄傲滴显示在界面上。等等,貌似哪里不对!为什么界面上从后台获取的数据没有加载?
获取数据的代码如下:
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 提供了不同级别的变更检测:
根据文档显示,ng 应用缺省就在使用 NgZone 来检测变更,这对于正常加载的组件是没有问题的,但是对于动态加载的组件却不起作用。几次试验下来,唯有第二种方法起作用:显式调用 ChangeDetectorRef.detectChanges()
于是修改 ts 代码:
interval:any
loadComponent() {
......
this.interval=setInterval(() => {
this.compRef.changeDetectorRef.detectChanges();
}, 50); //50毫秒检测一次变更
}
ngOnDestroy() {
......
clearInterval(this.interval)
}
鉴于本人的 ng 技能尚浅,就用这种笨拙的方法解决了数据加载问题,但是如鲠在喉,总觉应该还有更优雅的解决方法,待我再花时日研究下。
啰嗦至此,文中如有不妥之处,欢迎各位看官指正。
补充一句,强烈推荐PrimeNG,它提供了丰富的前端组件,可以方便取用,大大节省了界面的开发速度。
参考文献:
觉得有帮助的话,不妨考虑购买付费文章来支持我们 🙂 :
付费文章