Angular 學習筆記 (動態組件 & Material Overlay & Dialog 分析)


更新 : 2020-6-22

當 ngtemplate 被丟到千里之外, detech change 失效

refer issue : https://github.com/angular/vscode-ng-language-service/issues/824

舉個例子, 

比如我們把一個 template 傳進去 mat dialog 里面,然后讓 dialog 里面負責 container.insert template 

template 的 “家” 是打開 dialog 的組件. 假設 template 里面有一個 click 事件會修改外面組件的值.

但是呢,你會發現點擊的時候並不會更新.

 

如果是 index 在 template 內就 ok. 

如果你希望它更新的話,那么 click 就要寫 cdr.markForCheck 咯。

所以要記得哦,template 被傳去千里之外后,它的家就失去對它的監聽了. 

 

 

更新: 2020-06-22

當 ngZone.onStable 遇上 container.createEmbeddedView

今天遇到了一個坑. 

我們知道 container insert 是不會幫我們做 detect change 的. 

但是很奇怪哦,如果在 AfterViewInit 的時候我們 insert template 是 ok 的. 

但是如果你是在 ngZone.onStable 里面 insert template 那就不 ok 了

沒有花太多時間去研究這個,但是推測應該是 after view 以后其實還是會往下去檢測的.

但是 stable 后就肯定是不會了啦.

當然不管是上面那一個都不太邏輯. 因為我們應該要確保 insert 以后一定要 detect change 丫.

 

 

 

更新: 2020-06-01

使用 flexible-connected-position 時, append component after view init 的時候 host element client width 是 1 ?! 

今天踩了一個坑

material 的 overlay 在處理 flexible connected position 的時候會有一個 

BoundingBox 然后是 panel 然后是我們 append 的組件

我們定義的 width 會被寫到 panel 上, 可是呢, penel 一開始有一個 max-weight 100% 
也就是要看 parent 臉色, 而 bounding box 這個 parent 是一個 absolution 然后沒有定義 width, 只有 min-width 1px 
所以一開始的時候 panel 就是 1px 咯
一直到 onstable 以后, overlay 才會去計算, 然后把 boundingbox 的 width set 成 100%, 然后 panel 的 width 才是對的.
而我在組件的 after view init 去獲取 width 這個時候就是 1px. 
不是很清楚 material overlay 的原理,所以目前的解決方向就是閃....寫一個 request animation 就 ok 了。
但是還是要記入好好,可能會有其它隱患出現.
 

 

更新 :  2020-02-13

關於 position 的細節

1. withFlexibleDimensions(true) 

默認是 true 

overlay 在決定 position 時, 它會依據我們給的先后 position 

順序去看,如果其中一個可以完全顯示就馬上用那個。

 

如果全部都不能完整顯示,那么就要試試看調位置, 然后比分數

 

 然后是這樣

 

 最后比分數

 

 所以這個是配合 minHeight, minWidth 來使用的. 

 

還有一個設置也要留意

withLockedPosition(true)
overlay 在 2 種情況下會 reposition
一個是 on scroll 一個是 window resize
window resize 的情況下, position 100% 會重新計算, 不管有沒有 lock 
而 scroll 則是可以被 lock 的. 

window resize 之所以可以繞過 lock 是通過 _isInitialRender 實現的

另外還有一種就是我們手動調用 updatePosition. 

如果我們的內容是動態的,或者是 ajax 比較慢加載回來的話, 那么一般上我們都需要使用這個 reposition 來 update 一下. 

總結 : 

3 種情況下我們會需要 update position 

1. onscroll, (lock/nolock)

2. on resize, (no lock)

3. on content change (lock/nolock)

遇到的問題是 1,3 只可以用同一個config, 比如你 lock 那么 2 個都得 lock, 如果 nolock 2個都得 nolock

因為目前 material 並沒有給多得接口用, 除非我們調用 private 的 _isInitialRender

 

 

 

更新: 2019-11-24 

dialog vs router link 

refer : https://stackoverflow.com/questions/51821766/angular-material-dialog-not-closing-after-navigation

今天發現一些場景可能導致 dialog 不會關閉. 比如當子組件打開一個 dialog 后

某一個操作把父組件給銷毀了.這個時候 dialog content 會一起銷毀掉, 

因為 content 是 under 這個邏輯樹中 (當然如果你是放到 appRef 里頭就另外說)

content 銷毀了,但是 overlay 留在 body 丫. 

 

