angular/material2中dialog模塊的示例分析

這篇文章主要介紹angular/material2中dialog模塊的示例分析,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!

成都創(chuàng)新互聯(lián)專(zhuān)注于霍爾果斯網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠(chéng)為您提供霍爾果斯?fàn)I銷(xiāo)型網(wǎng)站建設(shè),霍爾果斯網(wǎng)站制作、霍爾果斯網(wǎng)頁(yè)設(shè)計(jì)、霍爾果斯網(wǎng)站官網(wǎng)定制、小程序開(kāi)發(fā)服務(wù),打造霍爾果斯網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供霍爾果斯網(wǎng)站排名全網(wǎng)營(yíng)銷(xiāo)落地服務(wù)。

使用方法

  1. 引入彈窗模塊

  2. 自己準(zhǔn)備作為模板的彈窗內(nèi)容組件

  3. 在需要使用的組件內(nèi)注入 MatDialog 服務(wù)

  4. 調(diào)用 open 方法創(chuàng)建彈窗,并支持傳入配置、數(shù)據(jù),以及對(duì)關(guān)閉事件的訂閱

深入源碼

進(jìn)入material2的源碼,先從 MatDialog 的代碼入手,找到這個(gè) open 方法:

open<T>(
 componentOrTemplateRef: ComponentType<T> | TemplateRef<T>,
 config?: MatDialogConfig
): MatDialogRef<T> {
 // 防止重復(fù)打開(kāi)
 const inProgressDialog = this.openDialogs.find(dialog => dialog._isAnimating());
 if (inProgressDialog) {
  return inProgressDialog;
 }
 // 組合配置
 config = _applyConfigDefaults(config);
 // 防止id沖突
 if (config.id && this.getDialogById(config.id)) {
  throw Error(`Dialog with id "${config.id}" exists already. The dialog id must be unique.`);
 }
 // 第一步:創(chuàng)建彈出層
 const overlayRef = this._createOverlay(config);
 // 第二步:在彈出層上添加彈窗容器
 const dialogContainer = this._attachDialogContainer(overlayRef, config);
 // 第三步:把傳入的組件添加到創(chuàng)建的彈出層中創(chuàng)建的彈窗容器中
 const dialogRef = this._attachDialogContent(componentOrTemplateRef, dialogContainer, overlayRef, config);
 // 首次彈窗要添加鍵盤(pán)監(jiān)聽(tīng)
 if (!this.openDialogs.length) {
  document.addEventListener('keydown', this._boundKeydown);
 }
 // 添加進(jìn)隊(duì)列
 this.openDialogs.push(dialogRef);
 // 默認(rèn)添加一個(gè)關(guān)閉的訂閱 關(guān)閉時(shí)要移除此彈窗
 // 當(dāng)是最后一個(gè)彈窗時(shí)觸發(fā)全部關(guān)閉的訂閱并移除鍵盤(pán)監(jiān)聽(tīng)
 dialogRef.afterClosed().subscribe(() => this._removeOpenDialog(dialogRef));
 // 觸發(fā)打開(kāi)的訂閱
 this.afterOpen.next(dialogRef);
 return dialogRef;
}

總體看來(lái)彈窗的發(fā)起分為三部曲:

  1. 創(chuàng)建一個(gè)彈出層(其實(shí)是一個(gè)原生DOM,起宿主和入口的作用)

  2. 在彈出層上創(chuàng)建彈窗容器組件(負(fù)責(zé)提供遮罩和彈出動(dòng)畫(huà))

  3. 在彈窗容器中創(chuàng)建傳入的彈窗內(nèi)容組件(負(fù)責(zé)提供內(nèi)容)

彈出層的創(chuàng)建

對(duì)于其他組件,僅僅封裝模板以及內(nèi)部實(shí)現(xiàn)就足夠了,最多還要增加與父組件的數(shù)據(jù)、事件交互,所有這些事情,單使用angular Component就足夠?qū)崿F(xiàn)了,在何處使用就將組件選擇器放到哪里去完事。

但對(duì)于彈窗組件,事先并不知道會(huì)在何處使用,因此不適合實(shí)現(xiàn)為一個(gè)組件后通過(guò)選擇器安放到頁(yè)面的某處,而應(yīng)該將其作為彈窗插座放置到全局,并通過(guò)服務(wù)來(lái)調(diào)用。

