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 相关文章推荐
基于jQuery的获取标签名的代码
Jul 16 Javascript
详解JavaScript的回调函数
Nov 20 Javascript
如何动态加载外部Javascript文件
Dec 02 Javascript
AngularJS入门教程引导程序
Aug 18 Javascript
AngularJS解决ng界面长表达式(ui-set)的方法分析
Nov 07 Javascript
jQuery实现对象转为url参数的方法
Jan 11 Javascript
easyui-datagrid特殊字符不能显示的处理方法
Apr 12 Javascript
jquery与js实现全选功能的区别
Jun 11 jQuery
JS声明对象时属性名加引号与不加引号的问题及解决方法
Feb 16 Javascript
Node.js动手撸一个静态资源服务器的方法
Mar 09 Javascript
JS制作简易计算器的实例代码
Jul 04 Javascript
Vue 解决通过this.$refs来获取DOM或者组件报错问题
Jul 28 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下目前为目最全的CURL中文说明
2010/08/01 PHP
浅谈PHP发送HTTP请求的几种方式
2017/07/25 PHP
PHP基于双向链表与排序操作实现的会员排名功能示例
2017/12/26 PHP
php使用fullcalendar日历插件详解
2019/03/06 PHP
PHP面向对象类型约束用法分析
2019/06/12 PHP
javascript 打印页面代码
2009/03/24 Javascript
基于jquery的设置页面文本框 只能输入数字的实现代码
2011/04/19 Javascript
深入理解JavaScript系列(3) 全面解析Module模式
2012/01/15 Javascript
document.write()及其输出内容的样式、位置控制
2013/08/12 Javascript
ExtJS4如何自动生成控制grid的列显示、隐藏的checkbox
2014/05/02 Javascript
Javascript基础教程之数据类型转换
2015/01/18 Javascript
jQuery 调用WebService 实例讲解
2016/06/28 Javascript
Bootstrap基本样式学习笔记之表单(3)
2016/12/07 Javascript
angular 动态组件类型详解(四种组件类型)
2017/02/22 Javascript
Vue + Webpack + Vue-loader学习教程之功能介绍篇
2017/03/14 Javascript
JavaScript之事件委托实例(附原生js和jQuery代码)
2017/07/22 jQuery
nodejs acl的用户权限管理详解
2018/03/14 NodeJs
layer弹出层全屏及关闭方法
2018/08/17 Javascript
JS实现多选框的操作
2020/06/24 Javascript
[01:29:46]DOTA2上海特级锦标赛C组资格赛#1 OG VS LGD第二局
2016/02/27 DOTA
Python实现全局变量的两个解决方法
2014/07/03 Python
Python生成器实现简单&quot;生产者消费者&quot;模型代码实例
2020/03/27 Python
如何使用PyCharm将代码上传到GitHub上(图文详解)
2020/04/27 Python
python中Pexpect的工作流程实例讲解
2021/03/02 Python
美津浓巴西官方网站:Mizuno巴西
2019/07/24 全球购物
蒂娜商店:Tiina the Store
2019/12/07 全球购物
大专生毕业的自我评价
2014/02/06 职场文书
洗车工岗位职责
2014/03/15 职场文书
领导干部个人对照检查材料(群众路线)
2014/09/26 职场文书
民间借贷纠纷案件代理词
2015/05/26 职场文书
雨雪天气温馨提示
2015/07/15 职场文书
干部培训简讯
2015/07/20 职场文书
护士旷工检讨书
2015/08/15 职场文书
婚礼长辈答谢词
2015/09/29 职场文书
保险公司2016开门红口号集锦
2015/12/24 职场文书
利用Python第三方库实现预测NBA比赛结果
2021/06/21 Python