前言
Bootstrap有一个Angular的版本——ngx-bootstrap,里面是有提供Boostrap Modal的,但是我一直觉得这种Modal的形式不太好。因为Modal本来就是相对独立在页面之外的,如果要把Modal的代码也写到当前页面里的话其实反而破坏了页面本身的结构。所以我自己封装了一个Modal,写这篇博客记录一下封装的过程。
Modal Component
不管Modal的html部分写在什么地方,Component都是必需的。
1 2 3 4 5 6 7 8
| <div (click)="onContainerClicked($event)" class="modal fade" tabindex="-1" [ngClass]="{'in': visibleAnimate}" [ngStyle]="{'display': visible ? 'block' : 'none', 'opacity': visibleAnimate ? 1 : 0}"> <div class="modal-dialog"> <div class="modal-content"> <ng-template modal-host></ng-template> </div> </div> </div>
|
template的部分非常简单。因为我希望Modal的主体是可以自定义的,所以就在里面使用了ng-template
标签。
1 2 3 4 5 6
| @Directive({ selector: '[modal-host]', }) export class ModalDirective { constructor(public viewContainerRef: ViewContainerRef) { } }
|
modal-host对应的就是这段代码。
接下来就是Modal Component的主体部分了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| export class ModalComponent{
public visible = false; public visibleAnimate = false; @ViewChild(ModalDirective) modalHost:ModalDirective;
constructor(private componentFactoryResolver: ComponentFactoryResolver,private modal:ModalService){ this.modal.show.subscribe(value => { this.show(value); }) }
public show(item:ShowItem): void { let componentFactory = this.componentFactoryResolver.resolveComponentFactory(item.component); let viewContainerRef = this.modalHost.viewContainerRef; viewContainerRef.clear(); let componentRef = viewContainerRef.createComponent(componentFactory); (<ModalValue>componentRef.instance).callback = item.callback; (<ModalValue>componentRef.instance).params = item.params; let modal = this; (<ModalValue>componentRef.instance).close = ()=>{modal.hide()}; (<ModalValue>componentRef.instance).onInit(); this.visible = true; setTimeout(() => this.visibleAnimate = true, 100); }
public hide(): void { this.visibleAnimate = false; setTimeout(() => this.visible = false, 300); }
public onContainerClicked(event: MouseEvent): void { if ((<HTMLElement>event.target).classList.contains('modal')) { this.hide(); } } }
export interface ModalValue { callback:(any)=>void; close:()=>void; params:any; onInit(); }
export class ShowItem{ component:Type<ModalValue>; callback:(any)=>void; params:any; constructor(item:Type<any>,params:any,callback:(any)=>void){ this.component = item; this.callback = callback; this.params = params; } }
|
关键部分在show
方法里。当从Modal Service(后面会写)里拿到item
之后就会调用show
方法。首先是拿到新的component,清除之前的component,接着把params
,callback
注入到component中,调用onInit
方法。最后显示Modal。
Component写完之后需要在app.component.html里给它腾个位置,不然没地方显示。
Modal Service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Injectable({ providedIn: 'root' }) export class ModalService { show: Observable<ShowItem>; private showIn: Subject<ShowItem>; constructor() { this.showIn = new Subject<ShowItem>(); this.show = this.showIn.asObservable(); }
modal(component:Type<any>,params:any):Promise<any>{ return new Promise((resolve) => { this.showIn.next(new ShowItem(component, params, resolve)); }) } }
|
Modal Service部分就非常简单了。创建一个Obervable
给Modal Component订阅。每当其他地方调用modal
方法的时候就会把component类型和参数发布出去。
使用举例
以一个非常简单的登出提示框为例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| @Component({ template: ` <div class="modal-header"> 登出确认 </div> <div class="modal-body"> <p>是否要退出当前账号</p> </div> <div class="modal-footer"> <button class="btn btn-primary" (click)="finish()">确定</button> </div> ` }) export class LogoutEnsure implements ModalValue { @Input() callback: (any) => void; @Input() close: () => void; @Input() params: any;
onInit() { }
finish() { this.callback({}); this.close(); } } .... logout() { this.modal.modal(LogoutEnsure, {}).then(() => { this.api.getLogout().subscribe(()=>{ this.router.navigate(['/']); }); }); } ....
|
Component只要继承ModalValue
就可以在Modal中显示出来。调用modal的时候只要传Component类型即可。
小结
自己实现一个Modal还是非常简单的。这样可以避免像ngx-bootstrap一样dom节点过深,而且代码结构也更加美观。