今天我們要閱讀的是 Message
元件,我們在之前介紹過,NG-ZORRO 的很多包含彈出層的元件均結合了 Material CDK Overlay 實現,我們就以這個簡單元件來看看如何使用 cdk overlay
來建立自定義元件。
import { Component } from '@angular/core';
import { NzMessageService } from 'ng-zorro-antd/message';
@Component({
selector: 'nz-demo-message-info',
template: `
<button nz-button [nzType]="'primary'" (click)="createBasicMessage()">Display normal message</button>
`
})
export class NzDemoMessageInfoComponent {
constructor(private message: NzMessageService) {}
createBasicMessage(): void {
this.message.info('This is a normal message');
}
}
點選按鈕 會在全域性螢幕上彈出一個 message
:
我們之前介紹過,cdk overlay
可以通過簡單的方法建立一個浮層:
constructor(private overlay: Overlay) {
this.createOverlay();
}
...
const overlayRef = overlay.create({
height: '400px',
width: '600px',
});
const userProfilePortal = new ComponentPortal(UserProfile); // 預設模板
overlayRef.attach(userProfilePortal);
這樣,cdk 會幫助我們建立一個 400 x 600
的浮層,那麼 Message
元件是不是這樣實現的呢?
我們先看一下 nz-message.service.ts 檔案:
export class NzMessageService extends NzMessageBaseService<...> {
...
// Shortcut methods
success(content: string | TemplateRef<void>, options?: NzMessageDataOptions): NzMessageDataFilled {
return this.createMessage({ type: 'success', content }, options);
}
create(
type: 'success' | 'info' | 'warning' | 'error' | 'loading' | string,
content: string | TemplateRef<void>,
options?: NzMessageDataOptions
): NzMessageDataFilled {
return this.createMessage({ type, content }, options);
}
}
這個檔案繼承 NzMessageBaseService
(nz-message-base.service.ts),方法均呼叫 createMessage
方法,那麼我們繼續追溯,找到了它的實現方法:
export class NzMessageBaseService<
ContainerClass extends NzMessageContainerComponent,
...
> {
protected _container: ContainerClass;
constructor(...) {
this._container = this.withContainer();
}
// Manually creating container for overlay to avoid multi-checking error, see: https://github.com/NG-ZORRO/ng-zorro-antd/issues/391
// NOTE: we never clean up the container component and it's overlay resources, if we should, we need to do it by our own codes.
private withContainer(): ContainerClass {
const containerInstance = this.nzSingletonService.getSingletonWithKey(this.name);
if (containerInstance) {
return containerInstance as ContainerClass;
}
// 關注一下這裡
const factory = this.cfr.resolveComponentFactory(this.containerClass);
const componentRef = factory.create(this.injector); // Use root injector
componentRef.changeDetectorRef.detectChanges(); // Immediately change detection to avoid multi-checking error
this.appRef.attachView(componentRef.hostView); // Load view into app root
const overlayPane = this.overlay.create().overlayElement;
overlayPane.style.zIndex = '1010'; // Patching: assign the same zIndex of ant-message to it's parent overlay panel, to the ant-message's zindex work.
overlayPane.appendChild((componentRef.hostView as EmbeddedViewRef<{}>).rootNodes[0] as HTMLElement);
return componentRef.instance;
}
}
看一下 create
部分,可以看到它建立了一個 overlay
浮層,然後返回 ComponentRef
(NzMessageContainerComponent) 例項。
它也是通過掛載到 root view
來渲染的,這樣彈出層預設 append
到頁面底部建立一個新的 div container
:
所以如果我們要修改 message
樣式需要到 styles.less
檔案中去修改,不能再呼叫其服務的元件裡修改。如果遇到使用問題沒辦法解決也可以通過這種回溯方式,嘗試檢視是否是元件的 Bug,從而選擇 提 Issue
或者 提 PR
去修復它。
還有其他元件諸如 Modal
、Drawer
等元件,彈出層模式基本都是這種思路,通過 overlay.create()
建立浮層,當然如果你需要在關閉浮層時返回一些資料,可以去監聽 OverlayRef.backdropClick 事件,參考我們之前介紹的 Select 元件(31行)。
我們今天僅以一個典型的浮層元件,介紹了它是如何工作的,更復雜的如 Modal
元件包含了更多響應事件,大家可以嘗試參考去實現,甚至可以按照這種方式搭建自己的浮層元件,結合 cdk 拖放 功能也可以實現可拖拽調整位置的浮層,就像 webstorm
等開發工具的搜尋結果框一樣。