资讯专栏INFORMATION COLUMN

Angular4 动态加载组件杂谈

testHs / 1542人阅读

摘要:最近接手了一个项目,客户提出了一个高大上的需求要求只有一个主界面,所有组件通过来显示。

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

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

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

加载已经声明的组件: 使用ComponentFactoryResolver,将一个组件实例呈现到另一个组件视图上;

动态创建组件并加载:使用ComponentFactory和Compiler,创建和呈现组件

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

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

ViewChild:属性装饰器,通过它可以获得视图上对应的元素;

ViewContainerRef:视图容器,可在其上创建、删除组件;

ComponentFactoryResolver:组件解析器,可以将一个组件呈现在另一个组件的视图上。

搞明白了概念,看看代码吧:

//// HTML代码
//// 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: ""
})
export class DynamicComponent implements OnDestroy,OnInit {
    @ViewChild("container", { read: ViewContainerRef }) container: ViewContainerRef;
    @Input() componentName      //需要加载的组件名
    compRef: ComponentRef; //  加载的组件实例
    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参数,其值为参数值对
//// 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提供了不同级别的变更检测:

变更检测策略:

Default :ng提供的Default的检测策略,只要组件的input发生改变,就触发检测;
  OnPush :OnPush检测策略是input发生改变,并不立即触发检测,而是输入的引用发生变化时,才会触发检测。

ChangeDetectorRef.detectChanges():可显式的控制变更检测,在需要的地方使用即可;

NgZone.run():在整个应用中进行变更检测

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,它提供了丰富的前端组件,可以方便取用,大大节省了界面的开发速度。

参考文献:

Angular 2 Change Detection, Zones and an example

Angular change detection explained

深入理解Angular2变化监测和ngZone

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/83891.html

相关文章

  • Angular2 VS Angular4 深度对比:特性、性能

    摘要:的特性和性能是的超集,用于帮助的开发。注解提供了连接元数据和功能的工具。通过在库中提供基本信息可以调用函数或创建类的实例来检查相关元数据,从而简化了对象实例的构建。停用它会响应跳出旧控制器的成功事件。 showImg(https://segmentfault.com/img/bVSqTU?w=850&h=460); 在Web应用开发领域,Angular被认为是最好的开源JavaScri...

    孙淑建 评论0 收藏0
  • 杂谈 CSS IN JS

    摘要:缺乏高级编程特性影响同样深远,社区发展的预处理器能够有效缓解,,,殊途同归,异军突起,基本实现变量嵌套变量混合扩展和逻辑等。 前言 关注点分离(separation of concerns)原则多年来大行其道,实践中一般将 HTML、CSS、JavaScript 分开编写维护,早期框架 angularjs 即是如此,直到 React 争议中问世,引领关注点混合趋势,驱使开发者重新审视 ...

    Nosee 评论0 收藏0
  • 前端模块化杂谈

    摘要:并不是使用安装的模块我们就可以使用同样的方式使用任何一个模块,使用某种工具将这些模块打包发布作为事实上的前端模块化标准,或可以出来解救我们。目前比较拿的出手的,也就是的模块化,比如或者等等,分别可以使用和。 Teambition是一家追求卓越技术的公司,我们工程师都很Geek,我们使用了很多新潮的,开源的技术。同时我们也贡献了很多开源的项目。我们希望能够把一些技术经验分享给大家。...

    yacheng 评论0 收藏0
  • 前端模块化杂谈

    摘要:并不是使用安装的模块我们就可以使用同样的方式使用任何一个模块,使用某种工具将这些模块打包发布作为事实上的前端模块化标准,或可以出来解救我们。目前比较拿的出手的,也就是的模块化,比如或者等等,分别可以使用和。 Teambition是一家追求卓越技术的公司,我们工程师都很Geek,我们使用了很多新潮的,开源的技术。同时我们也贡献了很多开源的项目。我们希望能够把一些技术经验分享给大家。...

    li21 评论0 收藏0
  • 利用angular4和nodejs-express构建一个简单的网站(六)—用户模块和路由分析

    摘要:上一节解决了用户注册和登录数据部分的内容。这一节开始分析用户模块用户路由。用户管理模块分析主要代码如下数组中,是构建子组件必须引入的模块。当点击标签时,根据路由定义直接跳转到组件,进行用户的注册操作。 上一节解决了用户注册和登录数据部分的内容。这一节开始分析用户模块、用户路由。## 用户管理模块UserModule分析 ##UserModule主要代码如下: import { NgMo...

    cfanr 评论0 收藏0

发表评论

0条评论

testHs

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<