此文基於官方文檔教程,是對其代碼細節的解釋文檔。
reuse-tab組件位於【delon/abc】包中,這意味着多標簽式選項卡組件是ng-alain內置的組件之一。 請注意,此組件不支持全部關閉,必須保留一個選項卡。
實現reuse-tab組件的基石
reuse-tab組件的原理是基於NG的RouteReuseStrategy服務在頁面上輸出一排按鈕。
RouteReuseStrategy是angular的路由復用策略,用於可復用路由的判定與存取的操作。前提是你要清楚ng-alain項目的路由框架是“一個頁面(URL)對應一個組件對應一個標簽/選項卡”的事實,所以RouteReuseStrategy實際上緩存的就是組件對象。【對於一個頁面內有兩個以上的路由插槽的例外情況暫(bing)且(bu)不(zhi)表(chi)】。
關於路由復用策略的情況我不再贅述,請移步此處。
代碼執行概覽
RouteReuseStrategy內有一個數組存儲着所有被緩存的路由快照,通過點擊reuse-tab在頁面上生成的分別與這些快照對應的按鈕,導航到相應的URL地址,這時候路由就不會生成新的組件對象而是使用緩存在RouteReuseStrategy服務內的路由快照。而當第一次訪問某個URL(路由)時,會生成路由快照,此時reuse-tab會自動生成一個按鈕(選項卡)。
路由判定可緩存
通過MenuService可以設置菜單列表,Menu對象的reuse屬性指示當前路由可否被緩存。如果為true,則該菜單對應的URL(路由)必然會被復用。
這時候我們就會遇到一個問題,那就是並不是所有路由在左側菜單欄都有對應的菜單項。此時沒有Menu對象供我們設置它的reuse屬性來指定路由可否被復用,難道真得向MenuService插入一個隱藏的菜單? 當然拒絕用這么low的方法,博主不甘心的仔細查閱reuse-tab組件,發現一個名為mode的枚舉屬性,我們可以通過指定此屬性的值來決定reuse-tab的復用方式。
當值為ReuseTabMatchMode.Menu時,除非標記Menu對象的屬性reuse為false否則都判定為可被復用。
當值為ReuseTabMatchMode.MenuFore時,除非標記Menu對象的屬性reuse為true否則都判定不可被復用。
當值為ReuseTabMatchMode.URL時,任何URL(路由)都將判定為可被復用(可以通過excludes屬性來指定不需要被復用的URL)。這並不妨礙你設置menu.reuse為false,因為Menu.reuse相當於css的行內樣式,優先級很高。
是故我們設置reuse-tab組件的mode屬性即可根據自身項目需求自定義reuse-tab的行為。友情提示 excludes與menu.reuse的設定請務必不要重復or互斥,如果出現不該被復用的路由被復用那可能就是這兒的問題。
mode的默認值

所以如上圖所示,ng-alin項目中默認情況下除了/500設置reuse為false,其他都會緩存。因為mode的默認值就是Menu。

辦法(二)

通常情況下,當模式為Menu時,我會朝不存在於MenuService的路由對象的date屬性里添加一個類型為Boolean的屬性,它指示是否復用此路由。
路由復用時存儲
【@delon/abc】中實現RouteReuseStrategy接口並用來緩存路由快照的類型是ReuseTabCached。

ReuseTabService.store方法是用來緩存路由的快照。請注意,路由復用策略同時把路由的title緩存了,將在下文指出。
多標簽式選項卡的標題
首先我們應該區分多標簽式選項卡的標題與路由快照緩存的標題,但前者來自於后者。

ReuseItem是多標簽式選項卡的元素類型,顧名思義title屬性直接作用於選項卡標題。
請允許我再次申明:reuse-tab組件遍歷了【ReuseItem數組】從而生成的按鈕們與路由快照一一對應,而【ReuseItem數組】是基於【路由快照緩存數組】生成的,所以說有多少個被緩存的路由快照就有多少個標簽式選項卡。