material2也要面臨這個(gè)問(wèn)題,這個(gè)彈窗插座是避免不了的,那就在內(nèi)部實(shí)現(xiàn)它,在實(shí)際調(diào)用彈窗方法時(shí)動(dòng)態(tài)創(chuàng)建這個(gè)插座就可以了。要實(shí)現(xiàn)效果是:對(duì)用戶(hù)來(lái)說(shuō)只是在單純調(diào)用一個(gè) open 方法,由material2內(nèi)部來(lái)創(chuàng)建一個(gè)彈出層,并在這個(gè)彈出層上創(chuàng)建彈窗。

找到彈出層的創(chuàng)建代碼如下:

create(config: OverlayConfig = defaultConfig): OverlayRef {
 const pane = this._createPaneElement(); // 彈出層DOM 將被添加到宿主DOM中
 const portalHost = this._createPortalHost(pane); // 宿主DOM 將被添加到<body>末端
 return new OverlayRef(portalHost, pane, config, this._ngZone); // 彈出層的引用
}
private _createPaneElement(): HTMLElement {
 let pane = document.createElement('div');
 pane.id = `cdk-overlay-${nextUniqueId++}`;
 pane.classList.add('cdk-overlay-pane');
 this._overlayContainer.getContainerElement().appendChild(pane); // 將創(chuàng)建好的帶id的彈出層添加到宿主
 return pane;
}
private _createPortalHost(pane: HTMLElement): DomPortalHost {
 // 創(chuàng)建宿主
 return new DomPortalHost(pane, this._componentFactoryResolver, this._appRef, this._injector);
}

其中最關(guān)鍵的方法其實(shí)是 getContainerElement() , material2把最"丑"最不angular的操作放在了這里面,看看其實(shí)現(xiàn):

getContainerElement(): HTMLElement {
 if (!this._containerElement) { this._createContainer(); }
 return this._containerElement;
}
protected _createContainer(): void {
 let container = document.createElement('div');
 container.classList.add('cdk-overlay-container');
 document.body.appendChild(container); // 在body下創(chuàng)建頂層的宿主 姑且稱(chēng)之為彈出層容器(OverlayContainer)
 this._containerElement = container;
}

彈窗容器的創(chuàng)建

跳過(guò)其他細(xì)節(jié),現(xiàn)在得到了一個(gè)彈出層引用 overlayRef。material2接下來(lái)給它添加了一個(gè)彈窗容器組件,這個(gè)組件是material2自己寫(xiě)的一個(gè)angular組件,打開(kāi)彈窗時(shí)的遮罩部分以及彈窗的外輪廓其實(shí)就是這個(gè)組件,對(duì)于為何要再套這么一層容器,有其一些考慮。

動(dòng)畫(huà)效果的保護(hù)

這樣動(dòng)態(tài)創(chuàng)建的組件有一個(gè)缺點(diǎn),那就是其銷(xiāo)毀是無(wú)法觸發(fā)angular動(dòng)畫(huà)的,因?yàn)橐凰查g就銷(xiāo)毀掉了,所以material2為了實(shí)現(xiàn)動(dòng)畫(huà)效果,多加了這么一個(gè)容器來(lái)實(shí)現(xiàn)動(dòng)畫(huà),在關(guān)閉彈窗時(shí),實(shí)際上是在播放彈窗的關(guān)閉動(dòng)畫(huà),然后監(jiān)聽(tīng)容器的動(dòng)畫(huà)狀態(tài)事件,在完成關(guān)閉動(dòng)畫(huà)后才執(zhí)行銷(xiāo)毀彈窗的一系列代碼,這個(gè)過(guò)程與其為難用戶(hù)來(lái)實(shí)現(xiàn),不如自己給封裝了。

注入服務(wù)的保護(hù)

目前版本的angular關(guān)于在動(dòng)態(tài)創(chuàng)建的組件中注入服務(wù)還存在一個(gè)注意點(diǎn),就是直接創(chuàng)建出的組件無(wú)法使用隱式的依賴(lài)注入,也就是說(shuō),直接在組件的 constructor 中聲明服務(wù)對(duì)象的實(shí)例是不起作用的,而必須先注入 Injector ,再使用這個(gè) Injector 把注入的服務(wù)都 get 出來(lái):

private 服務(wù);

constructor(
 private injector: Injector
 // private 服務(wù): 服務(wù)類(lèi) // 這樣是無(wú)效的
) {
 this.服務(wù) = injector.get('服務(wù)類(lèi)名');
}

解決的辦法是不直接創(chuàng)建出組件來(lái)注入服務(wù),而是先創(chuàng)建一個(gè)指令,再在這個(gè)指令中創(chuàng)建組件并注入服務(wù)使用,這時(shí)隱式的依賴(lài)注入就又有效了,material2就是這么干的:

<ng-template cdkPortalHost></ng-template>

其中的 cdkPortalHost 指令就是用來(lái)后續(xù)創(chuàng)建組件的。

所以創(chuàng)建這么一個(gè)彈窗容器組件,用戶(hù)就感覺(jué)不到這一點(diǎn),很順利的像普通組件一樣注入服務(wù)并使用。

創(chuàng)建彈窗容器的核心方法在 dom-portal-host.ts 中:

attachComponentPortal<T>(portal: ComponentPortal<T>): ComponentRef<T> {
 // 創(chuàng)建工廠
 let componentFactory = this._componentFactoryResolver.resolveComponentFactory(portal.component);
 let componentRef: ComponentRef<T>;
 if (portal.viewContainerRef) {
  componentRef = portal.viewContainerRef.createComponent(
   componentFactory,
   portal.viewContainerRef.length,
   portal.injector || portal.viewContainerRef.parentInjector);
  this.setDisposeFn(() => componentRef.destroy());
  // 暫不知道為何有指定宿主后面還要把它添加到宿主元素DOM中
 } else {
  componentRef = componentFactory.create(portal.injector || this._defaultInjector);
  this._appRef.attachView(componentRef.hostView);
  this.setDisposeFn(() => {
  this._appRef.detachView(componentRef.hostView);
   componentRef.destroy();
  });
  // 到這一步創(chuàng)建出了經(jīng)angular處理的DOM
 }
 // 將創(chuàng)建的彈窗容器組件直接append到彈出層DOM中
 this._hostDomElement.appendChild(this._getComponentRootNode(componentRef));
 // 返回組件的引用
 return componentRef;
}

所做的事情無(wú)非就是動(dòng)態(tài)創(chuàng)建組件的四步曲:

  1. 創(chuàng)建工廠

  2. 使用工廠創(chuàng)建組件

  3. 將組件整合進(jìn)AppRef(同時(shí)設(shè)置一個(gè)移除的方法)

  4. 在DOM中插入這個(gè)組件的原始節(jié)點(diǎn)

彈窗內(nèi)容

從上文可以知道,得到的彈窗容器組件中存在一個(gè)宿主指令,實(shí)際上是在這個(gè)宿主指令中創(chuàng)建彈窗內(nèi)容組件。進(jìn)入宿主指令的代碼可以找到 attachComponentPortal 方法:

attachComponentPortal<T>(portal: ComponentPortal<T>): ComponentRef<T> {
 portal.setAttachedHost(this);
 // If the portal specifies an origin, use that as the logical location of the component
 // in the application tree. Otherwise use the location of this PortalHost.
 // 如果入口已經(jīng)有宿主則使用那個(gè)宿主
 // 否則使用 PortalHost 作為宿主
 let viewContainerRef = portal.viewContainerRef != null ?
  portal.viewContainerRef :
  this._viewContainerRef;
 // 在宿主上動(dòng)態(tài)創(chuàng)建組件的代碼
 let componentFactory = this._componentFactoryResolver.resolveComponentFactory(portal.component);
 let ref = viewContainerRef.createComponent( // 使用 ViewContainerRef 動(dòng)態(tài)創(chuàng)建組件到當(dāng)前視圖容器(也就是彈窗容器指令)
  componentFactory, viewContainerRef.length,
  portal.injector || viewContainerRef.parentInjector
 );
 super.setDisposeFn(() => ref.destroy());
 this._portal = portal;
 return ref;
}

