Angular使用动态加载组件方法实现Dialog的示例


Posted in Javascript onMay 11, 2018

网上的文章和教程基本上写到组件加载完成就没了!没了?!而且都是只能存在一个dialog,想要打开另一个dialog必须先销毁当前打开的dialog,之后看过 material 的实现方式,怪自己太蠢看不懂源码,就只能用自己的方式来实现一个dialog组件了

Dialog组件的目标:可以同时存在多个Dialog,可销毁指定Dialog,销毁后html中无组件残留且提供回调

动态加载组件的实现方式有两种,angular4.0版本之前使用ComponentFactoryResolver来实现,4.0之后可以使用更便捷的ngComponentOutlet来实现,

通过ComponentFactoryResolver实现动态载入

首先理一下ViewChild、ViewChildren、ElementRef、ViewContainerRef、ViewRef、ComponentRef、ComponentFactoryResolver之间的关系:

ViewChild 与 ViewChildren

ViewChild是通过模板引用变量(#)或者指令(directive)用来获取 Angular Dom 抽象类,ViewChild可以使用 ElementRef 或者 ViewContainerRef 进行封装。

@ViewChild('customerRef') customerRef:ElementRef;

ViewChildren通过模板引用变量或者指令用来获取QueryList,像是多个ViewChild组成的数组。

@ViewChildren(ChildDirective) viewChildren: QueryList<ChildDirective>;

ElementRef 与 ViewContainerRef

ViewChild可以使用 ElementRef 或者 ViewContainerRef 进行封装,那么 ElementRef 和 ViewContainerRef 的区别是什么?

用 ElementRef 进行封装,然后通过 .nativeElement 来获取原生Dom元素

console.log(this.customerRef.nativeElement.outerHTML);

ViewContainerRef :视图的容器,包含创建视图的方法和操作视图的api(组件与模板共同定义了视图)。api会返回 ComponentRef 与 ViewRef,那么这两个又是什么?

// 使用ViewContainetRef时,请使用read声明
@ViewChild('customerRef',{read: ViewContainerRef}) customerRef:ViewContainerRef;
···
this.customerRef.createComponent(componentFactory) // componentFactory之后会提到

ViewRef 与 ComponentRef

ViewRef 是最小的UI单元,ViewContainerRef api操作和获取的就是ViewRef

ComponentRef:宿主视图(组件实例视图)通过 ViewContainerRef 创建的对组件视图的引用,可以获取组件的信息并调用组件的方法

ComponentFactoryResolver

要获取 ComponentRef ,需要调用 ViewContainer 的 createComponent 方法,方法需要传入ComponentFactoryResolver创建的参数

constructor(
 private componentFactoryResolver:ComponentFactoryResolver
) { }

viewInit(){
  componentFactory = 
   this.componentFactoryResolver.resolveComponentFactory(DialogComponent);
  // 获取对组件视图的引用,到这一步就已经完成了组件的动态加载
  componentRef = this.customerRef.createComponent(componentFactory);
  // 调用载入的组件的方法
  componentRef.instance.dialogInit(component);
}

具体实现

let componentFactory,componentRef;

@ViewChild('customerRef',{read: ViewContainerRef}) customerRef:ViewContainerRef;
constructor(
 private componentFactoryResolver:ComponentFactoryResolver
) { }

viewInit(){
 // DialogComponent:你想要动态载入的组件,customerRef:动态组件存放的容器
  componentFactory = 
   this.componentFactoryResolver.resolveComponentFactory(DialogComponent);
  componentRef = this.customerRef.createComponent(componentFactory);
}

通过ngComponentOutlet实现动态载入

ngComponentOutlet 大大缩减了代码量,但是只有带4.0之后的版本才支持

具体实现

在dialog.component.html建立动态组件存放节点

<ng-container *ngComponentOutlet="componentName"></ng-container>

将组件(不是组件名称)传入,就OK了,为什么可以这么简单!

dialogInit(component){
  this.componentName = component;
};

Dialog的实现

实现的思路是这样的:首先创建一个dialog组件用来承载其他组件,为dialog创建遮罩和动画,建立一个service来控制dialog的生成和销毁,不过service只生成dialog,dialog内的组件还是需要在dialog组件内进行生成

1、首先写一个公共的service,用来获取根组件的viewContainerRef(尝试过 ApplicationRef 获取根组件的 viewContainerRef 没成功,所以就写成service了)

gerRootNode(...rootNodeViewContainerRef){
  if(rootNode){
   return rootNode;
  }else {
   rootNode = rootNodeViewContainerRef[0];
  };
}

// 然后再根组件.ts内调用
this.fn.gerRootNode(this.viewcontainerRef);

2、创建dialog.service.ts,定义open、close三个方法,使用ViewContainerRef创建dialog组件,创建之前需要调用 ComponentFactoryReslover,并将DialogComponent传入

let componentFactory;
let componentRef;

@Injectable()
export class DialogService {
 constructor(
    private componentFactoryResolver:ComponentFactoryResolver
    private fn:FnService
  ) { }   

 open(component){
  componentFactory = 
   this.componentFactoryResolver.resolveComponentFactory(DialogComponent);
  
  // 这里的获取的是ComponentRef
  containerRef = this.fn.gerRootNode().createComponent(componentFactory); 
  
  // 将containerRef存储下来,以便之后的销毁
  containerRefArray.push(containerRef);
  
  // 调用了组件内的初始化方法,后面会提到
  return containerRef.instance.dialogInit(component,containerRef);
 }

  // 这里有两种情况,一种是在当前组件和dialog组件关闭调用的,因为有返回值所以可以关闭指定的dialog;还有一种是在插入到dialog组件内的组件调用的,因为不知道父组件的信息,所以默认关闭最后一个dialog
 close(_containerRef=null){
  if( _containerRef ){
   return _containerRef.containerRef.instance.dialogDestory();
  }else{
   containerRefArray.splice(-1,1)[0].instance.dialogDestory();
  }
 }

}

3、dialog.component.ts,这里使用 ngComponentOutlet 来实现(ngComponentOutlet 在下面提到,这里为了偷懒,直接拿来用了)

let containerRef,dialogRef = new DialogRef();

export class DialogComponent implements OnInit {

 componentName;
 constructor(
  private fn:FnService
 ) { }

 dialogInit( _component, _containerRef){
  this.componentName = _component;
  containerRef = _containerRef;
  dialogRef['containerRef'] = containerRef;
  return dialogRef;
 };

 dialogDestory(){
  let rootNode = this.fn.gerRootNode();
  // 等待动画结束再移除
  setTimeout(()=>{
  // 这里用到了 viewContainerRef 里的indexOf 和 remove 方法
   rootNode.remove(rootNode.indexOf(containerRef.hostView));
  },400);
  dialogRef.close();
  return true;
 };
 
}

4、这里还创建了一个 DialogRef 的类,用来处理 dialog 关闭后的回调,这样就可以使用 XX.afterClose().subscribe() 来创建回调的方法了

@Injectable()
export class DialogRef{

 public afterClose$ = new Subject();
 constructor(){}

 close(){
  this.afterClose$.next();
  this.afterClose$.complete();
 }

 afterClose(){
  return this.afterClose$.asObservable();
 }

}

创建和销毁dialog

// 创建
let _viewRef = this.dialogService.open(DialogTestComponent);
_viewRef.afterClose().subscribe(()=>{
  console.log('hi');
});

// 销毁
this.dialogService.close()

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Iframe实现跨浏览器自适应高度解决方法
Sep 02 Javascript
jQuery自带的一些常用方法总结
Sep 03 Javascript
js和jquery中循环的退出和继续学习记录
Sep 06 Javascript
jQuery动态修改字体大小的方法【测试可用】
Sep 09 Javascript
微信小程序 数组(增,删,改,查)等操作实例详解
Jan 05 Javascript
Vuejs 用$emit与$on来进行兄弟组件之间的数据传输通信
Feb 23 Javascript
完美实现js选项卡切换效果(二)
Mar 08 Javascript
微信小程序使用gitee进行版本管理
Sep 20 Javascript
Vue对象赋值视图不更新问题及解决方法
Jun 03 Javascript
解决layui中onchange失效以及form动态渲染失效的问题
Sep 27 Javascript
使用webpack将ES6转化ES5的实现方法
Oct 13 Javascript
javascript绘制简单钟表效果
Apr 07 Javascript
详解JavaScript中的数组合并方法和对象合并方法
May 11 #Javascript
Node.js使用cookie保持登录的方法
May 11 #Javascript
实例解析Vue.js下载方式及基本概念
May 11 #Javascript
AngularJS模态框模板ngDialog的使用详解
May 11 #Javascript
基于ionic实现下拉刷新功能
May 10 #Javascript
详解各版本React路由的跳转的方法
May 10 #Javascript
React路由管理之React Router总结
May 10 #Javascript
You might like
php中XMLHttpRequest(Ajax)不能设置自定义的Referer的解决方法
2011/11/26 PHP
浅析php变量作用域的一些问题
2013/08/08 PHP
如何阻止网站被恶意反向代理访问(防网站镜像)
2014/03/18 PHP
免费的ip数据库淘宝IP地址库简介和PHP调用实例
2014/04/08 PHP
护卫神php套件 php版本升级方法(php5.5.24)
2015/05/10 PHP
php session 写入数据库
2016/02/13 PHP
JavaScript 题型问答有答案参考
2010/02/17 Javascript
JS 仿腾讯发表微博的效果代码
2013/12/25 Javascript
Jsonp post 跨域方案
2015/07/06 Javascript
利用jQuery中的ajax分页实现代码
2016/02/25 Javascript
AngularJs中Bootstrap3 datetimepicker使用实例
2016/12/13 Javascript
$.browser.msie 为空或不是对象问题的多种解决方法
2017/03/19 Javascript
jQuery 导航自动跟随滚动的实现代码
2018/05/30 jQuery
react脚手架如何配置less和ant按需加载的方法步骤
2018/11/28 Javascript
小程序怎样让wx.navigateBack更好用的方法实现
2019/11/01 Javascript
uni-app如何实现增量更新功能
2020/01/03 Javascript
Python中在for循环中嵌套使用if和else语句的技巧
2016/06/20 Python
Python tkinter事件高级用法实例
2018/01/31 Python
python 字典修改键(key)的几种方法
2018/08/10 Python
Python清空文件并替换内容的实例
2018/10/22 Python
Python编程快速上手——PDF文件操作案例分析
2020/02/28 Python
python 制作简单的音乐播放器
2020/11/25 Python
CSS3选择器新增问题的实现
2021/01/21 HTML / CSS
吉力贝官方网站:Jelly Belly
2019/03/11 全球购物
施华洛世奇巴西官网:SWAROVSKI巴西
2019/12/03 全球购物
2019年分享net面试的经历和题目
2016/08/07 面试题
银行批评与自我批评
2014/02/10 职场文书
怎么写自荐书范文
2014/02/12 职场文书
六年级学生评语
2014/04/22 职场文书
收入及婚姻状况证明
2014/11/20 职场文书
有关骆驼祥子的读书笔记
2015/06/26 职场文书
婚礼伴郎致辞
2015/07/28 职场文书
使用Nginx搭载rtmp直播服务器的方法
2021/10/16 Servers
最新最全的手机号验证正则表达式
2022/02/24 Javascript
《原神》新角色演示“神里绫人:林隐泓洄” 宠妹狂魔
2022/04/03 其他游戏
Win11使用CAD卡顿或者致命错误怎么办?Win11无法正常使用CAD的解决方法
2022/07/23 数码科技