在路由快照存儲時title就已經被感知到了,上圖則是細節部分。
在顯示多標簽式選項卡標題時,優先使用通過ReuseTabService.title設置並存儲在ReuseTabService._titleCached數組內的標題,然后使用路由的data對象的title,否則使用菜單的text屬性,最后直接顯示URL。
根據此方法最后兩行代碼,如果設置mode的值為ReuseTabMatchModel.URL時,請務必滿足前兩個條件之一,否則只會顯示URL。
到此你會發現我們談的一直都是路由快照緩存標題,但為什么又牽扯到多標簽式選項卡標題呢?那是因為reuse-tab的數據源(ReuseItem[])基於路由快照緩存(ReuseTabCached)。

如上圖所示,reuse-tab遍歷了路由快照緩存生成ReuseItem,titile的值也來自於ResueTabCached.titile。
因為理解不了reuse-tab組件源代碼的初衷,所以我理的越來越亂。並且組件並沒有我預期中的那么精致,我勉強能總結一下標題顯示URL的問題。
- 當你的路由並不在左側菜單中時,你要么設置ReuseTabService.title = '標題',要么設置路由的 data: { title : ‘標題’}。
- 當你設置mode為ReuseTabMatchMode.URL時。你要么設置ReuseTabService.title = '標題',要么設置路由的 data: { title : ‘標題’},Menu對象的text和i18n不會起作用,它只會顯示URL。
- 不要糾結於ReuseTabService.mode和ReuseTabComponent.mode這兩個的關系,請統一使用前者。
- 當你確定全部路由都不需要復用時,請從layout/default.component.html中移除reuse-tab標簽。
reuse-tab選項卡切換
目前有個隱患就是,路由復用策略雖然能緩存緩存URL(路由)內的組件對象(一直存在),那么組件生命周期【OnDestroy】永遠不會被觸發,同時當被緩存的路由被激活后(切換選項卡)【OnInit】更不會觸發。

是故reuse-tab組件內部有新的生命周期,用來支持路由快照被激活和被緩存這兩個操作能被感知到。但如圖所示組件仍需要實現OnInit,因為第一次並不會觸發【_onReuseInit】。
ng-alain1.0.x之后必須由用戶主動注冊路由復用策略,具體請查閱ng-alain的更新日志或示例代碼。

同時reuse-tab組件支持在選項卡被切換后、關閉后發起通知。除非點擊reuse-tab組件在DOM上生成的按鈕,不然僅點擊左側菜單是不會觸發change事件的。
啟用reuse-tab后的路由傳參
在任意的單頁面web應用程序中,涉及到【路由】的項目都必須嚴格按照框架推薦的方式進行傳參。
而在ng-alain中,當reuse-tab被啟用后(即便在啟用之前),URL都不應該繼續使用帶有【?】的傳參方式,無論【?】是在【#】之前還是之后。此規則是強制性要求,不遵守即必然出現錯誤,表現為參數會在頁面切換或刷新后丟失。
當路由只需要一個參數時的正確做法:
{ path: 'edit/:id', component: EditNewComponent, data: { reuse: true } },
id: number; inputType: boolean; ngOnInit(): void { this.id = parseInt(this.route.snapshot.params.id); this.inputType = !this.id || this.id <= 0; }
當路由只需要一個參數時,如上方的示例代碼所示。使用【/:***】的方式傳遞參數。
當路由不需要或需要多個參數時的正確做法:
{ path: 'add', component: AddComponent, data: { reuse: true } },
ngOnInit(): void { console.log('this.route.snapshot.params :', this.route.snapshot.params); this.route.params.subscribe(c => console.log('c :', c)); }
輸出結果: this.route.snapshot.params : {id: "1", cd: "1583504839679"} c : {id: "1", cd: "1583504839679"}
此時無需在路由中定義參數,在頁面內使用任意一種方式皆可獲取到傳入的參數。第一種示例代碼強制要求必須傳入id,而此種方式指示所有可能的參數都是可選的。術語叫做【矩陣參數(matrix parameters)】。
最后的問題
並不是每個路由在事先都能知道標題,也不是每個路由的標題在事后都一成不變。如何才能根據業務需求動態的改變多標簽式選項卡的標題?似乎是只能將從服務器動態獲取的菜單設為隱藏后添加到MenuService里,但這樣做的限制是不能使用ReuseTabService.title和路由的data: { title : ‘標題’} 方式來設置標題。那么,該用何種方式才能寫出不會損耗性能的優雅代碼呢?
其余內容,待續……