material team 有考慮到這種情況所以做了一個 fallback 機制, 但是這個並不能解決上面的問題,因為無論如何 dialog 要求至少要啟動 animation start 

如果是 router link 切換的話,渲染會在同一個 detech change 下完成,所以 animation start 是不會被觸發的。

目前 dialog 沒有提供 displose 的方法,所以基本上不無法做到的,除非你去監聽 router event 之類的。

那我覺得比較合理的處理方式是。如果組件負責打開 dialog or overlay 

那么當這個組件 onDestroy 的時候,必須要確保它負責的 overlay 一定要 displose. 為此 dialog 應該要公開這個接口讓我們使用的. 

 

 

 

更新 : 2019-11-14 

小總結一下 angular 動態組件 -> portal -> overlay -> dialog 

ComponentFactoryResolver 是我們用 angular 做動態組件的基礎. cdk, material 都是基於它的。
在 ng 要做一個動態組件是這樣的
 
注入 factory resolver 服務
constructor(
  private componentFactoryResolver: ComponentFactoryResolver,
) { }

 

制作出組件工廠, 把動態組件丟進去就可以了

const componentFactory = this.componentFactoryResolver.resolveComponentFactory(DynamicComponent);

 

然后就可以創建組件實例了,這個時候需要基於一個注入器

通過 Injector.create 創建出一個新的 injector 並且繼承 parent injector, ng 的 injector 有分層的概念 ngModule 的 provider 通常會放到 root injector 里頭, lazy load module 則像下面那樣創建出第 2 層級的 injector 

const componentFactory = this.componentFactoryResolver.resolveComponentFactory(DynamicComponent);
const injector = Injector.create({
  providers: [{ provide: 'extraProvider', useValue: 'dada' }],
  parent: this.injector
});
const componentRef = componentFactory.create(this.injector);

這個時候組件就已經被實例化了,但是還沒有發生 detech change, OnInit 也還沒跑.

這個時候組件是獨立的,我們知道 angular 把所有東西看成 VIew 

組件就是組件 view, 模板就是 embedded view

然后所有 view 都必須放到 logical view tree 里頭. 這樣 change detech 才能遍歷執行

所以現在組件創建好以后,我們需要給它一個家. 

可以是 ViewContainerRef 或 ApplicationRef

像這樣

this.applicationRef.attachView(componentRef.hostView);
this.container.insert(componentRef.hostView, 0);

2 者最大的區別是在 detech change 上, 如果你放到 app 里頭, 那么組件是在最上層, 一旦 app.tick 觸發. 此組件就會觸發 doCheck 

如果你是放到 container 里頭,那么要看這個 container 在 logical tree 里面的第幾層. app.tick 時就不一定觸發 doCheck 了,要看 detech change 有沒有流到這一層里頭 (OnPush 的情況下)

此外, applicationRef.attachVIew 和 container 還有一個不同是, appRef attach 並不會把 dom append 出去

它只是把組件放進去 logical view tree 而且,並沒有 append to dom. 

view container 則會做這個事情. 

那我們得自己搞, 比如... 

document.body.appendChild((componentRef.hostView as EmbeddedViewRef<any>).rootNodes[0] as HTMLElement);

當然絕大部分情況下,我們應該使用 view container 因為這個是官方教我們正確插入 dom 的方式.

上面這個通常是用在 dialog 那種要 append to body 最外層的情況. 由於那里已經脫離的 angular 的 scope 所以我們得自己弄. 

不管是 container.insert 還是 appRef.attachView 調用后,組件就會被 detech change OnInit 了

組件 append 出去或需要一個 detech change 的 cycle 才會渲染哦. viewContainer 並不會替我們渲染組件. 它只是單純的 append 而已.

 

注意 : 

動態組件的 detech change 是比較難懂的. 我是在發現問題,看了源碼之后,找特定關鍵字才找到了相關的文章

https://netbasal.com/things-worth-knowing-about-dynamic-components-in-angular-166ce136b3eb

意思是 componentRef.hostview.detechChange 只會讓 component DoCheck 而已. 

因為 componentRef.hostView 並不是 componentRef.instance.changeDetectorRef 

componentRef.hostView 是一個 RootView 而不是 LView

RootView 重寫了 detech change

LView 的 detechChanges 是這樣的

 所以你會發現 hostView['_lView'] === instance.changeDetectorRef['_IView'] 

但是 detechChange 卻不一樣. 至於為什么這樣設計我也不清楚. 總之只有動態 create 出來的 component 才會有這個 RootViewRef 

