承接上文,本文将从一个基本的angular启动项目开始搭建一个具有基本功能、较通用、低耦合、可扩展的popup弹窗(脸红),主要分为以下几步:

  1. 基本项目结构搭建
  2. 弹窗服务
  3. 弹窗的引用对象
  4. 准备作为模板的弹窗组件
  5. 使用方法

基本项目结构

因为打算将我们的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弹窗的更多相关文章

  1. vue通过extend动态创建全局组件(插件)学习小记

    测试环境:nodejs+webpack,例子是看文章的,注释为自己的理解 创建一个toast.vue文件: <template> <div class="wrap" ...

  2. pyqt动态创建一系列组件并绑定信号和槽(网友提供学习)

    # -*- coding: utf-8 -*- # python:2.x __author__ = 'Administrator' #如上图要求:创建指定多个复选框,一种是通过QT设计器Designe ...

  3. delphi怎么一次性动态删除(释放)数个动态创建的组件?

    比如procedure TForm1.Button1Click(Sender: TObject);vari:Integer;lbl: TLabel;beginfor i:=1 to 3 dobegin ...

  4. Angular动态创建组件之Portals

    这篇文章主要介绍使用Angular api 和 CDK Portals两种方式实现动态创建组件,另外还会讲一些跟它相关的知识点,如:Angular多级依赖注入.ViewContainerRef,Por ...

  5. Delphi 动态创建组件,单个创建、单个销毁

    效果图如下: 实现部分代码如下: var rec: Integer = 0; //记录增行按钮点击次数 implementation {$R *.dfm} //动态释放单个组件内存,即销毁组件 pro ...

  6. OAF 动态创建组件以及动态绑定属性

    在开发中,我们遇到以下一个需求. 一个表格左侧有5列是固定存在的,右侧有N列是动态生成的,并且该N列中第一列可输入,第二列是不可编辑的,但是是数字,如果小于0,那么就要显示为红色,重点标识出来. 首先 ...

  7. ViewContainerRef 动态创建视图

    Angular DOM 操作 相关的APIs和类: 查询DOM节点 template variable ref: 模版变量引用,相当于react中的ref ViewChild: 查询DOM,返回单个元 ...

  8. Lightning框架示例 - 动态建立Lightning组件

    动态建立Lightning组件 组件化前端开发是Lightning框架的优点之一.在进行Lightning应用开发时,我们可以将组件进行嵌套.引用,从而实现模块的封装和重用,提高开发效率. 组件的嵌套 ...

  9. Angular中懒加载一个模块并动态创建显示该模块下声明的组件

    angular中支持可以通过路由来懒加载某些页面模块已达到减少首屏尺寸, 提高首屏加载速度的目的. 但是这种通过路由的方式有时候是无法满足需求的. 比如, 点击一个按钮后显示一行工具栏, 这个工具栏组 ...

随机推荐

  1. HDU 2202 最大三角形(凸包)

    Problem Description 老师在计算几何这门课上给Eddy布置了一道题目,题目是这样的:给定二维的平面上n个不同的点,要求在这些点里寻找三个点,使他们构成的三角形拥有的面积最大.Eddy ...

  2. POJ 2479 Maximum sum 解题报告

    Maximum sum Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 40596   Accepted: 12663 Des ...

  3. Qt词典搜索

    Qt词典搜索 采用阿凡达数据-API数据接口及爱词霸API数据接口实现词典搜索功能,实例字符串搜索接口分别为:中文词组采用“词典”,中文单个字采用“中华字典”,英文或其他字符采用“爱词霸”: 对应的A ...

  4. UI设计|PS软件操作应用——GIF动图

      前  言 JRedu 在之前的分享中,跟大家主要讲解了PS软件工具的基本操作,对主要的图层.蒙版.通道和滤镜都有一些介绍,希望对大家有所帮助,在介绍这些工具时也提到过GIF,而在本次分享中就跟大家 ...

  5. 百度将与W3C中国召开MIP技术研讨会

    百度计划与W3C中国共同组织国内W3C会员,于8月30日召开MIP 技术研讨会,讨论 MIP 等技术相关的应用标准,以期推进 MIP/AMP 在W3C中国的标准化进程. MIP (Mobile Ins ...

  6. node.js之require

    1.require.resolve('./testModeule.js')在REPL运行环境下输入,可以查询到当前目录下textModeule.js模块文件的完整文件名 2.require.cache ...

  7. 如何用QUnit为JS代码做单元测试

    非常好的入门文章: http://www.zhangxinxu.com/wordpress/2013/04/qunit-javascript-unit-test-%E5%8D%95%E5%85%83% ...

  8. python开源项目及示例代码(转)

    本页面是俺收集的各种 Python 资源,不定期更新. 下面列出的各种 Python 库/模块/工具,如果名称带超链接,说明是第三方的:否则是 Python 语言内置的. 1 算法 1.1 字符串处理 ...

  9. 80C51学习 闪烁灯

    //引入头文件 #include <reg52.h> typedef unsigned char u8; typedef unsigned int u16; //位定义 sbit LED= ...

  10. dotnet使用Selenium执行自动化任务

    如果要做百度文库,百度贴吧,百度知道签到,你,会怎么做?前不久我还会觉得这好像太麻烦了,now,soeasy. 自动化测试工具:Selenium Selenium是一个用于Web应用程序测试的工具.S ...