亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

從源碼看angular/material2 中 dialog模塊的實現方法

2019-11-19 15:08:56
字體:
來源:轉載
供稿:網友

本文將探討material2中popup彈窗即其Dialog模塊的實現。

使用方法

  1. 引入彈窗模塊
  2. 自己準備作為模板的彈窗內容組件
  3. 在需要使用的組件內注入 MatDialog 服務
  4. 調用 open 方法創建彈窗,并支持傳入配置、數據,以及對關閉事件的訂閱

深入源碼

進入material2的源碼,先從 MatDialog 的代碼入手,找到這個 open 方法:

open<T>( componentOrTemplateRef: ComponentType<T> | TemplateRef<T>, config?: MatDialogConfig): MatDialogRef<T> { // 防止重復打開 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.`); } // 第一步:創建彈出層 const overlayRef = this._createOverlay(config); // 第二步:在彈出層上添加彈窗容器 const dialogContainer = this._attachDialogContainer(overlayRef, config); // 第三步:把傳入的組件添加到創建的彈出層中創建的彈窗容器中 const dialogRef = this._attachDialogContent(componentOrTemplateRef, dialogContainer, overlayRef, config); // 首次彈窗要添加鍵盤監聽 if (!this.openDialogs.length) {  document.addEventListener('keydown', this._boundKeydown); } // 添加進隊列 this.openDialogs.push(dialogRef); // 默認添加一個關閉的訂閱 關閉時要移除此彈窗 // 當是最后一個彈窗時觸發全部關閉的訂閱并移除鍵盤監聽 dialogRef.afterClosed().subscribe(() => this._removeOpenDialog(dialogRef)); // 觸發打開的訂閱 this.afterOpen.next(dialogRef); return dialogRef;}

總體看來彈窗的發起分為三部曲:

  1. 創建一個彈出層(其實是一個原生DOM,起宿主和入口的作用)
  2. 在彈出層上創建彈窗容器組件(負責提供遮罩和彈出動畫)
  3. 在彈窗容器中創建傳入的彈窗內容組件(負責提供內容)

彈出層的創建

對于其他組件,僅僅封裝模板以及內部實現就足夠了,最多還要增加與父組件的數據、事件交互,所有這些事情,單使用angular Component就足夠實現了,在何處使用就將組件選擇器放到哪里去完事。

但對于彈窗組件,事先并不知道會在何處使用,因此不適合實現為一個組件后通過選擇器安放到頁面的某處,而應該將其作為彈窗插座放置到全局,并通過服務來調用。

material2也要面臨這個問題,這個彈窗插座是避免不了的,那就在內部實現它,在實際調用彈窗方法時動態創建這個插座就可以了。要實現效果是:對用戶來說只是在單純調用一個 open 方法,由material2內部來創建一個彈出層,并在這個彈出層上創建彈窗。

找到彈出層的創建代碼如下:

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); // 將創建好的帶id的彈出層添加到宿主 return pane;}private _createPortalHost(pane: HTMLElement): DomPortalHost { // 創建宿主 return new DomPortalHost(pane, this._componentFactoryResolver, this._appRef, this._injector);}

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

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下創建頂層的宿主 姑且稱之為彈出層容器(OverlayContainer) this._containerElement = container;}

彈窗容器的創建

跳過其他細節,現在得到了一個彈出層引用 overlayRef。material2接下來給它添加了一個彈窗容器組件,這個組件是material2自己寫的一個angular組件,打開彈窗時的遮罩部分以及彈窗的外輪廓其實就是這個組件,對于為何要再套這么一層容器,有其一些考慮。

動畫效果的保護

這樣動態創建的組件有一個缺點,那就是其銷毀是無法觸發angular動畫的,因為一瞬間就銷毀掉了,所以material2為了實現動畫效果,多加了這么一個容器來實現動畫,在關閉彈窗時,實際上是在播放彈窗的關閉動畫,然后監聽容器的動畫狀態事件,在完成關閉動畫后才執行銷毀彈窗的一系列代碼,這個過程與其為難用戶來實現,不如自己給封裝了。

注入服務的保護

目前版本的angular關于在動態創建的組件中注入服務還存在一個注意點,就是直接創建出的組件無法使用隱式的依賴注入,也就是說,直接在組件的 constructor 中聲明服務對象的實例是不起作用的,而必須先注入 Injector ,再使用這個 Injector 把注入的服務都 get 出來:

private 服務;

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

解決的辦法是不直接創建出組件來注入服務,而是先創建一個指令,再在這個指令中創建組件并注入服務使用,這時隱式的依賴注入就又有效了,material2就是這么干的:

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