那么問題來了, 外部如何讓內部 detech change 呢 ? 

第一種方法就是傳 rxjs 流進去咯. 里面監聽然后 mark for check.

第二種是通過 componentRef.injector 獲取到內部的 changeDetectorRef, 然后調用 markForCheck

此外別無它法, componentRef.detechChange 由於是上層,它只能讓 component DoCheck 而已. 記住了。

所以,記住這幾個點.

1. componentFactory.create 

這個時候組件只是被實例化, 沒有 detech change, 沒有 OnInit 沒有 DoCheck.此時它也沒有在 logical tree 里頭

2. ComponentRef.hostView 是 RootViewRef 而不是平常我們看到的 LView 

RootViewRef 重寫了 changesDetech 方法,所以當我們調用 hostView.changesDetech 的時候,我們的組件並沒有渲染, 因為它執行的是 component 的上一層, 這只會讓 component 執行 DoCheck 而已. 

3. appRef.attachView(componentRef.hostView) 

插入到 logical tree 頂端. 每一次 app.tick 就會被執行 hostView.detechChange. <-- 記住它只是讓 component DoCheck 而不是 render. 

appRef.attach 不會 append dom, 我們需要自己寫代碼去 append dom.

4.container.insert 

插入到 container 這一層級的 logical tree, app.tick 如果有流到這一次就會被 detech change. <-- 還是一樣它只能讓 component DoCheck 而不是 render. 

會 append dom 到 container 的位置.

5. 唯一能讓 component detech change render 的方式是傳一個 rxjs 流進去, 或者通過 componentRef.injector 獲取到內部的 ViewRef (也就是 ChangeDetectorRef)

 

好說完 component,現在說說 template 

template 是通過 <ng-template> 制作出來的。

const context = {};
const viewRef = this.templateRef.createEmbeddedView(context);

和 component 相同的,這個時候 viewRef 還沒有被 detech change. 也沒有在 logical tree 里頭.

我們可以通過 appRef.attachView 或者 container.insert 讓它插入到 logical tree 里頭.

這里主要說說它和 component 不同的地方.

首先它沒有 RootView 這個概念, 我們獲取到的就是 ViewRef. 

另外 template 和 component 一個很大的區別在於它的通訊值. 

template 本身就被定義在某個 component 當中, 然后又被丟到另一個可能千里之外的 component.container 里頭.

<ng-template #template let-age="age" >
  {{ value }} and {{ age }}
</ng-template>

value 來自定義 template 的 component, age 來自使用 template 的 component. 

那它的 detech change 是這樣工作的。

當定義它的 component 發生 detech change 時, value 就被更新了, 使用它的組件並不會因此觸發 detech change 之類的, age 也不會從新去拿. 

就只是更新了 value 然后渲染出效果而已. 如果你在期中偷偷的修改了 age,ng 是不會發現的, 因為它不會去 get age. 

反過來如果是使用 template 的組件做了 detech change, 定義它的組件也不會發生 detech change, 但是呢 value 卻會去 getter 一下 (這里和 age 的表現不相同).

 

上面說了 component 和 template 的基本用法和 detech change 的更新機制.

現在說說 cdk 的 portal 

cdk 提我們封裝了上面這些動態創建 component 和 template 的方法. 其實也沒有什么好封裝的啦,不就那幾行...

但是還是得要搞清楚它是怎樣用起來的。

我們通常會用到是

ComponentPortal 

 

4個都很合理,我們上一段都有用到這些. 至於為什么可以替換 componentFactory 呢, 這個不是很清楚,不是都一樣的嗎.. ? 

另一個可能會用到得是 

PortalInjector

mat dialog 傳遞 data 和 dialogRef 就是用這種簡單方式做的. 它不像上一段使用 Injector.create 然后提供 provider

它只是用一個 weakmap 來實現而已. 

然后是 TemplatePortal 

 

 都是動態創建需要的東西. 

TemplatePortal 和 ComponentPortal 都繼承了 Portal 類

 

 沒什么特別的,只是有 attach 和 detech 的方法而已。然后主要, 這 2 個方法其實內部是調用了 outlet.attach 和 detech 

也就是說邏輯根本沒有寫在這里,這 2 個方法只是一個委托方法而已,除了讓初學者亂沒有看出其它意義.

再來一個 DomPortal

 很簡單,就是放了一個 dom 在里面...

