上例子:https://github.com/wuzhouyang/angular-dynamic-component-example
接觸 angular2 也有好幾月了译柏,由于在做的項目dom的操作貌似比較頻繁厉膀,而且并不能針對dom來編程,這樣的話與模板的耦合度便十分大傀蚌,所以只能針對組件編程,動態(tài)加載組件來實現(xiàn)項目中相關(guān)的功能只估,那就不得不用到angular2中一些比較低等級的api霸株。此文要記錄的便是目前用到的兩種動態(tài)創(chuàng)建組件的方案诸衔。
(一)動態(tài)加載已經(jīng)聲明的組件
針對我的項目場景:用戶拖動相關(guān)的塊到特定區(qū)域,區(qū)域中便會生成相應(yīng)的UI控件颇玷,此UI控件有自己的模板笨农、行為等等。生成UI 后便會在區(qū)域中顯示出來帖渠。所以我覺得將UI控件封為一個小組件谒亦,再動態(tài)加載,是個不錯的方案空郊。angular2中是如何動態(tài)加載組件的份招?
要實現(xiàn)這個功能,得先簡單了解angular2 中相關(guān)的api
- ViewChild:一個屬性裝飾器狞甚,用來從模板視圖中獲取對應(yīng)的元素锁摔,可以通過模板變量獲取,獲取時可以通過 read 屬性設(shè)置查詢的條件哼审,就是說可以把此視圖轉(zhuǎn)為不同的實例
- ViewContainerRef :一個視圖容器谐腰,可以在此上面創(chuàng)建、插入涩盾、刪除組件等等
- ComponentFactoryResolve:一個服務(wù)怔蚌,動態(tài)加載組件的核心,這個服務(wù)可以將一個組件實例呈現(xiàn)到另一個組件視圖上
有了這三個旁赊,一個簡單的思路便連貫了:特定區(qū)域就是一個視圖容器桦踊,可以通過 ViewChild 來實現(xiàn)獲取和查詢,然后使用ComponentFactoryResolve將已聲明未實例化的組件解析成為可以動態(tài)加載的 component终畅,再將此component 呈現(xiàn)到此前的視圖容器中
為了實現(xiàn)此功能籍胯,我寫了一個簡單的例子
我單獨寫了一個特性模塊來測試,下面是結(jié)構(gòu)圖
特性模塊涉及到 lazyload 的知識离福,我也不多講了杖狼。直接看結(jié)構(gòu)說明
- dy1.component.ts
- dy2.component.ts
這兩個就是將要動態(tài)加載的組件,內(nèi)容不多妖爷,就是為了測試蝶涩,如圖
- dynamic.component.ts
這個是此特性模塊對應(yīng)的組件實例,用于聲明一些邏輯動作絮识。簡單看看此組件代碼
可以看到在頂部導入處必需的三個都在绿聘。在組件類的頂部我通過模板變量的方式獲取了此組件模板視圖上了一個元素來作為視圖容器,可以看看模板的代碼
紅框處便是我們要在上面動態(tài)加載組件的容器次舌。
看看獲取容器的代碼
通過模板變量名獲取熄攘,然后 可以通過 read 選項設(shè)置為一個 ViewContainerRef ,最終在生命鉤期 ngAfterViewInit 過后便會獲取此區(qū)域的一個 ViewContainerRef 實例彼念。
看看主要的加載組件函數(shù)
我們已經(jīng)通過組件類的構(gòu)造函數(shù)注入了ComponentFactoryResolve服務(wù)挪圾,現(xiàn)在便可以調(diào)用其方法來解析得到一個 componentFactory 了浅萧,resolveComponentFactory 解析一個已經(jīng)聲明的組件得到一個可動態(tài)加載的 componentFactory,這里的DY1Component我們已經(jīng)在頂部導入了哲思。然后我們可以直接調(diào)用容器的createComponent函數(shù)將解析出來的componentFactory動態(tài)呈現(xiàn)到容器視圖上洼畅。
然后我們就可以開心的運行、點擊 ”動態(tài)加載組件“ 的按鈕了棚赔。帝簇。
不開心的是,報錯了忆嗜,相信我們可以得到這樣的報錯
原來動態(tài)加載的組件必須聲明在特性模塊的 entryComponents 中,下面是angular官網(wǎng)對于entryComponents的說明
Specifies a list of components that should be compiled when this module is defined. For each component listed here, Angular will create a ComponentFactory
and store it in the ComponentFactoryResolver
.
就是說此處聲明的組件 Angular 都會創(chuàng)建一個ComponentFactory并將其存儲在ComponentFactoryResolver中崎岂,也就是動態(tài)加載必需的步驟捆毫。
所以我們將其加到特性模塊 entryComponents中
然后我們又可以開心地運行,點擊了冲甘。绩卤。。
然而這次我們又得到另一個錯誤了
意思就是DY1Component 還沒有聲明 — — ||| 江醇。 所以還需要將要動態(tài)加載的組件聲明為是此特性模塊的組件濒憋,如圖
然后這次可以開心的看例子了
對于這個方案,上面展示的是最簡單的一面陶夜,沒有考慮到項目的優(yōu)化還有代碼的簡潔凛驮。項目的優(yōu)化問題體現(xiàn)在后期項目的壓縮和預編譯中,如果我們是想要通過組件的名字來動態(tài)加載組件条辟,可能在優(yōu)化有組件的名字都被統(tǒng)一壓縮成一個字母黔夭,所以會導致找不到組件的問題;代碼的簡潔呢因人而異羽嫡,我是喜歡保持根 module 的簡潔本姥,所以有必要另起文件來作為媒介。
說一說改進的方式
新建一個管理類
導入動態(tài)加載的組件杭棵,聲明一個供名字獲取組件的變量婚惫,還有一個供根模塊聲明的變量
接下來就可以修改之前的引用代碼了:
可以看到當動態(tài)組件多的時候,根 module還是能保持簡潔魂爪。并且在根組件中可以通過名字獲取對應(yīng)的組件先舷,不怕后期項目優(yōu)化的影響。
(二)動態(tài)創(chuàng)建模塊的方式來加載動態(tài)創(chuàng)建的組件
不同的需求有不同的方案滓侍,對于上面的需求與方案無疑是很適合的密浑,但是需要我們先創(chuàng)建好組件,再聲明到根 module 中粗井,至少缺失了一些靈活性尔破。
現(xiàn)在我又有了另外一個項目場景:我拖動生成了UI 控件街图,只是為了展示對應(yīng)的樣式,UI控件是需要第三方環(huán)境支持的懒构,所以我需要加工拖動的數(shù)據(jù)餐济,拋給后臺處理舟奠,后臺返回包含表示模板议双、組件的字符串的 JSON 數(shù)據(jù)回來,然后我通知另外一個網(wǎng)站卫袒,此網(wǎng)站包含了第三方環(huán)境秩霍,讓他們?nèi)討B(tài)創(chuàng)建這些組件篙悯。一句話概括,就是我要動態(tài)創(chuàng)建不存在的組件而不是已經(jīng)聲明的組件铃绒。
要完成動態(tài)創(chuàng)建組件的鸽照,我們得先看看相關(guān)的api
- Compiler:用于在運行時運行angular編譯器來創(chuàng)建 ComponentFactory 的服務(wù),然后可以使用它來創(chuàng)建和呈現(xiàn)組件實例
其實最簡單的了解這一個就能實現(xiàn)了颠悬,我們知道 容器創(chuàng)建和呈現(xiàn)組件的函數(shù)需要一個 ComponentFactory矮燎,而Compiler能夠在運行時動態(tài)創(chuàng)建一個ComponentFactory,那就十分符合需求了赔癌。
我新增了一些代碼
這些都是在根組件中的代碼诞外,引入了 Compiler 服務(wù)。新增一個 createModule函數(shù)灾票,通過 Component 和 NgModule 修飾器動態(tài)創(chuàng)建新的組件和模塊峡谊,然后調(diào)用 Compiler 的 compileModuleAndAllComponentsSync 方法獲取一個新的ComponentFactory。然后容器的呈現(xiàn)還是一樣刊苍,直接 createComponent靖苇。在模板的按鈕中設(shè)置對應(yīng)的動作后就可以開心的運行和點擊了。班缰。
這次十分順利贤壁,沒有任何報錯。
可能我們會有疑惑埠忘,這個不也還是事先聲明了動態(tài)的組件脾拆。
其實只是我這個例子展示的問題,這里的 模板莹妒、組件類都是可以動態(tài)當成參數(shù)設(shè)置的名船,下面此圖是我在項目中用到的
aot build的問題
使用了這種方案,在項目正常開發(fā)預覽的時候是沒有任何問題的旨怠,但是當我構(gòu)建項目(aot 等)運行的時候渠驼,就會出現(xiàn)下面報錯
看錯誤可以知道在項目構(gòu)建后是缺失angular編譯器的,原因就是使用了AOT后已經(jīng)是預編譯了鉴腻,編譯器就會從中移除迷扇。為了解決這一問題百揭,我們可以在這個把 compiler 編譯器在構(gòu)建的時候打到當前的特性包中
通過此處代碼就可以將一個angular 編譯器打到當前包中。再次運行build構(gòu)建蜓席,可以看到已經(jīng)沒有之前的錯誤了器一,但是缺因此得到另一個錯誤
此錯誤暫時沒能解決,待更新厨内!