承接上文,本文将从一个基本的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. 12. leetcode 455.Assign Cookies

    Assume you are an awesome parent and want to give your children some cookies. But, you should give e ...

  2. 如何关闭浏览器的HSTS功能

    在安装配置 SSL 证书时,可以使用一种能使数据传输更加安全的Web安全协议,即在服务器端上开启 HSTS (HTTP Strict Transport Security).它告诉浏览器只能通过HTT ...

  3. ios扫雷

    就这些代码敲了我两个小时...... //  ViewController.m //  扫雷 // //  Created by 晚起的蚂蚁 on 2017/3/22. //  Copyright © ...

  4. QueueAPI记录

    队列是一种数据结构.它有两个基本操作:在队列尾部加人一个元素,和从队列头部移除一个元素就是说,队列以一种先进先出的方式管理数据,如果你试图向一个 已经满了的阻塞队列中添加一个元素或者是从一个空的阻塞队 ...

  5. 【NO.5】jmeter-结果文件

    Jmeter的结果文件可以保存很多内容,你需要看哪个就勾选哪个,很简单是吧. 结果文件可以保存为2种形式:XML或者CSV.我印象里在书上提到过,如果保存为XML形式的结果文件,后续可以转化为表格便于 ...

  6. FPS手游如何脱颖而出?看《CF手游》的性能突破之路

    WeTest导读 俗话说:用户体验不谈性能就是耍流氓. 在PC游戏上的性能问题并没有那么明显, 加个内存换个CPU或者刷个主频就能轻松搞定:到了手游时代后情况则显得比较严峻,捉襟见肘的内存使得资源加载 ...

  7. MySQL锁与MVCC

    --MySQL锁与MVCC --------------------2014/06/29 myisam表锁比较简单,这里主要讨论一下innodb的锁相关问题. innodb相比oracle锁机制简单许 ...

  8. 基于FPGA的RGB565_YCbCr_Gray算法实现

    前面我们讲了基于FPGA用VGA显示一副静态图片,那么接下来我们就接着前面的工程来实现我们图像处理的基础算法里最简单的一个那就是彩色图像转灰度的实现. 将彩色图像转化为灰度的方法有两种,一个是令RGB ...

  9. php基础。php与js的不同

    1 . PHP拼字符串用的是点. js用+号. 2.  php文件要放在wamp文件里面的www里面. 3.  php与js的嵌入方式相同,只是嵌入的标记不一样. 4.  php输出语法用echo.可 ...

  10. log4j配置文件,用时导入jar包buildPAthena、

    log4j.rootLogger=debug,CONSOLE,file#log4j.rootLogger=ERROR,ROLLING_FILElog4j.logger.cn.smbms=debuglo ...