NgModules
可以幫你把應(yīng)用組織成一些緊密相關(guān)的代碼塊。
這里回答的是開(kāi)發(fā)者常問(wèn)起的關(guān)于 NgModule
的設(shè)計(jì)與實(shí)現(xiàn)問(wèn)題。
把可聲明的類(組件、指令和管道)添加到 declarations
列表中。
這些類只能在應(yīng)用程序的一個(gè)并且只有一個(gè)模塊中聲明。 只有當(dāng)它們從屬于某個(gè)模塊時(shí),才能把在此模塊中聲明它們。
聲明的就是組件、指令和管道這些可以被加到模塊的 declarations
列表中的類。它們也是所有能被加到 declarations
中的類。
只有可聲明的類才能加到模塊的 declarations
列表中。
不要聲明:
@NgModule
)還是第三方模塊。@angular/forms
的 FORMS_DIRECTIVES
,因?yàn)?FormsModule
已經(jīng)聲明過(guò)它們了。
AppComponent
經(jīng)常被同時(shí)列在 declarations
和 bootstrap
中。 另外你還可能看到 HeroComponent
被同時(shí)列在 declarations
、exports
和 entryComponent
中。
這看起來(lái)是多余的,不過(guò)這些函數(shù)具有不同的功能,從它出現(xiàn)在一個(gè)列表中無(wú)法推斷出它也應(yīng)該在另一個(gè)列表中。
AppComponent
可能被聲明在此模塊中,但可能不是引導(dǎo)組件。AppComponent
可能在此模塊中引導(dǎo),但可能是由另一個(gè)特性模塊聲明的。
這個(gè)錯(cuò)誤通常意味著你或者忘了聲明指令“x”
,或者你沒(méi)有導(dǎo)入“x”
所屬的模塊。
如果
“x”
其實(shí)不是屬性,或者是組件的私有屬性(比如它不帶@Input
或@Output
裝飾器),那么你也同樣會(huì)遇到這個(gè)錯(cuò)誤。
導(dǎo)入你需要在當(dāng)前模塊的組件模板中使用的那些公開(kāi)的(被導(dǎo)出的)可聲明類。
這意味著要從 @angular/common
中導(dǎo)入 CommonModule
才能訪問(wèn) Angular 的內(nèi)置指令,比如 NgIf
和 NgFor
。 你可以直接導(dǎo)入它或者從重新導(dǎo)出過(guò)該模塊的其它模塊中導(dǎo)入它。
如果你的組件有 [(ngModel)]
雙向綁定表達(dá)式,就要從 @angular/forms
中導(dǎo)入 FormsModule
。
如果當(dāng)前模塊中的組件包含了共享模塊和特性模塊中的組件、指令和管道,就導(dǎo)入這些模塊。
只能在根模塊 AppModule
中導(dǎo)入 BrowserModule
。
幾乎所有要在瀏覽器中使用的應(yīng)用的根模塊(AppModule)都應(yīng)該從 @angular/platform-browser
中導(dǎo)入 BrowserModule
。
BrowserModule
提供了啟動(dòng)和運(yùn)行瀏覽器應(yīng)用的那些基本的服務(wù)提供者。
BrowserModule
還從 @angular/common
中重新導(dǎo)出了 CommonModule
,這意味著 AppModule
中的組件也同樣可以訪問(wèn)那些每個(gè)應(yīng)用都需要的 Angular 指令,如 NgIf
和 NgFor
。
在其它任何模塊中都不要導(dǎo)入BrowserModule
。 特性模塊和惰性加載模塊應(yīng)該改成導(dǎo)入 CommonModule
。 它們需要通用的指令。它們不需要重新初始化全應(yīng)用級(jí)的提供者。
沒(méi)有任何問(wèn)題。當(dāng)三個(gè)模塊全都導(dǎo)入模塊'A'
時(shí),Angular 只會(huì)首次遇到時(shí)加載一次模塊'A'
,之后就不會(huì)這么做了。
無(wú)論 A
出現(xiàn)在所導(dǎo)入模塊的哪個(gè)層級(jí),都會(huì)如此。 如果模塊'B'
導(dǎo)入模塊'A'
、模塊'C'
導(dǎo)入模塊'B'
,模塊'D'
導(dǎo)入 [C, B, A]
,那么'D'
會(huì)觸發(fā)模塊'C'
的加載,'C'
會(huì)觸發(fā)'B'
的加載,而'B'
會(huì)加載'A'
。 當(dāng) Angular 在'D'
中想要獲取'B'
和'A'
時(shí),這兩個(gè)模塊已經(jīng)被緩存過(guò)了,可以立即使用。
Angular 不允許模塊之間出現(xiàn)循環(huán)依賴,所以不要讓模塊'A'
導(dǎo)入模塊'B'
,而模塊'B'
又導(dǎo)入模塊'A'
。
特性模塊中導(dǎo)入 CommonModule
可以讓它能用在任何目標(biāo)平臺(tái)上,不僅是瀏覽器。那些跨平臺(tái)庫(kù)的作者應(yīng)該喜歡這種方式的。
導(dǎo)出那些其它模塊希望在自己的模板中引用的可聲明類。這些也是你的公共類。 如果你不導(dǎo)出某個(gè)類,它就是私有的,只對(duì)當(dāng)前模塊中聲明的其它組件可見(jiàn)。
你可以導(dǎo)出任何可聲明類(組件、指令和管道),而不用管它是聲明在當(dāng)前模塊中還是某個(gè)導(dǎo)入的模塊中。
你可以重新導(dǎo)出整個(gè)導(dǎo)入過(guò)的模塊,這將導(dǎo)致重新導(dǎo)出它們導(dǎo)出的所有類。重新導(dǎo)出的模塊甚至不用先導(dǎo)入。
不要導(dǎo)出:
HttpClientModule
,因?yàn)樗粚?dǎo)出任何東西。 它唯一的用途是一起把 http 的那些服務(wù)提供者添加到應(yīng)用中。毫無(wú)疑問(wèn)!
模塊是從其它模塊中選取類并把它們重新導(dǎo)出成統(tǒng)一、便利的新模塊的最佳方式。
模塊可以重新導(dǎo)出其它模塊,這會(huì)導(dǎo)致重新導(dǎo)出它們導(dǎo)出的所有類。 Angular 自己的 BrowserModule
就重新導(dǎo)出了一組模塊,例如:
exports: [CommonModule, ApplicationModule]
模塊還能導(dǎo)出一個(gè)組合,它可以包含自己的聲明、某些導(dǎo)入的類以及導(dǎo)入的模塊。
不要費(fèi)心去導(dǎo)出純服務(wù)類。 純服務(wù)類的模塊不會(huì)導(dǎo)出任何可供其它模塊使用的可聲明類。 例如,不用重新導(dǎo)出 HttpClientModule
,因?yàn)樗鼪](méi)有導(dǎo)出任何東西。 它唯一的用途是把那些 http
服務(wù)提供者一起添加到應(yīng)用中。
靜態(tài)方法 forRoot()
是一個(gè)約定,它可以讓開(kāi)發(fā)人員更輕松的配置模塊的想要單例使用的服務(wù)及其提供者。RouterModule.forRoot()
就是一個(gè)很好的例子。
應(yīng)用把一個(gè) Routes
對(duì)象傳給 RouterModule.forRoot()
,為的就是使用路由配置全應(yīng)用級(jí)的 Router
服務(wù)。 RouterModule.forRoot()
返回一個(gè)ModuleWithProviders
對(duì)象。 你把這個(gè)結(jié)果添加到根模塊 AppModule
的 imports
列表中。
只能在應(yīng)用的根模塊 AppModule
中調(diào)用并導(dǎo)入 forRoot()
的結(jié)果。 在其它模塊,特別是惰性加載模塊中,不要導(dǎo)入它。 要了解關(guān)于 forRoot()
的更多信息,參見(jiàn)單例服務(wù)一章的 the forRoot()
模式部分。
對(duì)于服務(wù)來(lái)說(shuō),除了可以使用 forRoot()
外,更好的方式是在該服務(wù)的 @Injectable()
裝飾器中指定 providedIn
: 'root'
,它讓該服務(wù)自動(dòng)在全應(yīng)用級(jí)可用,這樣它也就默認(rèn)是單例的。
RouterModule
也提供了靜態(tài)方法 forChild()
,用于配置惰性加載模塊的路由。
forRoot()
和 forChild()
都是約定俗成的方法名,它們分別用于在根模塊和特性模塊中配置服務(wù)。
當(dāng)你寫(xiě)類似的需要可配置的服務(wù)提供者時(shí),請(qǐng)遵循這個(gè)約定。
列在引導(dǎo)模塊的 @NgModule.providers
中的服務(wù)提供者具有全應(yīng)用級(jí)作用域。 往 NgModule.providers
中添加服務(wù)提供者將導(dǎo)致該服務(wù)被發(fā)布到整個(gè)應(yīng)用中。
當(dāng)你導(dǎo)入一個(gè)模塊時(shí),Angular 就會(huì)把該模塊的服務(wù)提供者(也就是它的 providers
列表中的內(nèi)容)加入該應(yīng)用的根注入器中。
這會(huì)讓該提供者對(duì)應(yīng)用中所有知道該提供者令牌(token
)的類都可見(jiàn)。
通過(guò) NgModule
導(dǎo)入來(lái)實(shí)現(xiàn)可擴(kuò)展性是 NgModule
體系的主要設(shè)計(jì)目標(biāo)。 把 NgModule
的提供者并入應(yīng)用程序的注入器可以讓庫(kù)模塊使用新的服務(wù)來(lái)強(qiáng)化應(yīng)用程序變得更容易。 只要添加一次 HttpClientModule
,那么應(yīng)用中的每個(gè)組件就都可以發(fā)起 Http
請(qǐng)求了。
不過(guò),如果你期望模塊的服務(wù)只對(duì)那個(gè)特性模塊內(nèi)部聲明的組件可見(jiàn),那么這可能會(huì)帶來(lái)一些不受歡迎的意外。 如果 HeroModule
提供了一個(gè) HeroService
,并且根模塊 AppModule
導(dǎo)入了 HeroModule
,那么任何知道 HeroService
類型的類都可能注入該服務(wù),而不僅是在 HeroModule
中聲明的那些類。
要限制對(duì)某個(gè)服務(wù)的訪問(wèn),可以考慮惰性加載提供該服務(wù)的 NgModule
。
和啟動(dòng)時(shí)就加載的模塊中的提供者不同,惰性加載模塊中的提供者是局限于模塊的。
當(dāng) Angular 路由器惰性加載一個(gè)模塊時(shí),它創(chuàng)建了一個(gè)新的運(yùn)行環(huán)境。 那個(gè)環(huán)境擁有自己的注入器,它是應(yīng)用注入器的直屬子級(jí)。
路由器把該惰性加載模塊的提供者和它導(dǎo)入的模塊的提供者添加到這個(gè)子注入器中。
這些提供者不會(huì)被擁有相同令牌的應(yīng)用級(jí)別提供者的變化所影響。 當(dāng)路由器在惰性加載環(huán)境中創(chuàng)建組件時(shí),Angular 優(yōu)先使用惰性加載模塊中的服務(wù)實(shí)例,而不是來(lái)自應(yīng)用的根注入器的。
當(dāng)同時(shí)加載了兩個(gè)導(dǎo)入的模塊,它們都列出了使用同一個(gè)令牌的提供者時(shí),后導(dǎo)入的模塊會(huì)“獲勝”,這是因?yàn)檫@兩個(gè)提供者都被添加到了同一個(gè)注入器中。
當(dāng) Angular 嘗試根據(jù)令牌注入服務(wù)時(shí),它使用第二個(gè)提供者來(lái)創(chuàng)建并交付服務(wù)實(shí)例。
每個(gè)注入了該服務(wù)的類獲得的都是由第二個(gè)提供者創(chuàng)建的實(shí)例。 即使是聲明在第一個(gè)模塊中的類,它取得的實(shí)例也是來(lái)自第二個(gè)提供者的。
如果模塊 A
提供了一個(gè)使用令牌'X'
的服務(wù),并且導(dǎo)入的模塊 B
也用令牌'X'
提供了一個(gè)服務(wù),那么模塊 A
中定義的服務(wù)“獲勝”了。
由根 AppModule
提供的服務(wù)相對(duì)于所導(dǎo)入模塊中提供的服務(wù)有優(yōu)先權(quán)。換句話說(shuō):AppModule
總會(huì)獲勝。
如果一個(gè)模塊在應(yīng)用程序啟動(dòng)時(shí)就加載,它的 @NgModule.providers
具有全應(yīng)用級(jí)作用域。 它們也可用于整個(gè)應(yīng)用的注入中。
導(dǎo)入的提供者很容易被由其它導(dǎo)入模塊中的提供者替換掉。 這雖然是故意這樣設(shè)計(jì)的,但是也可能引起意料之外的結(jié)果。
作為一個(gè)通用的規(guī)則,應(yīng)該只導(dǎo)入一次帶提供者的模塊,最好在應(yīng)用的根模塊中。 那里也是配置、包裝和改寫(xiě)這些服務(wù)的最佳位置。
假設(shè)模塊需要一個(gè)定制過(guò)的 HttpBackend
,它為所有的 Http
請(qǐng)求添加一個(gè)特別的請(qǐng)求頭。 如果應(yīng)用中其它地方的另一個(gè)模塊也定制了 HttpBackend
或僅僅導(dǎo)入了 HttpClientModule
,它就會(huì)改寫(xiě)當(dāng)前模塊的 HttpBackend
提供者,丟掉了這個(gè)特別的請(qǐng)求頭。 這樣服務(wù)器就會(huì)拒絕來(lái)自該模塊的請(qǐng)求。
要消除這個(gè)問(wèn)題,就只能在應(yīng)用的根模塊 AppModule
中導(dǎo)入 HttpClientModule
。
如果你必須防范這種“提供者腐化”現(xiàn)象,那就不要依賴于“啟動(dòng)時(shí)加載”模塊的 providers
。
只要可能,就讓模塊惰性加載。 Angular 給了惰性加載模塊自己的子注入器。 該模塊中的提供者只對(duì)由該注入器創(chuàng)建的組件樹(shù)可見(jiàn)。
如果你必須在應(yīng)用程序啟動(dòng)時(shí)主動(dòng)加載該模塊,就改成在組件中提供該服務(wù)。
繼續(xù)看這個(gè)例子,假設(shè)某個(gè)模塊的組件真的需要一個(gè)私有的、自定義的 HttpBackend
。
那就創(chuàng)建一個(gè)“頂層組件”來(lái)扮演該模塊中所有組件的根。 把這個(gè)自定義的 HttpBackend
提供者添加到這個(gè)頂層組件的 providers
列表中,而不是該模塊的 providers
中。 回憶一下,Angular 會(huì)為每個(gè)組件實(shí)例創(chuàng)建一個(gè)子注入器,并使用組件自己的 providers
來(lái)配置這個(gè)注入器。
當(dāng)該組件的子組件想要一個(gè) HttpBackend
服務(wù)時(shí),Angular 會(huì)提供一個(gè)局部的 HttpBackend
服務(wù),而不是應(yīng)用的根注入器創(chuàng)建的那個(gè)。 子組件將正確發(fā)起 http 請(qǐng)求,而不管其它模塊對(duì) HttpBackend
做了什么。
確保把模塊中的組件都創(chuàng)建成這個(gè)頂層組件的子組件。
你可以把這些子組件都嵌在頂層組件的模板中?;蛘?,給頂層組件一個(gè) <router-outlet>
,讓它作為路由的宿主。 定義子路由,并讓路由器把模塊中的組件加載進(jìn)該路由出口(outlet
)中。
雖然通過(guò)在惰性加載模塊中或組件中提供某個(gè)服務(wù)來(lái)限制它的訪問(wèn)都是可行的方式,但在組件中提供服務(wù)可能導(dǎo)致這些服務(wù)出現(xiàn)多個(gè)實(shí)例。因此,應(yīng)該優(yōu)先使用惰性加載的方式。
通過(guò)在服務(wù)的 @Injectable()
裝飾器中(例如服務(wù))指定 providedIn: 'root'
來(lái)定義全應(yīng)用級(jí)提供者,或者 InjectionToken
的構(gòu)造器(例如提供令牌的地方),都可以定義全應(yīng)用級(jí)提供者。 通過(guò)這種方式創(chuàng)建的服務(wù)提供者會(huì)自動(dòng)在整個(gè)應(yīng)用中可用,而不用把它列在任何模塊中。
如果某個(gè)提供者不能用這種方式配置(可能因?yàn)樗鼪](méi)有有意義的默認(rèn)值),那就在根模塊 AppModule
中注冊(cè)這些全應(yīng)用級(jí)服務(wù),而不是在 AppComponent
中。
惰性加載模塊及其組件可以注入 AppModule
中的服務(wù),卻不能注入 AppComponent
中的。
只有當(dāng)該服務(wù)必須對(duì) AppComponent
組件樹(shù)之外的組件不可見(jiàn)時(shí),才應(yīng)該把服務(wù)注冊(cè)進(jìn) AppComponent
的 providers
中。 這是一個(gè)非常罕見(jiàn)的異常用法。
更一般地說(shuō),優(yōu)先把提供者注冊(cè)進(jìn)模塊中,而不是組件中。
Angular 把所有啟動(dòng)期模塊的提供者都注冊(cè)進(jìn)了應(yīng)用的根注入器中。 這些服務(wù)是由根注入器中的提供者創(chuàng)建的,并且在整個(gè)應(yīng)用中都可用。 它們具有應(yīng)用級(jí)作用域。
某些服務(wù)(比如 Router
)只有當(dāng)注冊(cè)進(jìn)應(yīng)用的根注入器時(shí)才能正常工作。
相反,Angular 使用 AppComponent
自己的注入器注冊(cè)了 AppComponent
的提供者。 AppComponent
服務(wù)只在該組件及其子組件樹(shù)中才能使用。 它們具有組件級(jí)作用域。
AppComponent
的注入器是根注入器的子級(jí),注入器層次中的下一級(jí)。 這對(duì)于沒(méi)有路由器的應(yīng)用來(lái)說(shuō)幾乎是整個(gè)應(yīng)用了。 但對(duì)那些帶路由的應(yīng)用,路由操作位于頂層,那里不存在 AppComponent
服務(wù)。這意味著惰性加載模塊不能使用它們。
提供者應(yīng)該使用 @Injectable
語(yǔ)法進(jìn)行配置。只要可能,就應(yīng)該把它們?cè)趹?yīng)用的根注入器中提供(providedIn: 'root'
)。 如果它們只被惰性加載的上下文中使用,那么這種方式配置的服務(wù)就是惰性加載的。
如果要由消費(fèi)方來(lái)決定是否把它作為全應(yīng)用級(jí)提供者,那么就要在模塊中(@NgModule.providers
)注冊(cè)提供者,而不是組件中(@Component.providers
)。
當(dāng)你必須把服務(wù)實(shí)例的范圍限制到某個(gè)組件及其子組件樹(shù)時(shí),就把提供者注冊(cè)到該組件中。 指令的提供者也同樣照此處理。
例如,如果英雄編輯組件需要自己私有的緩存英雄服務(wù)實(shí)例,那就應(yīng)該把 HeroService
注冊(cè)進(jìn) HeroEditorComponent
中。 這樣,每個(gè)新的 HeroEditorComponent
的實(shí)例都會(huì)得到一份自己的緩存服務(wù)實(shí)例。 編輯器的改動(dòng)只會(huì)作用于它自己的服務(wù),而不會(huì)影響到應(yīng)用中其它地方的英雄實(shí)例。
總是在根模塊 AppModule
中注冊(cè)全應(yīng)用級(jí)服務(wù),而不要在根組件 AppComponent
中。
急性加載的場(chǎng)景
當(dāng)急性加載的模塊提供了服務(wù)時(shí),比如 UserService
,該服務(wù)是在全應(yīng)用級(jí)可用的。如果根模塊提供了 UserService
,并導(dǎo)入了另一個(gè)也提供了同一個(gè) UserService
的模塊,Angular 就會(huì)把它們中的一個(gè)注冊(cè)進(jìn)應(yīng)用的根注入器中(參見(jiàn)如果兩次導(dǎo)入了同一個(gè)模塊會(huì)怎樣?)。
然后,當(dāng)某些組件注入 UserService
時(shí),Angular 就會(huì)發(fā)現(xiàn)它已經(jīng)在應(yīng)用的根注入器中了,并交付這個(gè)全應(yīng)用級(jí)的單例服務(wù)。這樣不會(huì)出現(xiàn)問(wèn)題。
惰性加載場(chǎng)景
現(xiàn)在,考慮一個(gè)惰性加載的模塊,它也提供了一個(gè)名叫 UserService
的服務(wù)。
當(dāng)路由器準(zhǔn)備惰性加載 HeroModule
的時(shí)候,它會(huì)創(chuàng)建一個(gè)子注入器,并且把 UserService
的提供者注冊(cè)到那個(gè)子注入器中。子注入器和根注入器是不同的。
當(dāng) Angular 創(chuàng)建一個(gè)惰性加載的 HeroComponent
時(shí),它必須注入一個(gè) UserService
。 這次,它會(huì)從惰性加載模塊的子注入器中查找 UserService
的提供者,并用它創(chuàng)建一個(gè) UserService
的新實(shí)例。 這個(gè) UserService
實(shí)例與 Angular 在主動(dòng)加載的組件中注入的那個(gè)全應(yīng)用級(jí)單例對(duì)象截然不同。
這個(gè)場(chǎng)景導(dǎo)致你的應(yīng)用每次都創(chuàng)建一個(gè)新的服務(wù)實(shí)例,而不是使用單例的服務(wù)。
Angular 會(huì)把 @NgModule.providers
中的提供者添加到應(yīng)用的根注入器中…… 除非該模塊是惰性加載的,這種情況下,Angular 會(huì)創(chuàng)建一子注入器,并且把該模塊的提供者添加到這個(gè)子注入器中。
這意味著模塊的行為將取決于它是在應(yīng)用啟動(dòng)期間加載的還是后來(lái)惰性加載的。如果疏忽了這一點(diǎn),可能導(dǎo)致嚴(yán)重后果。
為什么 Angular 不能像主動(dòng)加載模塊那樣把惰性加載模塊的提供者也添加到應(yīng)用程序的根注入器中呢?為什么會(huì)出現(xiàn)這種不一致?
歸根結(jié)底,這來(lái)自于 Angular 依賴注入系統(tǒng)的一個(gè)基本特征: 在注入器還沒(méi)有被第一次使用之前,可以不斷為其添加提供者。 一旦注入器已經(jīng)創(chuàng)建和開(kāi)始交付服務(wù),它的提供者列表就被凍結(jié)了,不再接受新的提供者。
當(dāng)應(yīng)用啟動(dòng)時(shí),Angular 會(huì)首先使用所有主動(dòng)加載模塊中的提供者來(lái)配置根注入器,這發(fā)生在它創(chuàng)建第一個(gè)組件以及注入任何服務(wù)之前。 一旦應(yīng)用開(kāi)始工作,應(yīng)用的根注入器就不再接受新的提供者了。
之后,應(yīng)用邏輯開(kāi)始惰性加載某個(gè)模塊。 Angular 必須把這個(gè)惰性加載模塊中的提供者添加到某個(gè)注入器中。 但是它無(wú)法將它們添加到應(yīng)用的根注入器中,因?yàn)楦⑷肫饕呀?jīng)不再接受新的提供者了。 于是,Angular 在惰性加載模塊的上下文中創(chuàng)建了一個(gè)新的子注入器。
某些模塊及其服務(wù)只能被根模塊 AppModule
加載一次。 在惰性加載模塊中再次導(dǎo)入這個(gè)模塊會(huì)導(dǎo)致錯(cuò)誤的行為,這個(gè)錯(cuò)誤可能非常難于檢測(cè)和診斷。
為了防范這種風(fēng)險(xiǎn),可以寫(xiě)一個(gè)構(gòu)造函數(shù),它會(huì)嘗試從應(yīng)用的根注入器中注入該模塊或服務(wù)。如果這種注入成功了,那就說(shuō)明這個(gè)類是被第二次加載的,你就可以拋出一個(gè)錯(cuò)誤,或者采取其它挽救措施。
某些 NgModule
(例如 BrowserModule
)就實(shí)現(xiàn)了那樣一個(gè)守衛(wèi)。 下面是一個(gè)名叫 GreetingModule
的 NgModule
的 自定義構(gòu)造函數(shù)。
Path:"src/app/greeting/greeting.module.ts (Constructor)" 。
constructor (@Optional() @SkipSelf() parentModule?: GreetingModule) {
if (parentModule) {
throw new Error(
'GreetingModule is already loaded. Import it in the AppModule only');
}
}
Angular 根據(jù)組件類型命令式加載的組件是入口組件.
而通過(guò)組件選擇器聲明式加載的組件則不是入口組件。
Angular 會(huì)聲明式的加載組件,它使用組件的選擇器在模板中定位元素。 然后,Angular 會(huì)創(chuàng)建該組件的 HTML 表示,并把它插入 DOM 中所選元素的內(nèi)部。它們不是入口組件。
而用于引導(dǎo)的根 AppComponent
則是一個(gè)入口組件。 雖然它的選擇器匹配了 "index.html" 中的一個(gè)元素,但是 "index.html" 并不是組件模板,而且 AppComponent
選擇器也不會(huì)在任何組件模板中出現(xiàn)。
在路由定義中用到的組件也同樣是入口組件。 路由定義根據(jù)類型來(lái)引用組件。 路由器會(huì)忽略路由組件的選擇器(即使它有選擇器),并且把該組件動(dòng)態(tài)加載到 RouterOutlet
中。
引導(dǎo)組件是入口組件的一種。 它是被 Angular 的引導(dǎo)(應(yīng)用啟動(dòng))過(guò)程加載到 DOM 中的入口組件。 其它入口組件則是被其它方式動(dòng)態(tài)加載的,比如被路由器加載。
@NgModule.bootstrap
屬性告訴編譯器這是一個(gè)入口組件,同時(shí)它應(yīng)該生成一些代碼來(lái)用該組件引導(dǎo)此應(yīng)用。
不需要把組件同時(shí)列在 bootstrap
和 entryComponent
列表中 —— 雖然這樣做也沒(méi)壞處。
大多數(shù)應(yīng)用開(kāi)發(fā)者都不需要把組件添加到 entryComponents
中。
Angular 會(huì)自動(dòng)把恰當(dāng)?shù)慕M件添加到入口組件中。 列在 @NgModule.bootstrap
中的組件會(huì)自動(dòng)加入。 由路由配置引用到的組件會(huì)被自動(dòng)加入。 用這兩種機(jī)制添加的組件在入口組件中占了絕大多數(shù)。
如果你的應(yīng)用要用其它手段來(lái)根據(jù)類型引導(dǎo)或動(dòng)態(tài)加載組件,那就得把它顯式添加到 entryComponents
中。
雖然把組件加到這個(gè)列表中也沒(méi)什么壞處,不過(guò)最好還是只添加真正的入口組件。 不要添加那些被其它組件的模板引用過(guò)的組件。
原因在于搖樹(shù)優(yōu)化。對(duì)于產(chǎn)品化應(yīng)用,你會(huì)希望加載盡可能小而快的代碼。 代碼中應(yīng)該僅僅包括那些實(shí)際用到的類。 它應(yīng)該排除那些從未用過(guò)的組件,無(wú)論該組件是否被聲明過(guò)。
事實(shí)上,大多數(shù)庫(kù)中聲明和導(dǎo)出的組件你都用不到。 如果你從未引用它們,那么搖樹(shù)優(yōu)化器就會(huì)從最終的代碼包中把這些組件砍掉。
如果Angular 編譯器為每個(gè)聲明的組件都生成了代碼,那么搖樹(shù)優(yōu)化器的作用就沒(méi)有了。
所以,編譯器轉(zhuǎn)而采用一種遞歸策略,它只為你用到的那些組件生成代碼。
編譯器從入口組件開(kāi)始工作,為它在入口組件的模板中找到的那些組件生成代碼,然后又為在這些組件中的模板中發(fā)現(xiàn)的組件生成代碼,以此類推。 當(dāng)這個(gè)過(guò)程結(jié)束時(shí),它就已經(jīng)為每個(gè)入口組件以及從入口組件可以抵達(dá)的每個(gè)組件生成了代碼。
如果該組件不是入口組件或者沒(méi)有在任何模板中發(fā)現(xiàn)過(guò),編譯器就會(huì)忽略它。
每個(gè)應(yīng)用都不一樣。根據(jù)不同程度的經(jīng)驗(yàn),開(kāi)發(fā)者會(huì)做出不同的選擇。下列建議和指導(dǎo)原則廣受歡迎。
為那些可能會(huì)在應(yīng)用中到處使用的組件、指令和管道創(chuàng)建 SharedModule
。 這種模塊應(yīng)該只包含 declarations
,并且應(yīng)該導(dǎo)出幾乎所有 declarations
里面的聲明。
SharedModule
可以重新導(dǎo)出其它小部件模塊,比如 CommonModule
、FormsModule
和提供你廣泛使用的 UI 控件的那些模塊。
SharedModule
不應(yīng)該帶有 providers
,原因在前面解釋過(guò)了。 它的導(dǎo)入或重新導(dǎo)出的模塊中也不應(yīng)該有 providers
。 如果你要違背這條指導(dǎo)原則,請(qǐng)務(wù)必想清楚你在做什么,并要有充分的理由。
在任何特性模塊中(無(wú)論是你在應(yīng)用啟動(dòng)時(shí)主動(dòng)加載的模塊還是之后惰性加載的模塊),你都可以隨意導(dǎo)入這個(gè) SharedModule
。
特性模塊是你圍繞特定的應(yīng)用業(yè)務(wù)領(lǐng)域創(chuàng)建的模塊,比如用戶工作流、小工具集等。它們包含指定的特性,并為你的應(yīng)用提供支持,比如路由、服務(wù)、窗口部件等。 要對(duì)你的應(yīng)用中可能會(huì)有哪些特性模塊有個(gè)概念,考慮如果你要把與特定功能(比如搜索)有關(guān)的文件放進(jìn)一個(gè)目錄下,該目錄的內(nèi)容就可能是一個(gè)名叫 SearchModule
的特性模塊。 它將會(huì)包含構(gòu)成搜索功能的全部組件、路由和模板。
在 Angular 應(yīng)用中,NgModule
會(huì)和 JavaScript 的模塊一起工作。
在現(xiàn)代 JavaScript 中,每個(gè)文件都是模塊(參見(jiàn)模塊)。 在每個(gè)文件中,你要寫(xiě)一個(gè) export
語(yǔ)句將模塊的一部分公開(kāi)。
Angular 模塊是一個(gè)帶有 @NgModule
裝飾器的類,而 JavaScript 模塊則沒(méi)有。 Angular 的 NgModule
有自己的 imports
和 exports
來(lái)達(dá)到類似的目的。
你可以導(dǎo)入其它 NgModules
,以便在當(dāng)前模塊的組件模板中使用它們導(dǎo)出的類。 你可以導(dǎo)出當(dāng)前 NgModules
中的類,以便其它 NgModules
可以導(dǎo)入它們,并用在自己的組件模板中。
Angular 編譯器在組件模板內(nèi)查找其它組件、指令和管道。一旦找到了,那就是一個(gè)“模板引用”。
Angular 編譯器通過(guò)在一個(gè)模板的 HTML 中匹配組件或指令的選擇器(selector
),來(lái)查找組件或指令。
編譯器通過(guò)分析模板 HTML 中的管道語(yǔ)法中是否出現(xiàn)了特定的管道名來(lái)查找對(duì)應(yīng)的管道。
Angular 只查詢兩種組件、指令或管道: 1)那些在當(dāng)前模塊中聲明過(guò)的,以及 2)那些被當(dāng)前模塊導(dǎo)入的模塊所導(dǎo)出的。
Angular 編譯器會(huì)把你所編寫(xiě)的應(yīng)用代碼轉(zhuǎn)換成高性能的 JavaScript 代碼。 在編譯過(guò)程中,@NgModule
的元數(shù)據(jù)扮演了很重要的角色。
你寫(xiě)的代碼是無(wú)法直接執(zhí)行的。 比如組件。 組件有一個(gè)模板,其中包含了自定義元素、屬性型指令、Angular 綁定聲明和一些顯然不屬于原生 HTML 的古怪語(yǔ)法。
Angular 編譯器讀取模板的 HTML,把它和相應(yīng)的組件類代碼組合在一起,并產(chǎn)出組件工廠。
組件工廠為組件創(chuàng)建純粹的、100% JavaScript 的表示形式,它包含了 @Component
元數(shù)據(jù)中描述的一切:HTML、綁定指令、附屬的樣式等……
由于指令和管道都出現(xiàn)在組件模板中,*Angular
編譯器**也同樣會(huì)把它們組合進(jìn)編譯后的組件代碼中。
@NgModule
元數(shù)據(jù)告訴 Angular 編譯器要為當(dāng)前模塊編譯哪些組件,以及如何把當(dāng)前模塊和其它模塊鏈接起來(lái)。
更多建議: