动态创建angular组件实现popup弹窗
承接上文,本文将从一个基本的angular启动项目开始搭建一个具有基本功能、较通用、低耦合、可扩展的popup弹窗(脸红),主要分为以下几步:
- 基本项目结构搭建
- 弹窗服务
- 弹窗的引用对象
- 准备作为模板的弹窗组件
- 使用方法
基本项目结构
因为打算将我们的popup弹窗设计为在npm托管的包,以便其他项目可以下载并使用,所以我们的启动项目大概包含如下结构:
- package.json // 定义包的基本信息,包括名字、版本号、依赖等
- tsconfig.json // angular项目基于typescript进行搭建,需要此文件来指定ts的编译规则
- ... // tslint等一些帮助开发的配置文件
- index.ts // 放在根目录,导出需要导出的模块、服务等
- /src // 实际模块的实现
- /src/module.ts // 模块的定义
- /src/service.ts // 弹窗服务
- /src/templates/* // 作为模板的组件
- /src/popup.ref.ts // 对创建好的组件引用的封装对象
- /src/animations.ts // 动画的配置
现在我们只来关心src目录下的实现。
弹窗服务
弹窗服务的职责是提供一个叫做open的方法,用来创建出组件并显示,还得对创建好的组件进行良好的控制:
import { Injectable, ApplicationRef, ComponentFactoryResolver,
ComponentRef, EmbeddedViewRef } from '@angular/core';
import { YupRef, ComponentType } from './popup.ref';
@Injectable()
export class DialogService {
private loadRef: YupRef<LoadComponent>;
constructor(
private appRef: ApplicationRef,
private compFactRes: ComponentFactoryResolver
) {}
// 创建一个组件,组件通过泛型传入以做到通用
public open<T>(component: ComponentType<T>, config: any) {
// 创建组件工厂
const factory = this.compFactRes.resolveComponentFactory(component);
// 创建一个新的弹窗引用
const dialogRef = new YupRef(factory, config);
// 将创建好的组件引用(由弹窗引用创建并返回)append到body标签下
window.document.body.appendChild(this.getComponentRootNode(dialogRef.componentRef()));
// 加入angular脏检查
this.appRef.attachView(dialogRef.componentRef().hostView);
// 将创建的弹窗引用返回给外界
return dialogRef;
}
// 参考自Material2,将ComponentRef类型的组件引用转换为DOM节点
private getComponentRootNode(componentRef: ComponentRef<any>): HTMLElement {
return (componentRef.hostView as EmbeddedViewRef<any>).rootNodes[0] as HTMLElement;
}
}
// 参考自Material2 用于作为传入组件的类型
export interface ComponentType<T> {
new (...args: any[]): T;
}
弹窗的引用对象
上面服务中的open方法实际上把创建组件的细节通过new一个YupRef即弹窗引用来实现,这是因为考虑到服务本身是单例,如果仅使用open方法直接创建多个弹窗,在使用时会丢失除了最后一个弹窗外的控制能力,笔者这里采用的办法是将创建的弹窗封装成一个类即YupRef:
import { ComponentRef, InjectionToken, ReflectiveInjector, ComponentFactory } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
// 用于注入自定义数据到创建的组件中
export const YUP_DATA = new InjectionToken<any>('YUPPopupData');
export class YupRef<T> {
// 弹窗关闭的订阅
private afterClose$: Subject<any>;
// 弹窗引用变量
private dialogRef: ComponentRef<T>;
constructor(
private factory: ComponentFactory<T>,
private config: any // 传入的自定义数据
) {
this.afterClose$ = new Subject<any>();
this.dialogRef = this.factory.create(
ReflectiveInjector.resolveAndCreate([
{provide: YUP_DATA, useValue: config}, // 注入自定义数据
{provide: YupRef, useValue: this} // 注入自身,这样就可以在创建的组件里控制组件的关闭等
])
);
}
// 提供给外界的对窗口关闭的订阅
public afterClose(): Observable<any> {
return this.afterClose$.asObservable();
}
// 关闭方法,将销毁组件
public close(data?: any) {
this.afterClose$.next(data);
this.afterClose$.complete();
this.dialogRef.destroy();
}
// 提供给弹窗服务以帮助添加到DOM中
public componentRef() {
return this.dialogRef;
}
}
这样一来每次调用open方法后都能得到一个YupRef对象,提供了关闭方法以及对关闭事件的订阅方法。
预制弹窗组件
弹窗服务中的open方法需要两个参数,第二个是传入的自定义数据,第一个就是需要创建的组件了,现在我们创建出几个预制组件,以dialog.component为例:
import { Component, Injector } from '@angular/core';
import { YupRef, YUP_DATA } from '../popup.ref';
import { mask, dialog } from '../animations';
@Component({
template: `
<div class="yup-mask" [@mask]="disp" (click)="!data?.mask && close(false)"></div>
<div class="yup-body" [@dialog]="disp">
<div class="yup-body-head">{{data?.title || '消息'}}</div>
<div class="yup-body-content">{{data?.msg || ' '}}</div>
<div class="yup-body-btns">
<div class="btn default" (click)="close(false)">{{data?.no || '取消'}}</div>
<div class="btn primary" (click)="close(true)">{{data?.ok || '确认'}}</div>
</div>
</div>
`,
styles: [`这里省略一堆样式`]
animations: [mask, dialog]
})
export class DialogComponent {
public data: {
title?: string,
msg?: string,
ok?: string,
no?: string,
mask?: string
};
public dialogRef: YupRef<DialogComponent>;
public disp: string;
constructor(
private injector: Injector
) {
this.data = this.injector.get(YUP_DATA);
this.dialogRef = this.injector.get(YupRef);
this.disp = 'init';
setTimeout(() => {
this.disp = 'on';
});
}
public close(comfirm: boolean) {
this.disp = 'off';
setTimeout(() => {
this.disp = 'init';
this.dialogRef.close(comfirm);
}, 300);
}
}
用笔者这种方式创建的组件有两个尴尬的小问题:
- 不能使用隐式的依赖注入了,必须注入Injector服务来手动get到注入的两个依赖,即代码中的
this.injector.get(YUP_DATA) 和 this.injector.get(YupRef) 。 - 直接使用angular动画会失效,因为是暴力添加到DOM中的方式,必须手动setTimeout过等动画结束再真正销毁组件。
创建好组件后再服务中添加快捷创建此组件的方法:
public dialog(config: {
title?: string,
msg?: string,
ok?: string,
no?: string,
mask?: boolean
}) {
return this.open(DialogComponent, config);
}
额外需要提一点是虽然这样创建的组件没有被一开始就添加到页面中,仍然需要在所属模块的declaration中声明,并且还得在entryComponent中声明过,否则angular就会通过报错的方式让你这么做,就像下面这个弹窗模块的定义这样:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DialogComponent, AlertComponent, ToastComponent, LoadComponent } from './templates';
import { DialogService } from './service';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
@NgModule({
declarations: [DialogComponent, AlertComponent, ToastComponent, LoadComponent],
imports: [ NoopAnimationsModule, CommonModule ],
exports: [],
providers: [DialogService],
entryComponents: [DialogComponent, AlertComponent, ToastComponent, LoadComponent]
})
export class YupModule {}
而此弹窗模块真正需要导出的东西有4个,都列在index.ts中:
export { YupModule } from './module'; // 需要在AppModule中引入
export { DialogService as Yup } from './service'; // 用于发起弹窗
export { YupRef, YUP_DATA } from './popup.ref'; // 用于创建自定义弹窗时提供控制
使用方法
最终在外界的使用方式如下:
constructor(
public yup: Yup // 其实是DialogService,被笔者改了名
) { }
public ngOnInit() {
this.yup.dialog({msg: '弹不弹?', title: '我弹', ok: '弹弹', no: '别弹了', mask: true}).afterClose().subscribe((res) => {
if (res) {
console.log('点击了确定');
} else {
console.log('点击了取消');
}
});
}
当不想使用预制的弹窗组件时,大可以自行创建好一个组件,然后使用open方法:
this.yup.open(CustomComponent, '我是自定义数据').afterClose().subscribe((res) => {
console.log(`我已经被关闭了,不过我能携带出来数据: 【${res}】`);
});
乍一看是不是有点接近Material2的Dialog的使用呢
动态创建angular组件实现popup弹窗的更多相关文章
- vue通过extend动态创建全局组件(插件)学习小记
测试环境:nodejs+webpack,例子是看文章的,注释为自己的理解 创建一个toast.vue文件: <template> <div class="wrap" ...
- pyqt动态创建一系列组件并绑定信号和槽(网友提供学习)
# -*- coding: utf-8 -*- # python:2.x __author__ = 'Administrator' #如上图要求:创建指定多个复选框,一种是通过QT设计器Designe ...
- delphi怎么一次性动态删除(释放)数个动态创建的组件?
比如procedure TForm1.Button1Click(Sender: TObject);vari:Integer;lbl: TLabel;beginfor i:=1 to 3 dobegin ...
- Angular动态创建组件之Portals
这篇文章主要介绍使用Angular api 和 CDK Portals两种方式实现动态创建组件,另外还会讲一些跟它相关的知识点,如:Angular多级依赖注入.ViewContainerRef,Por ...
- Delphi 动态创建组件,单个创建、单个销毁
效果图如下: 实现部分代码如下: var rec: Integer = 0; //记录增行按钮点击次数 implementation {$R *.dfm} //动态释放单个组件内存,即销毁组件 pro ...
- OAF 动态创建组件以及动态绑定属性
在开发中,我们遇到以下一个需求. 一个表格左侧有5列是固定存在的,右侧有N列是动态生成的,并且该N列中第一列可输入,第二列是不可编辑的,但是是数字,如果小于0,那么就要显示为红色,重点标识出来. 首先 ...
- ViewContainerRef 动态创建视图
Angular DOM 操作 相关的APIs和类: 查询DOM节点 template variable ref: 模版变量引用,相当于react中的ref ViewChild: 查询DOM,返回单个元 ...
- Lightning框架示例 - 动态建立Lightning组件
动态建立Lightning组件 组件化前端开发是Lightning框架的优点之一.在进行Lightning应用开发时,我们可以将组件进行嵌套.引用,从而实现模块的封装和重用,提高开发效率. 组件的嵌套 ...
- Angular中懒加载一个模块并动态创建显示该模块下声明的组件
angular中支持可以通过路由来懒加载某些页面模块已达到减少首屏尺寸, 提高首屏加载速度的目的. 但是这种通过路由的方式有时候是无法满足需求的. 比如, 点击一个按钮后显示一行工具栏, 这个工具栏组 ...
随机推荐
- PocScan的搭建与使用
安装Docker, 然后下载镜像 $ sudo curl -sSL https://get.daocloud.io/docker | sh $ sudo systemctl start docker ...
- 华为服务器Linux在线做RAID方法
背景概述 最近维护大数据的一些主机,大概有3k+的数目,有很大一部分是华为的服务器,大部分是12块数据盘,单盘做RAID0来存放数据,但是通常硬件是不可靠的,磁盘损坏是常态, 然而磁盘损坏进行定位更换 ...
- git远程仓库之添加远程库
现在的情景是,你已经在本地创建了一个Git仓库后,又想在GitHub创建一个Git仓库,并且让这两个仓库进行远程同步,这样,GitHub上的仓库既可以作为备份,又可以让其他人通过该仓库来协作,真是一举 ...
- swift 获取文件大小
//获取文件大小 func getSize(url: URL)->UInt64 { var fileSize : UInt64 = 0 do { let attr = try FileManag ...
- hdu--1258--Sum It Up(Map水过)
Sum It Up Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total S ...
- NYOJ--102--次方求模(快速求幂取模)
次方求模 时间限制:1000 ms | 内存限制:65535 KB 难度:3 描述 求a的b次方对c取余的值 输入 第一行输入一个整数n表示测试数据的组数(n<100)每组测试只有一 ...
- hdu--2570--迷瘴
#include<iostream> #include<vector> #include<algorithm> using namespace std; int m ...
- HTML 文本格式化实例 超链接
HTML 文本格式化实例 1.文本格式化 <b>加粗文字</b> <strong>加重语气</strong> <big>dingyi< ...
- Slave_SQL_Runing:NO 复制出现问题的解决办法
--Slave_SQL_Runing:NO 复制出现问题的解决办法 -------------------------------------------------------2014/05/21 ...
- Redis和消息队列使用实战
消息队列是在乐视这边非常普遍使用的技术.在我们部门内部,不同的项目使用的消息队列实现也不一样.下面是支付系统的流转图(部门兄弟画的,借用一下): 从图中可以看到,里面用到了kafka消息队列.作用是做 ...