记一个angular在路由配置中管理 Angular Material Dialog(实现动态组件的弹窗显示)

背景描述

目标

我们的目标正如标题所言:在路由配置中管理 Angular Material Dialog,从而更简便(代码量更少,可复用性,可拓展性、可维护性更强)地实现动态组件的弹窗显示。不难看出,我们的目标由两个部分组成,动态组件和弹窗。其实二者的单独实现都并不困难,angular官网所推荐的众多UI库中都有二者对应的demo。但是,要将二者健壮地结合起来并不是一件容易的事情。

我曾经尝试过两种方案:一是bootstrap->model实现弹窗+Material->Cdk->Portal实现动态组件;二是Material->Dialog实现弹窗+向openDialog()方法中传参数以控制要加载的组件实现动态组件。对两种方案经过尝试后会发现,两个方案都有问题且大同小异:对使用的组件(C层、V层)代码侵入过多,且部分代码需要多次复写,这对于开发而言并不是好的现象。

后来经过老师引导,最终发现以路由配置去管理Material Dialog实现动态组件弹窗显示的方案更为合适。这就是我们下面所要研究的。

Material Dialog使用方法

首先我们需要对Material->Dialog基础的使用方法有一定的了解,具体详情请参考官方文档:
https://material.angular.io/components/dialog/overview,在此就不过多赘述。

如何实现以路由配置去管理

实现思路

首先我们要理解好官方提供的dialog的经典用法,经观察和研究后我们可以看出它的大体运作流程,如下图:
image.png
此流程中出现上述问题的地方主要集中在第三步骤。由于openDialog()方法是直接写在组件C层中的,这就导致代码入侵这个问题无法避免。而倘若以向方法中传参数来控制要加载的组件实现动态组件的方式,这无疑会让本就侵入的代码量变得更多了,而且还有部分代码需要多次复写;最最让人受不了的还是:每个想要这样用的组件,都得来上这么一套,实在折磨。所以现在问题进一步转化成了:如何在Material->Dialog经典用法的基础上,将原本需要写在组件C层中的openDialog()方法抽离出来,变得可复用,易维护。

经老师指导和上网查阅资料,最终以路由配置去管理的方式实现了我们想要的效果,再回过头来总结它具体的实现思路:V层button不再直接绑定openDialog()方法,而是绑定路由(routerLink)且要加上路由内容输出语句(<router-outlet></router-outlet>);在routing.module文件中给所绑定子路由的component设置为DialogEntryComponent(翻译过来为弹窗入口组件,没错这是我们新建的dialog-entry.component.ts文件,这时候需要将openDialog()方法及其他相关方法从原组件C层中挪动到这里面来,这样原组件C层将不会出现代码侵入的问题);然后在routing.module文件中给所绑定路由的data中设置component:你想要弹出组件。具体流程如下图:
image.png
这样做,将实现弹窗的效果的openDialog()方法及其他相关方法抽离到DialogEntryComponent中;将组件的动态渲染交由路由文件中向DialogEntryComponent传入目标组件来完成。完美解决了上文中提到的一系列问题。

代码实现

文件目录准备:1.Term目录下需要有term-index组件,term-add组件,term-edit组件,每个组件中都有C层、V层、CSS文件、测试文件;还需要有term.module.ts、term-routing.module.ts文件。2.dialog-entry目录下需要有dialog-entry.component.ts、dialog-entry.module.ts文件。

term-index的C层什么都不需要

term-index的V层

<!--基于Material Dialogs的弹窗显示动态组件demo-->
<button mat-raised-button routerLink="add">add</button>
<button mat-raised-button routerLink="edit/3">edit</button>
<router-outlet></router-outlet>

term-add的C层

export class TermAddComponent implements OnInit {
  constructor(
    public dialogRef: MatDialogRef<TermAddComponent>,
  ) {}
  ngOnInit(): void {
  }
  onNoClick(): void {
    this.dialogRef.close();
  }
  onOkClick(): void {
    this.dialogRef.close();
  }
}

term-add的V层

<h1 mat-dialog-title>Hi! term-add works</h1>
<div mat-dialog-actions>
  <button mat-button (click)="onNoClick()">No Thanks</button>
  <button mat-button (click)="onOkClick()" cdkFocusInitial>Ok</button>
</div>

term-edit的C层与term-add的基本相同

只需将构造函数中MatDialogRef<>填入自己的组件即可。

term-edit的V层与term-add的基本相同

只需将h1标签中填入可辨识此组件的内容即可。

term-routing.module.ts

import { NgModule } from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {TermIndexComponent} from './term-index/term-index.component';
import {TermAddComponent} from './term-add/term-add.component';
import {TermEditComponent} from './term-edit/term-edit.component';
import {DialogEntryComponent} from '../../common/dialog-entry/dialog-entry.component';

const routes: Routes = [
  {
    path: '',
    component: TermIndexComponent,
    children: [
      {
        path: 'add',
        component: DialogEntryComponent,
        data: {
          component: TermAddComponent
        }
      },
      {
        path: 'edit/:TermId',
        component: DialogEntryComponent,
        data: {
          component: TermEditComponent
        }
      }
    ]
  }
];