最后是 PortalOutlet, outlet 的職責是 container 用來 append dom 的,

這個是最常用到的,可以簡單理解它就是 container 

 另一個是專門給 append body 用的,類似上段我們說的 appRef 然后自己 append body 

 整個 portal 看下來沒有什么奇特的地方,就真的只是封裝而已. 

我覺得最需要懂得邏輯在這里, portal.attach 

cdk outlet 和 dom outlet 區別就在於此

先看看 ckd outlet attach template 

關鍵在 viewContainerRef, 這個 container 說的是 outlet 這個指令依賴注入得 container (outlet 這個位置)

再看看 ckd outlet attach component 

關鍵也在 container, 如果 portal 本身有 container, 就用,不然就用 outlet 的. 

這里和剛才 template 不同,template 沒有判斷 portal 是否有 container 

這樣的設計我是覺得挺奇怪的,我把 portal 交給了 outlet 結果, portal 被 attach 在原本的 container 里, 這里關 outlet 什么是呢 ? 

然后 outlet 被 destroy 時也 destroy 掉 portal ? 

我們把疑點留着,等下一起討論. 繼續往下走.

這是 dom outlet 的 attach template 

 dom outlet 不是指令,它是 new 出來的,所以它本身不會有 view container ref, 所以 portal 理應要有 view container ref 

這里沒有任何判斷就直接使用了 portal.viewContainerRef ... 挺勇敢的嘛...

此外這里還有一個 cut and paste 的動作. 當 portal 內容 attach 到 container 后, 這里做了一個 dom 操作就是把內容 cut and paste 到 outlet element 里頭.

這應該是和 cdk outlet 最大的不同點. 

最后是 dom outlet 的 attach component 

 

 這里有做 viewcontainer 判斷, 和 template 的處理手法不同. 

如果 portal 沒有 viewcontainer 那么就放到 appRef 里頭. 最后依然會 cut and paste. 

解析完了。總結一下我覺得不太容易理解的幾個點.

1. outlet 決定位置

cdk outlet attach template, dom outlet attach component/template 都可以確保最終的內容渲染在 outlet 位置上. 

但是 cdk outlet attach component 卻不是這樣..

這個確定是一個 bug, https://github.com/angular/components/issues/17650 -> https://github.com/angular/components/pull/17731

所以 cdk outlet 的會確保最終內容出現在 outlet 里

2. cdk outlet append component 時,有可能把 component 系在 appRef 上, 但是 append template 時卻不會這樣. 

反而強制要求 portal 一定要有 container (我的想到的一個解是, template 定義在 component 里,所以它肯定是有 container 的)

在我的理解中. template 和 component 應該保持行為一致. 讓使用者決定要用哪一種. 可以簡單的替換. 

可能 cdk 很靈活時因為 mat 需要這么靈活. 但對我來說這些不一致會導致維護起來比較麻煩. 

所以我的做法通常是 portal 不需要帶 container 邏輯. 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

更新: 2019-11-08 

記入一下 overlay 的使用

material 有 8 個組件用到了 overlay 

autocomplete
datepicker
select
menu
bottom sheet
dialog
snackbar
tooltip

在真實項目中,還有很多組件是沒有的. 比如 

小 form 

 

 

 比如大 message tip

 

這些都得我們自己去實現. 所以就需要用到 overlay 了.

先說說它的過程

當我們調用 overlay.create 的時候, overlay 會在 body 層創建一個 div 

然后依據我們的 width height 在放一個 div 在里面 (其實好像有 3 - 4 層 div)

如果我們要 backdrop 也可以通過 overlay 設置. 

有了 backdrop 我們就可以監聽點擊事件然后關掉 overlay 了. 

這里有一個小體驗. 很久以前,我是用 body click + stop bubble 來實現這種 modal close 的. 后來發現大家都用 overlay + 透明 backdrop 來做

省去了不少麻煩. stop bubble 在多層次的情況下不太好處理, 但是這個做法也有它的局限. 比如只能 body scroll 因為 backdrop 在最上層, 會把其它 div 擋住, 如果我們依賴其它 div 來做 scroll 

那么就 scroll 不了的. 所以多用 body scroll 還是比較正確的姿勢. 

我還發現一個小秘密,就是 material tooltip 沒有使用 backdrop 但是缺可以點擊 body 關閉. 它也是通過監聽 body click 實現的,因為 tooltip 內只可以是字, 所以不會有點擊事件也就不需要顧慮 bubble 的問題. 很巧妙的在設計上躲過了實現的難題. 