其中的 cdkPortalHost 指令就是用來后續創建組件的。

所以創建這么一個彈窗容器組件,用戶就感覺不到這一點,很順利的像普通組件一樣注入服務并使用。

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

attachComponentPortal<T>(portal: ComponentPortal<T>): ComponentRef<T> { // 創建工廠 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();  });  // 到這一步創建出了經angular處理的DOM } // 將創建的彈窗容器組件直接append到彈出層DOM中 this._hostDomElement.appendChild(this._getComponentRootNode(componentRef)); // 返回組件的引用 return componentRef;}

所做的事情無非就是動態創建組件的四步曲:

  1. 創建工廠
  2. 使用工廠創建組件
  3. 將組件整合進AppRef(同時設置一個移除的方法)
  4. 在DOM中插入這個組件的原始節點

彈窗內容

從上文可以知道,得到的彈窗容器組件中存在一個宿主指令,實際上是在這個宿主指令中創建彈窗內容組件。進入宿主指令的代碼可以找到 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. // 如果入口已經有宿主則使用那個宿主 // 否則使用 PortalHost 作為宿主 let viewContainerRef = portal.viewContainerRef != null ?  portal.viewContainerRef :  this._viewContainerRef; // 在宿主上動態創建組件的代碼 let componentFactory = this._componentFactoryResolver.resolveComponentFactory(portal.component); let ref = viewContainerRef.createComponent( // 使用 ViewContainerRef 動態創建組件到當前視圖容器(也就是彈窗容器指令)  componentFactory, viewContainerRef.length,  portal.injector || viewContainerRef.parentInjector ); super.setDisposeFn(() => ref.destroy()); this._portal = portal; return ref;}

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

彈窗的關閉

還有最后一個要注意的點就是彈窗如何關閉,從上文可以知道應該要先執行關閉動畫,然后才能銷毀彈窗,material2的彈窗容器組件添加了一堆節點:

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)',}

其中需要關注的就是material2在容器組件中添加了一個動畫叫 slideDialog ,并為其設置了動畫事件,現在關注動畫完成事件的回調:

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

這里發射了這個事件,并在 MatDialogRef 中訂閱:

constructor(  private _overlayRef: OverlayRef,  private _containerInstance: MatDialogContainer,  public readonly id: string = 'mat-dialog-' + (uniqueId++)) {  // 添加彈窗開啟的訂閱 這里的 RxChain 是material2自己對rxjs的工具類封裝  RxChain.from(_containerInstance._animationStateChanged)  .call(filter, event => event.phaseName === 'done' && event.toState === 'enter')  .call(first)  .subscribe(() => {    this._afterOpen.next();    this._afterOpen.complete();  });  // 添加彈窗關閉的訂閱,并且需要在收到回調后銷毀彈窗  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!;  });}/*** 這個也就是實際使用時的關閉方法* 所做的事情是添加beforeClose的訂閱并執行 _startExitAnimation 以開始關閉動畫* 底層做的事是 改變了彈窗容器中 slideDialog 的狀態值*/close(dialogResult?: any): void {  this._result = dialogResult; // 把傳入的結果賦值給私有變量 _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();}

總結

以上就是整個material2 dialog能力走通的過程,可見即使是 angular 這么完善又龐大的框架,想要完美解耦封裝彈窗能力也不能完全避免原生DOM操作。

除此之外給我的感覺還有――無論是angular還是material2,它們對TypeScript的使用都讓我自嘆不如,包括但不限于抽象類、泛型等裝逼技巧,把它們的源碼慢慢看下來,著實能學到不少東西。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
午夜伦理精品一区| 成人www视频在线观看| 96精品久久久久中文字幕| 色综合天天综合网国产成人网| 亚洲国产97在线精品一区| 亚洲人成电影网站| 另类专区欧美制服同性| 91地址最新发布| 久久久久中文字幕| 97久久国产精品| 亚洲国产精品一区二区久| 亚洲第一在线视频| 亚洲欧美中文在线视频| 91在线网站视频| 精品久久香蕉国产线看观看亚洲| 法国裸体一区二区| 色午夜这里只有精品| 亚洲sss综合天堂久久| 中文字幕av一区| 欧美日韩亚洲系列| 亚洲毛片在线观看.| 久久精品夜夜夜夜夜久久| 午夜剧场成人观在线视频免费观看| 亚洲精品一区在线观看香蕉| 亚洲高清一区二| 日韩亚洲精品视频| 人妖精品videosex性欧美| 一区二区三区久久精品| 精品视频在线播放| 久久男人资源视频| 亚洲欧美国产精品久久久久久久| 色综合久久88| 91在线视频精品| 久久久视频免费观看| 日韩精品免费观看| 久久99久久99精品中文字幕| 日韩精品视频观看| 午夜美女久久久久爽久久| 亚洲三级黄色在线观看| 久久精品福利视频| 亚洲欧美一区二区精品久久久| 久久久www成人免费精品张筱雨| 精品国产一区二区三区四区在线观看| 国产日韩在线亚洲字幕中文| 97热精品视频官网| 日韩精品视频观看| 日本精品视频在线| 中文字幕日韩在线视频| 亚洲欧美在线一区| 久久久成人精品| 亚洲欧美在线第一页| 国产精品久久久久久久av电影| 亚洲美女av黄| 国产精品极品在线| 亚洲国模精品私拍| 日韩精品欧美国产精品忘忧草| 色偷偷亚洲男人天堂| 国产精品久久久久免费a∨大胸| 色综合伊人色综合网| 久久久久久com| 欧美在线视频观看| 亚洲国产91色在线| yw.139尤物在线精品视频| 亚洲精品一区二区三区婷婷月| 亚洲第一男人天堂| 亚洲成人在线视频播放| 欧美裸体xxxx极品少妇| 国产久一一精品| 欧美电影在线免费观看网站| 欧美日韩国产精品| 欧美成人精品在线视频| 久久久精品在线观看| 亚洲欧美日韩天堂| 1769国内精品视频在线播放| 亚洲精品日韩在线| 国产成人在线亚洲欧美| 海角国产乱辈乱精品视频| 不卡av日日日| 欧美成人高清视频| 丁香五六月婷婷久久激情| 久久久久久亚洲精品不卡| 欧洲亚洲免费在线| 91人人爽人人爽人人精88v| 久久精品国产69国产精品亚洲| 91tv亚洲精品香蕉国产一区7ujn| 97国产真实伦对白精彩视频8| 亚洲精品成a人在线观看| 亚洲性生活视频| 国外成人在线直播| 日本国产一区二区三区| 国产免费一区二区三区在线观看| 久久久久久亚洲精品不卡| 中文字幕欧美日韩精品| 日韩欧美亚洲范冰冰与中字| 一区二区三区国产在线观看| 久久久亚洲国产| 亚洲free嫩bbb| 精品视频一区在线视频| 97人人做人人爱| 欧美性猛交99久久久久99按摩| 色先锋久久影院av| 日韩精品久久久久久福利| 日韩在线观看高清| 精品久久久久久电影| 国产精品自产拍在线观看中文| 最近2019中文字幕mv免费看| 久久91精品国产91久久久| 7777精品视频| 日本成熟性欧美| 亚洲福利在线观看| 岛国av午夜精品| 国产一区二区在线免费| 亚洲视频自拍偷拍| 欧美高清在线观看| 亚洲第一男人天堂| 国产精品一区二区三区久久| 国产99视频在线观看| 精品久久久久久久久久| 日韩国产在线看| 97超碰国产精品女人人人爽| 亚洲精品视频中文字幕| 伊人久久精品视频| 亚洲激情电影中文字幕| 亚洲国产91色在线| 国产一区二区三区在线视频| 91美女片黄在线观| 亚洲精品一区二三区不卡| 亚洲国产美女精品久久久久∴| 欧美日韩福利电影| 2020国产精品视频| 日韩黄色在线免费观看| 最近中文字幕mv在线一区二区三区四区| 久久人人爽亚洲精品天堂| 亚洲新中文字幕| 国产一区av在线| 亚洲国产日韩欧美综合久久| 亚洲精品中文字幕女同| 日韩欧美一区视频| 久久久久久久一区二区| 国产午夜精品理论片a级探花| 97视频在线观看免费高清完整版在线观看| 国产精品久久久久久搜索| 亚洲免费成人av电影| 欧美大尺度激情区在线播放| 91色p视频在线| 日韩视频免费观看| 国产精品流白浆视频| 欧美电影院免费观看| 日本高清不卡的在线| 国产日韩欧美中文| 日韩性xxxx爱| 国产欧美婷婷中文| 亚洲第一中文字幕在线观看| 中文字幕视频一区二区在线有码| 久久频这里精品99香蕉| 在线播放日韩精品| 国产视频亚洲视频| 亚洲成人激情小说| 久操成人在线视频| 欧美性视频在线| 欧美精品在线视频观看| 国产精品成人aaaaa网站| 亚洲精品大尺度| 日韩精品视频免费在线观看|