@NgModule({
  imports: [
    RouterModule.forChild(routes)
  ],
  exports: [RouterModule]
})
export class TermRoutingModule { }

dialog-entry.component.ts

import {Component} from '@angular/core';
import {MatDialog} from '@angular/material/dialog';
import {ActivatedRoute, Router} from '@angular/router';

@Component({
  template: ''
})
export class DialogEntryComponent {
  url: string | undefined;
  constructor(public dialog: MatDialog,
              private router: Router,
              private route: ActivatedRoute) {
    this.url = this.route.snapshot.url[0].path;
    this.openDialog();
  }
  openDialog(): void {
    const dialogRef = this.dialog.open(this.route.snapshot.data.component, {
      width: '250px'
    });
    const relativeBackUrl = this.getRelativeBackUrl();
    dialogRef.afterClosed().subscribe(result => {
      this.router.navigate([relativeBackUrl], { relativeTo: this.route });
    });
  }

  private getRelativeBackUrl(): string {
    if (this.url === 'add') {
      return '../';
    } else if (this.url === 'edit') {
      return '../../';
    } else {
      return '';
    }
  }
}

dialog-entry.module.ts

import {NgModule} from '@angular/core';
import {MatDialogModule} from '@angular/material/dialog';
import {DialogEntryComponent} from './dialog-entry.component';

@NgModule({
  declarations: [
    DialogEntryComponent
  ],
  imports: [
    MatDialogModule
  ],
})
export class DialogEntryModule {
}

最后在使用时需要记得在模块中引入DialogEntryModule。此demo为term模块则有

term.module.ts

@NgModule({
  declarations: [TermIndexComponent, TermAddComponent, TermEditComponent],
  imports: [
    CommonModule,
    TermRoutingModule,
    ReactiveFormsModule,
    MatButtonModule,
    DialogEntryModule,
    MatDialogModule
  ],
  providers: [
    { provide: MatDialogRef, useValue: {} },
  ],
})

总结

至此,我们的目标终于达成了,之后其他模块想要复用的话,只需要做以下3项工作即可:
1.在module文件中importers我们的DialogEntryModule;
2.给主组件的V层button中绑定转跳的路由信息;
3.在routing.module文件中设置对应路由所要转跳的组件。

众所周知,第2第3步工作就算不使用动态组件弹窗也是无法避免的。而像本文这般配置好后,之后其他模块再想要复用时,多出的工作内容只有第1步和第3步中多写几行简单的代码而已。这就是我们想要的,不用造重复的轮子,以及可以在复用起来轻松而简便。

效果预览

4.gif

参考文章

https://medium.com/ngconf/routing-to-angular-material-dialogs...

河北工业大学

12 声望
10 粉丝
0 条评论
推荐阅读
「多图预警」完美实现一个@功能
一天产品大大向 boss 汇报完研发成果和产品业绩产出,若有所思的走出来,劲直向我走过来,嘴角微微上扬。产品大大:boss 对我们的研发成果挺满意的,balabala...(内心 OS:不听,讲重点)产品大大:咱们的客服 I...

wuwhs40阅读 4.7k评论 5

封面图
涨姿势了,有意思的气泡 Loading 效果
今日,群友提问,如何实现这么一个 Loading 效果:这个确实有点意思,但是这是 CSS 能够完成的?没错,这个效果中的核心气泡效果,其实借助 CSS 中的滤镜,能够比较轻松的实现,就是所需的元素可能多点。参考我们...

chokcoco20阅读 2.1k评论 2

在前端使用 JS 进行分类汇总
最近遇到一些同学在问 JS 中进行数据统计的问题。虽然数据统计一般会在数据库中进行,但是后端遇到需要使用程序来进行统计的情况也非常多。.NET 就为了对内存数据和数据库数据进行统一地数据处理,发明了 LINQ (L...

边城17阅读 1.9k

封面图
你可能不需要JS!CSS实现一个计时器
CSS现在可不仅仅只是改一个颜色这么简单,还可以做很多交互,比如做一个功能齐全的计时器?样式上并不复杂,主要是几个交互的地方数字时钟的变化开始、暂停操作重置操作如何仅使用 CSS 来实现这样的功能呢?一起...

XboxYan21阅读 1.6k评论 1

封面图
「彻底弄懂」this全面解析
当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在 哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this就是记录的其中一个属性,会在 函数执行的过程中用到...

wuwhs17阅读 2.4k

封面图
学会这些 Web API 使你的开发效率翻倍
随着浏览器的日益壮大,浏览器自带的功能也随着增多,在 Web 开发过程中,我们经常会使用一些 Web API 增加我们的开发效率。本篇文章主要选取了一些有趣且有用的 Web API 进行介绍,并且 API 可以在线运行预览。C...

九旬13阅读 1.5k

封面图
用了那么久的 SVG,你还没有入门吗?
其实在大部分的项目中都有 直接 或 间接 使用到 SVG 和 Canvas,但是在大多数时候我们只是选择 简单了解 或 直接跳过,这有问题吗?没有问题,毕竟砖还是要搬的!

熊的猫17阅读 1.5k评论 2

封面图

河北工业大学

12 声望
10 粉丝
宣传栏