做小 modal 要搞懂 position strategy

const positionStrategy = this.overlay.position()
  .flexibleConnectedTo(origin)
  .withTransformOriginOn('.transformOrigin')
  .withFlexibleDimensions(false)
  .withViewportMargin(8)
  .withPush(false)
  .withLockedPosition(true)
  .withPositions([
    { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'top' },
    { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom' },
    { originX: 'end', originY: 'top', overlayX: 'end', overlayY: 'top' },
    { originX: 'end', originY: 'top', overlayX: 'end', overlayY: 'bottom' }
  ]);

const overlayRef = this.overlay.create({
    positionStrategy,
    scrollStrategy: this.overlay.scrollStrategies.reposition(),
    hasBackdrop: true,
});

scroll strategy 用的是 reposition, 這個很好里理解, 就是當 scroll 的時候我們的 modal 需要始終維持對的位置.

來說說 position strategy, 和 big modal 不同, small modal 需要一個位置, 通常是在我們點擊按鈕的附近. 

可以叫它 origin element, 我們要呈現的內容 (content) 必須和 origin element 做一個定位.

flexibleConnectedTo(origin element) 就 content connect to origin 的意思

withPositions 提供一個位置匹配, origin 9 個點, content 9 個點, 所以總共可以擺出 81 一個位置. 

 

 

我們提供一個 array 寫上各種匹配方式, 要有順序之分哦,overlay 會先后判斷可見范圍,找出一個可見度最高的作為展現, 比如 drop down 在屏幕上方,顯示位置是下,在屏幕下方顯示位置是上,這種體驗.

withTransformOriginOn(content element selector string) 主要是給我們做 animation scale 用的,由於 content 出現的位置是不固定的

所以 animation 展示的位置也是不固定的,overlay 會通過我們傳入的 selector 找到 element 然后把 transform origin 設置進去. 

withFlexibleDimensions 這個我到現在都沒有搞懂是啥, default 是 true, 但是我發現它的效果怪怪的,所以就不用了. 跳

withViewportMargin 我們不希望我們的 content 和 viewport 黏在一起, 就可以放這個 margin 給它. 

紅色區域就是那個 margin 

withPush 默認是 true, 有了這個, 用戶不管 scroll 上下左右, 我們的 content 就會一直保持在可見區, 會跟着 scroll 走. 

withScrollableContainers(element) 這個是用於當我們有多層 scroll bar 時用到的,默認情況下, overlay 是通過 scrollDispatcher 去監聽 body scroll 的. 

但是如果我們的 origin 在一個 div scroll 里, 只監聽 body scroll 是無法做出正確體驗的,所以我們要讓 overlay 知道這個事情. 

做法是這樣的, 我們得把我們能 scroll 的 element 都注冊進去 scrollDispatcher (可以自己調用 register 或者用 cdkScrollable 指令)

當 scrollDispatcher 有了所有的 scrollable div, 當我們調用 withScrollableContainers,它會拿我們傳入的 element 去 match (element 的 parent 如果有在 scrollable list 中就去監聽這個 scrollable 的滾動事件) 

這樣當 scroll 的時候, 我們的 content 就會正確的被 reposition 了.

withLockedPosition 當我們 scroll 的時候, overlay 會替我們 reposition 但是有時候這種跳來跳去不一定是好的體驗,這個時候我們可以使用 lock, content 顯示時會用最佳位置,然后就一直保持這個位置,不管用戶 resize or scroll.

到這個環境, overlay 算是做出來了. 下一個是做 content 的 animation

通常 overlay append content 我們都希望有同一種 animation 體驗,所以一般上會封裝 animation 

它的具體做法是做一個 container 組件, overlay 每次 append 都是這個 container 組件,然后這個組件在 append 我們的動態組件.

const containerInjector = new PortalInjector(this.vcr.injector, new WeakMap());
const containerPortal = new ComponentPortal(ContainerComponent, this.vcr, containerInjector);
const container = overlayRef.attach(containerPortal).instance;

overlay 內部有一個 dom portal outlet (這個和我們經常用的 cdk portal outlet 指令不是同一個哦),我們調用 overlay.attach(我們的 portal)

overlay 會調用 DomPortalOutlet.attachComponent.

這里的關鍵是我們傳入的 portal 是否有 viewContainerRef  它會決定之后的 detech change 時機和 injection.

如果有 viewcontainer 那么會把 portal  先創建到 view container 然后通過 outletElement (body 的 div) appendchild (cut and paste) 出去.

如果沒有會直接創建 component 然后放入 appRef.views 里面. 然后依然 append to body 

大部分情況下我們 portal 應該要有 view container ref.

下一個動作就是 container append 動態組件了. 

<ng-template cdkPortalOutlet></ng-template>

我們可以在 container.html 使用 cdkPortalOutlet 

@ViewChild(CdkPortalOutlet, { static: true }) portalOutlet: CdkPortalOutlet;

通過 viewchild + static 獲取到這個指令. (看到 static true 的用途了吧...嘻嘻)

static 的特色是,在 component construtor 運行完后就可以獲取到這個屬性值了, 不需要等到 after view init.

container.animationStateChanged.pipe(filter(e => e.toState === 'enter' && e.phaseName === 'done'), take(1)).subscribe(e => {
  container.autoFocus();
});
const contentInjector = new PortalInjector(this.vcr.injector, new WeakMap([[MODAL_DATA, 'data']]));
const contentPortal = new ComponentPortal(AbcComponent, null, contentInjector); // 這里 view container ref 是 null
container.attachComponentPortal(contentPortal);

注意那個 animationStateChanged. overlay dispose 是很突兀的,所以我們幾乎不可能直接調用。

正確的做法是通過控制我們 container 的 animation 來完成關閉, 比如先 fade out container,然后監聽 container fade out done 才調用 overlay dispose.

上面這個例子是做了一個 autofocus, 看的出來 container 內部封裝了 cdk focus trap 功能.

另一個要留意的是, container.attachComponent 

剛才我們說 container 內有一個 cdk portal outlet, 拿我們只需要開一個接口接受動態組件,然后就可以 attach 出去了。

cdk portal outlet vs dom portal outlet 

 cdk portal outlet 處理 view container ref 的方式有點不同, cdk poral outlet 本身有自己的 view container ref (剛才 dom outlet 是用 appRef)

如果 portal 自帶 view container ref, 那么會直接把 portal 插入到其中, 所以內容不會被 append 到 cdk portal outlet 的位置哦. (這有點怪,注釋說了只是邏輯樹會插入到 portal 的 view container, 但是渲染應該是在 portal outlet 的位置才對呀. 但是沒有..)

提了一個 issue 希望能問個明白

https://github.com/angular/components/issues/17650

如果沒有, 就會使用 cdk portal outlet 的 viewcontainer 了. 這通常會是我們想要的結果.

 

 

 

 

 

 

在學習 overlay 和 portal 的時候,一直沒有弄明白 viewContainerRef 在其中扮演的角色

這里說一下來龍去脈

當我們創建一個 overlay 時,同時創建了一個 portal outlet

 

 當我們要 append 內容時,內部其實時調用了 DomPortalOutlet 的 attachComponentPortal 方法

 

這時候會依據 portal <-- 傳入的component portal,不是 portal outlet 哦,不要搞混了.

時候有 viewContainerRef 決定如何創建 component.

如果有就調用 viewContainerRef create component 方法, 這時會 insert component to container 渲染. 然后再通過 dom 操作 cut and paste 去 portal outlet (body)。

如果沒有的話就直接通過 component factory create component 然后把 view 放入到全局 appRef 里面. 這時候組件並沒有 append to dom 任何地方. 

然后 cut and paste to portal outlet.

當 app.tick 時,所有的 appRef.views 就會 detech change. 

 

2 者有什么區別呢 ? 

 

在 portal 的文檔里並沒有解釋太多... 只是說什么邏輯樹和 view 樹的不同而已. 

反而是 dialog 的文檔里解釋了

 

從源碼上看確實如此.

在使用了 viewContainerRef 之后, detech change 的時機是依據 viewContainerRef 的

而放入 appRef 的情況, detech change 的時機是 app.tick 每一次都觸發.

 

 appRef.attachView 將 view 放入了一個 array 中.

 

 在 tick 的時候調用 detech change.

 

至於 injector 其實蠻困惑的,因為 attachComponentPortal injector 是基於 component portal 的 injector,跟 viewContainerRef 沒有啥關系丫. 那為什么 dialog 文檔說有關系呢

 

看了源碼就會發現了,dialog 創建 portal 使用的 injector 是 userInjector || rootInjector, 而所謂的 userInjector 就是 viewContainerRef.injector.

這樣就真相大白了咯。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM