Angular5中基于NgbActiveModal的可复用模态框动态内容适配问题
优化Angular5可复用NgbActiveModal组件的内容/样式切换方案
嘿,我来给你几个更优雅的方案解决这个问题——你现在用一堆*ngIf的临时写法虽然能跑,但以后需求变多的话,代码肯定会越来越臃肿,维护起来贼头疼。下面这几个思路能让你的模态框复用性更强,代码也更清爽:
方案1:用内容投影(Content Projection)彻底解放内容控制权
这绝对是Angular做组件复用最常用的技巧之一,简单来说就是让父组件直接把模态框里要展示的内容传进来,你的通用模态框只负责处理弹窗的基础逻辑(比如关闭、标题、底部按钮这些固定部分)。
具体怎么改:
- 先修改你的可复用模态框模板,把原本写死的内容区域换成
<ng-content></ng-content>:
<div class="modal-header"> <h4 class="modal-title">{{ data.title }}</h4> <button type="button" class="close" aria-label="Close" (click)="activeModal.dismiss('Cross click')"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> <!-- 这里就是内容投影的入口,父组件传啥就显示啥 --> <ng-content></ng-content> </div> <div class="modal-footer"> <button type="button" class="btn btn-outline-dark" (click)="activeModal.close('Close click')">Close</button> </div>
- 然后父组件调用的时候,直接在模态框标签内部写你要的内容——不管是列表、表格甚至自定义组件都可以:
<!-- 父组件里调用模态框,直接把表格写进去 --> <app-reusable-modal #modalRef [data]="{ title: '用户列表' }"> <table class="table table-striped"> <thead> <tr> <th>ID</th> <th>用户名</th> </tr> </thead> <tbody> <tr *ngFor="let user of userList"> <td>{{ user.id }}</td> <td>{{ user.name }}</td> </tr> </tbody> </table> </app-reusable-modal>
这种方式最灵活,父组件完全掌控内容和样式,通用模态框只做“壳子”,扩展性拉满。
方案2:通过TemplateRef传递自定义模板
如果不想把内容直接写在调用处,或者有些模板需要在多个地方复用,那就用TemplateRef让父组件把预先定义好的模板传给模态框。
实现步骤:
- 先在通用模态框组件里加个输入属性,用来接收父组件传的模板:
import { Component, Input, TemplateRef } from '@angular/core'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; @Component({ selector: 'app-reusable-modal', templateUrl: './reusable-modal.component.html' }) export class ReusableModalComponent { @Input() contentTemplate: TemplateRef<any>; // 接收父组件的模板 @Input() data: any; constructor(public activeModal: NgbActiveModal) {} }
- 模态框模板里用
<ng-container [ngTemplateOutlet]="contentTemplate"></ng-container>来渲染这个模板:
<div class="modal-body"> <ng-container [ngTemplateOutlet]="contentTemplate"></ng-container> </div>
- 父组件里先定义模板,再传递给模态框:
<!-- 父组件里预先定义好表格模板 --> <ng-template #userTableTemplate> <table class="table table-bordered"> <thead> <tr> <th>ID</th> <th>用户名</th> </tr> </thead> <tbody> <tr *ngFor="let user of userList"> <td>{{ user.id }}</td> <td>{{ user.name }}</td> </tr> </tbody> </table> </ng-template> <!-- 按钮触发打开模态框,把模板传进去 --> <button class="btn btn-primary" (click)="openModal(userTableTemplate)">打开用户表格</button>
import { TemplateRef } from '@angular/core'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { ReusableModalComponent } from './reusable-modal.component'; export class ParentComponent { userList = [{ id: 1, name: '张三' }, { id: 2, name: '李四' }]; constructor(private modalService: NgbModal) {} openModal(template: TemplateRef<any>) { const modalRef = this.modalService.open(ReusableModalComponent); modalRef.componentInstance.contentTemplate = template; modalRef.componentInstance.data = { title: '用户表格' }; } }
方案3:用枚举+ngSwitch切换预设模板
如果你的模态框内容类型是固定的几种(比如列表、表格、纯文本),那可以定义一个枚举类型,在模态框内部根据传入的类型自动渲染对应的预设模板,比堆一堆*ngIf干净多了。
具体操作:
- 先定义一个枚举,列出所有支持的内容类型:
// 可以单独放在一个文件里,或者和模态框组件放一起 export enum ModalContentType { LIST = 'list', TABLE = 'table', TEXT = 'text' }
- 模态框组件里加两个输入属性:一个是内容类型,一个是对应的数据:
import { Component, Input } from '@angular/core'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { ModalContentType } from './modal-content-type.enum'; @Component({ selector: 'app-reusable-modal', templateUrl: './reusable-modal.component.html' }) export class ReusableModalComponent { @Input() contentType: ModalContentType; @Input() contentData: any; // 根据类型不同,这里可以是数组、对象或者字符串 readonly ModalContentType = ModalContentType; // 让模板能访问枚举 constructor(public activeModal: NgbActiveModal) {} }
- 模板里用
[ngSwitch]替代多个*ngIf:
<div class="modal-body" [ngSwitch]="contentType"> <!-- 列表类型 --> <ul *ngSwitchCase="ModalContentType.LIST" class="list-group"> <li *ngFor="let item of contentData" class="list-group-item">{{ item }}</li> </ul> <!-- 表格类型 --> <table *ngSwitchCase="ModalContentType.TABLE" class="table table-hover"> <thead> <tr> <th *ngFor="let header of contentData.headers">{{ header }}</th> </tr> </thead> <tbody> <tr *ngFor="let row of contentData.rows"> <td *ngFor="let cell of row">{{ cell }}</td> </tr> </tbody> </table> <!-- 默认显示纯文本 --> <p *ngSwitchDefault class="text-muted">{{ contentData }}</p> </div>
- 父组件调用时指定类型和对应的数据:
openTableModal() { const modalRef = this.modalService.open(ReusableModalComponent); modalRef.componentInstance.contentType = ModalContentType.TABLE; modalRef.componentInstance.contentData = { headers: ['ID', '用户名'], rows: [[1, '张三'], [2, '李四']] }; modalRef.componentInstance.data = { title: '用户表格' }; }
这几个方案各有侧重,你可以根据自己的需求选:如果内容差异特别大,优先选内容投影;如果有固定的几种内容类型,用枚举+ngSwitch最整洁;如果需要复用某些模板,就用TemplateRef的方式。
内容的提问来源于stack exchange,提问作者Tim
相关产品推荐
相关产品推荐

