Angular4 动态加载组件杂谈

最近接手了一个项目,客户提出了一个高大上的需求:要求只有一个主界面,所有组件通过Tab来显示。其实这个需求并不诡异,不喜欢界面跳转的客户都非常热衷于这种展现形式。

好吧,客户至上,搞定它!这种实现方式在传统的HTML应用中,非常简单,只是在这Angular4(以下简称ng)中,咋个弄呢?

我们先来了解下ng中动态加载组件的两种方式:

  1. 加载已经声明的组件: 使用ComponentFactoryResolver,将一个组件实例呈现到另一个组件视图上;
  2. 动态创建组件并加载:使用ComponentFactory和Compiler,创建和呈现组件

根据我们的需求,各个组件是事先开发好的,需要在同一个组件上显示出来。所以第一种方式符合我们的要求。

使用ComponentFactoryResolver动态加载组件,需要先了解如下概念:

  1. ViewChild:属性装饰器,通过它可以获得视图上对应的元素;
  2. ViewContainerRef:视图容器,可在其上创建、删除组件;
  3. 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提供了不同级别的变更检测:

  1. 变更检测策略:
    Default :ng提供的Default的检测策略,只要组件的input发生改变,就触发检测;
    OnPush :OnPush检测策略是input发生改变,并不立即触发检测,而是输入的引用发生变化时,才会触发检测。
  2. ChangeDetectorRef.detectChanges():可显式的控制变更检测,在需要的地方使用即可;
  3. NgZone.run():在整个应用中进行变更检测
  4. ApplicationRef.tick():在整个应用中进行变更检测,侦听NgZone的onTurnDone事件,来触发检测

根据文档显示,ng应用缺省就在使用NgZone来检测变更,这对于正常加载的组件是没有问题的,但是对于动态加载的组件却不起作用。几次试验下来,唯有第二种方法起作用:显式调用ChangeDetectorRef.detectChanges()

于是修改ts代码:

interval:any 
loadComponent() {
    ......
    this.interval=setInterval(() => {
      this.compRef.changeDetectorRef.detectChanges();
    }, 50);  //50毫秒检测一次变更
}
ngOnDestroy() {
    ......
    clearInterval(this.interval)
}

鉴于本人的ng技能尚浅,就用这种笨拙的方法解决了数据加载问题,但是如鲠在喉,总觉应该还有更优雅的解决方法,待我再花时日研究下。

啰嗦至此,文中如有不妥之处,欢迎各位看官指正。

补充一句,强烈推荐PrimeNG,它提供了丰富的前端组件,可以方便取用,大大节省了界面的开发速度。

参考文献:

  1. Angular 2 Change Detection, Zones and an example
  2. Angular change detection explained
  3. 深入理解Angular2变化监测和ngZone
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,085评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,096评论 4 62
  • 这是回学校的时候,拖了个快要坏掉的行李箱,而且里面装了很多东西,但他还是很顽强的坚持到宿舍楼下,终于,要上楼的时...
    方舟lsy阅读 303评论 2 1
  • 不甘于寂寞,却又害怕热闹;想要去远方看看,但又没有勇气独自前行。我希望自己身旁也有个朋友,他的名字叫小王子——可以...
    微风轻轻暖阅读 254评论 0 0
  • 佛家一切都讲究缘分, 有缘而来,无缘而去。 该来的自然会来, 不该来的盼也无用,求也无益。 一切随缘,顺其自然。 ...
    风情公子阅读 311评论 0 0