最后這一步就非常明了了,正是官方文檔中使用的動(dòng)態(tài)創(chuàng)建組件的方式(ViewContainerRef),至此彈窗已經(jīng)成功彈出到界面中了。

彈窗的關(guān)閉

還有最后一個(gè)要注意的點(diǎn)就是彈窗如何關(guān)閉,從上文可以知道應(yīng)該要先執(zhí)行關(guān)閉動(dòng)畫(huà),然后才能銷(xiāo)毀彈窗,material2的彈窗容器組件添加了一堆節(jié)點(diǎn):

host: {
 'class': 'mat-dialog-container',
 'tabindex': '-1',
 '[attr.role]': '_config?.role',
 '[attr.aria-labelledby]': '_ariaLabelledBy',
 '[attr.aria-describedby]': '_config?.ariaDescribedBy || null',
 '[@slideDialog]': '_state',
 '(@slideDialog.start)': '_onAnimationStart($event)',
 '(@slideDialog.done)': '_onAnimationDone($event)',
}

其中需要關(guān)注的就是material2在容器組件中添加了一個(gè)動(dòng)畫(huà)叫 slideDialog ,并為其設(shè)置了動(dòng)畫(huà)事件,現(xiàn)在關(guān)注動(dòng)畫(huà)完成事件的回調(diào):

_onAnimationDone(event: AnimationEvent) {
  if (event.toState === 'enter') {
    this._trapFocus();
  } else if (event.toState === 'exit') {
    this._restoreFocus();
  }
  this._animationStateChanged.emit(event);
  this._isAnimating = false;
}

這里發(fā)射了這個(gè)事件,并在 MatDialogRef 中訂閱:

constructor(
  private _overlayRef: OverlayRef,
  private _containerInstance: MatDialogContainer,
  public readonly id: string = 'mat-dialog-' + (uniqueId++)
) {
  // 添加彈窗開(kāi)啟的訂閱 這里的 RxChain 是material2自己對(duì)rxjs的工具類(lèi)封裝
  RxChain.from(_containerInstance._animationStateChanged)
  .call(filter, event => event.phaseName === 'done' && event.toState === 'enter')
  .call(first)
  .subscribe(() => {
    this._afterOpen.next();
    this._afterOpen.complete();
  });
  // 添加彈窗關(guān)閉的訂閱,并且需要在收到回調(diào)后銷(xiāo)毀彈窗
  RxChain.from(_containerInstance._animationStateChanged)
  .call(filter, event => event.phaseName === 'done' && event.toState === 'exit')
  .call(first)
  .subscribe(() => {
    this._overlayRef.dispose();
    this._afterClosed.next(this._result);
    this._afterClosed.complete();
    this.componentInstance = null!;
  });
}
/**
* 這個(gè)也就是實(shí)際使用時(shí)的關(guān)閉方法
* 所做的事情是添加beforeClose的訂閱并執(zhí)行 _startExitAnimation 以開(kāi)始關(guān)閉動(dòng)畫(huà)
* 底層做的事是 改變了彈窗容器中 slideDialog 的狀態(tài)值
*/
close(dialogResult?: any): void {
  this._result = dialogResult; // 把傳入的結(jié)果賦值給私有變量 _result 以便在上面的 this._afterClosed.next(this._result) 中使用
  // Transition the backdrop in parallel to the dialog.
  RxChain.from(this._containerInstance._animationStateChanged)
  .call(filter, event => event.phaseName === 'start')
  .call(first)
  .subscribe(() => {
    this._beforeClose.next(dialogResult);
    this._beforeClose.complete();
    this._overlayRef.detachBackdrop();
  });
  this._containerInstance._startExitAnimation();
}

以上是“angular/material2中dialog模塊的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對(duì)大家有幫助,更多相關(guān)知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!

文章題目:angular/material2中dialog模塊的示例分析
當(dāng)前路徑:http://bm7419.com/article26/jdggcg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站導(dǎo)航、手機(jī)網(wǎng)站建設(shè)、小程序開(kāi)發(fā)、營(yíng)銷(xiāo)型網(wǎng)站建設(shè)軟件開(kāi)發(fā)、靜態(tài)網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

h5響應(yīng)式網(wǎng)站建設(shè)