Angular9 NgModule 常見問題

2020-07-03 14:40 更新

NgModules 可以幫你把應(yīng)用組織成一些緊密相關(guān)的代碼塊。

這里回答的是開發(fā)者常問起的關(guān)于 NgModule設(shè)計與實現(xiàn)問題。

我應(yīng)該把哪些類加到 declarations 中?

把可聲明的類(組件、指令和管道)添加到 declarations 列表中。

這些類只能在應(yīng)用程序的一個并且只有一個模塊中聲明。 只有當(dāng)它們從屬于某個模塊時,才能把在此模塊中聲明它們。

什么是可聲明的?

聲明的就是組件、指令和管道這些可以被加到模塊的 declarations 列表中的類。它們也是所有能被加到 declarations 中的類。

哪些類不應(yīng)該加到 declarations 中?

只有可聲明的類才能加到模塊的 declarations 列表中。

不要聲明:

  • 已經(jīng)在其它模塊中聲明過的類。無論它來自應(yīng)用自己的模塊(@NgModule)還是第三方模塊。

  • 從其它模塊中導(dǎo)入的指令。例如,不要聲明來自 @angular/formsFORMS_DIRECTIVES,因為 FormsModule 已經(jīng)聲明過它們了。

  • 模塊類。

  • 服務(wù)類

  • 非 Angular 的類和對象,比如:字符串、數(shù)字、函數(shù)、實體模型、配置、業(yè)務(wù)邏輯和輔助類。

為什么要把同一個組件聲明在不同的 NgModule 屬性中?

AppComponent 經(jīng)常被同時列在 declarationsbootstrap 中。 另外你還可能看到 HeroComponent 被同時列在 declarations、exportsentryComponent 中。

這看起來是多余的,不過這些函數(shù)具有不同的功能,從它出現(xiàn)在一個列表中無法推斷出它也應(yīng)該在另一個列表中。

  • AppComponent 可能被聲明在此模塊中,但可能不是引導(dǎo)組件。

  • AppComponent 可能在此模塊中引導(dǎo),但可能是由另一個特性模塊聲明的。

  • 某個組件可能是從另一個應(yīng)用模塊中導(dǎo)入的(所以你沒法聲明它)并且被當(dāng)前模塊重新導(dǎo)出。

  • 某個組件可能被導(dǎo)出,以便用在外部組件的模板中,也可能同時被一個彈出式對話框加載。

"Can't bind to 'x' since it isn't a known property of 'y'"是什么意思?

這個錯誤通常意味著你或者忘了聲明指令“x”,或者你沒有導(dǎo)入“x”所屬的模塊。

如果“x”其實不是屬性,或者是組件的私有屬性(比如它不帶 @Input@Output 裝飾器),那么你也同樣會遇到這個錯誤。

我應(yīng)該導(dǎo)入什么?

導(dǎo)入你需要在當(dāng)前模塊的組件模板中使用的那些公開的(被導(dǎo)出的)可聲明類。

這意味著要從 @angular/common 中導(dǎo)入 CommonModule 才能訪問 Angular 的內(nèi)置指令,比如 NgIfNgFor。 你可以直接導(dǎo)入它或者從重新導(dǎo)出過該模塊的其它模塊中導(dǎo)入它。

如果你的組件有 [(ngModel)] 雙向綁定表達(dá)式,就要從 @angular/forms 中導(dǎo)入 FormsModule。

如果當(dāng)前模塊中的組件包含了共享模塊和特性模塊中的組件、指令和管道,就導(dǎo)入這些模塊。

只能在根模塊 AppModule 中導(dǎo)入 BrowserModule。

我應(yīng)該導(dǎo)入 BrowserModule 還是 CommonModule?

幾乎所有要在瀏覽器中使用的應(yīng)用的根模塊(AppModule)都應(yīng)該從 @angular/platform-browser 中導(dǎo)入 BrowserModule。

BrowserModule 提供了啟動和運(yùn)行瀏覽器應(yīng)用的那些基本的服務(wù)提供者。

BrowserModule 還從 @angular/common 中重新導(dǎo)出了 CommonModule,這意味著 AppModule 中的組件也同樣可以訪問那些每個應(yīng)用都需要的 Angular 指令,如 NgIfNgFor。

在其它任何模塊中都不要導(dǎo)入BrowserModule。 特性模塊和惰性加載模塊應(yīng)該改成導(dǎo)入 CommonModule。 它們需要通用的指令。它們不需要重新初始化全應(yīng)用級的提供者。

如果我兩次導(dǎo)入同一個模塊會怎么樣?

沒有任何問題。當(dāng)三個模塊全都導(dǎo)入模塊'A'時,Angular 只會首次遇到時加載一次模塊'A',之后就不會這么做了。

無論 A 出現(xiàn)在所導(dǎo)入模塊的哪個層級,都會如此。 如果模塊'B'導(dǎo)入模塊'A'、模塊'C'導(dǎo)入模塊'B',模塊'D'導(dǎo)入 [C, B, A],那么'D'會觸發(fā)模塊'C'的加載,'C'會觸發(fā)'B'的加載,而'B'會加載'A'。 當(dāng) Angular 在'D'中想要獲取'B''A'時,這兩個模塊已經(jīng)被緩存過了,可以立即使用。

Angular 不允許模塊之間出現(xiàn)循環(huán)依賴,所以不要讓模塊'A'導(dǎo)入模塊'B',而模塊'B'又導(dǎo)入模塊'A'。

特性模塊中導(dǎo)入 CommonModule 可以讓它能用在任何目標(biāo)平臺上,不僅是瀏覽器。那些跨平臺庫的作者應(yīng)該喜歡這種方式的。

我應(yīng)該導(dǎo)出什么?

導(dǎo)出那些其它模塊希望在自己的模板中引用的可聲明類。這些也是你的公共類。 如果你不導(dǎo)出某個類,它就是私有的,只對當(dāng)前模塊中聲明的其它組件可見。

你可以導(dǎo)出任何可聲明類(組件、指令和管道),而不用管它是聲明在當(dāng)前模塊中還是某個導(dǎo)入的模塊中。

你可以重新導(dǎo)出整個導(dǎo)入過的模塊,這將導(dǎo)致重新導(dǎo)出它們導(dǎo)出的所有類。重新導(dǎo)出的模塊甚至不用先導(dǎo)入。

我不應(yīng)該導(dǎo)出什么?

不要導(dǎo)出:

  • 那些你只想在當(dāng)前模塊中聲明的那些組件中使用的私有組件、指令和管道。如果你不希望任何模塊看到它,就不要導(dǎo)出。

  • 不可聲明的對象,比如服務(wù)、函數(shù)、配置、實體模型等。

  • 那些只被路由器或引導(dǎo)函數(shù)動態(tài)加載的組件。 比如入口組件可能從來不會在其它組件的模板中出現(xiàn)。 導(dǎo)出它們沒有壞處,但也沒有好處。

  • 純服務(wù)模塊沒有公開(導(dǎo)出)的聲明。 例如,沒必要重新導(dǎo)出 HttpClientModule,因為它不導(dǎo)出任何東西。 它唯一的用途是一起把 http 的那些服務(wù)提供者添加到應(yīng)用中。

我可以重新導(dǎo)出類和模塊嗎?

毫無疑問!

模塊是從其它模塊中選取類并把它們重新導(dǎo)出成統(tǒng)一、便利的新模塊的最佳方式。

模塊可以重新導(dǎo)出其它模塊,這會導(dǎo)致重新導(dǎo)出它們導(dǎo)出的所有類。 Angular 自己的 BrowserModule 就重新導(dǎo)出了一組模塊,例如:

exports: [CommonModule, ApplicationModule]

模塊還能導(dǎo)出一個組合,它可以包含自己的聲明、某些導(dǎo)入的類以及導(dǎo)入的模塊。

不要費(fèi)心去導(dǎo)出純服務(wù)類。 純服務(wù)類的模塊不會導(dǎo)出任何可供其它模塊使用的可聲明類。 例如,不用重新導(dǎo)出 HttpClientModule,因為它沒有導(dǎo)出任何東西。 它唯一的用途是把那些 http 服務(wù)提供者一起添加到應(yīng)用中。

forRoot()方法是什么?

靜態(tài)方法 forRoot() 是一個約定,它可以讓開發(fā)人員更輕松的配置模塊的想要單例使用的服務(wù)及其提供者。RouterModule.forRoot() 就是一個很好的例子。

應(yīng)用把一個 Routes 對象傳給 RouterModule.forRoot(),為的就是使用路由配置全應(yīng)用級的 Router 服務(wù)。 RouterModule.forRoot() 返回一個ModuleWithProviders對象。 你把這個結(jié)果添加到根模塊 AppModuleimports 列表中。

只能在應(yīng)用的根模塊 AppModule 中調(diào)用并導(dǎo)入 forRoot() 的結(jié)果。 在其它模塊,特別是惰性加載模塊中,不要導(dǎo)入它。 要了解關(guān)于 forRoot() 的更多信息,參見單例服務(wù)一章的 the forRoot() 模式部分。

對于服務(wù)來說,除了可以使用 forRoot() 外,更好的方式是在該服務(wù)的 @Injectable() 裝飾器中指定 providedIn: 'root',它讓該服務(wù)自動在全應(yīng)用級可用,這樣它也就默認(rèn)是單例的。

RouterModule 也提供了靜態(tài)方法 forChild(),用于配置惰性加載模塊的路由。

forRoot()forChild() 都是約定俗成的方法名,它們分別用于在根模塊和特性模塊中配置服務(wù)。

當(dāng)你寫類似的需要可配置的服務(wù)提供者時,請遵循這個約定。

為什么服務(wù)提供者在特性模塊中的任何地方都是可見的?

列在引導(dǎo)模塊的 @NgModule.providers 中的服務(wù)提供者具有全應(yīng)用級作用域。 往 NgModule.providers 中添加服務(wù)提供者將導(dǎo)致該服務(wù)被發(fā)布到整個應(yīng)用中。

當(dāng)你導(dǎo)入一個模塊時,Angular 就會把該模塊的服務(wù)提供者(也就是它的 providers 列表中的內(nèi)容)加入該應(yīng)用的根注入器中。

這會讓該提供者對應(yīng)用中所有知道該提供者令牌(token)的類都可見。

通過 NgModule 導(dǎo)入來實現(xiàn)可擴(kuò)展性是 NgModule 體系的主要設(shè)計目標(biāo)。 把 NgModule 的提供者并入應(yīng)用程序的注入器可以讓庫模塊使用新的服務(wù)來強(qiáng)化應(yīng)用程序變得更容易。 只要添加一次 HttpClientModule,那么應(yīng)用中的每個組件就都可以發(fā)起 Http 請求了。

不過,如果你期望模塊的服務(wù)只對那個特性模塊內(nèi)部聲明的組件可見,那么這可能會帶來一些不受歡迎的意外。 如果 HeroModule 提供了一個 HeroService,并且根模塊 AppModule 導(dǎo)入了 HeroModule,那么任何知道 HeroService類型的類都可能注入該服務(wù),而不僅是在 HeroModule 中聲明的那些類。

要限制對某個服務(wù)的訪問,可以考慮惰性加載提供該服務(wù)的 NgModule。

為什么在惰性加載模塊中聲明的服務(wù)提供者只對該模塊自身可見?

和啟動時就加載的模塊中的提供者不同,惰性加載模塊中的提供者是局限于模塊的。

當(dāng) Angular 路由器惰性加載一個模塊時,它創(chuàng)建了一個新的運(yùn)行環(huán)境。 那個環(huán)境擁有自己的注入器,它是應(yīng)用注入器的直屬子級。

路由器把該惰性加載模塊的提供者和它導(dǎo)入的模塊的提供者添加到這個子注入器中。

這些提供者不會被擁有相同令牌的應(yīng)用級別提供者的變化所影響。 當(dāng)路由器在惰性加載環(huán)境中創(chuàng)建組件時,Angular 優(yōu)先使用惰性加載模塊中的服務(wù)實例,而不是來自應(yīng)用的根注入器的。

如果兩個模塊提供了同一個服務(wù)會怎么樣?

當(dāng)同時加載了兩個導(dǎo)入的模塊,它們都列出了使用同一個令牌的提供者時,后導(dǎo)入的模塊會“獲勝”,這是因為這兩個提供者都被添加到了同一個注入器中。

當(dāng) Angular 嘗試根據(jù)令牌注入服務(wù)時,它使用第二個提供者來創(chuàng)建并交付服務(wù)實例。

每個注入了該服務(wù)的類獲得的都是由第二個提供者創(chuàng)建的實例。 即使是聲明在第一個模塊中的類,它取得的實例也是來自第二個提供者的。

如果模塊 A 提供了一個使用令牌'X'的服務(wù),并且導(dǎo)入的模塊 B 也用令牌'X'提供了一個服務(wù),那么模塊 A 中定義的服務(wù)“獲勝”了。

由根 AppModule 提供的服務(wù)相對于所導(dǎo)入模塊中提供的服務(wù)有優(yōu)先權(quán)。換句話說:AppModule 總會獲勝。

我應(yīng)該如何把服務(wù)的范圍限制到模塊中?

如果一個模塊在應(yīng)用程序啟動時就加載,它的 @NgModule.providers 具有全應(yīng)用級作用域。 它們也可用于整個應(yīng)用的注入中。

導(dǎo)入的提供者很容易被由其它導(dǎo)入模塊中的提供者替換掉。 這雖然是故意這樣設(shè)計的,但是也可能引起意料之外的結(jié)果。

作為一個通用的規(guī)則,應(yīng)該只導(dǎo)入一次帶提供者的模塊,最好在應(yīng)用的根模塊中。 那里也是配置、包裝和改寫這些服務(wù)的最佳位置。

假設(shè)模塊需要一個定制過的 HttpBackend,它為所有的 Http 請求添加一個特別的請求頭。 如果應(yīng)用中其它地方的另一個模塊也定制了 HttpBackend 或僅僅導(dǎo)入了 HttpClientModule,它就會改寫當(dāng)前模塊的 HttpBackend 提供者,丟掉了這個特別的請求頭。 這樣服務(wù)器就會拒絕來自該模塊的請求。

要消除這個問題,就只能在應(yīng)用的根模塊 AppModule 中導(dǎo)入 HttpClientModule

如果你必須防范這種“提供者腐化”現(xiàn)象,那就不要依賴于“啟動時加載”模塊的 providers。

只要可能,就讓模塊惰性加載。 Angular 給了惰性加載模塊自己的子注入器。 該模塊中的提供者只對由該注入器創(chuàng)建的組件樹可見。

如果你必須在應(yīng)用程序啟動時主動加載該模塊,就改成在組件中提供該服務(wù)。

繼續(xù)看這個例子,假設(shè)某個模塊的組件真的需要一個私有的、自定義的 HttpBackend

那就創(chuàng)建一個“頂層組件”來扮演該模塊中所有組件的根。 把這個自定義的 HttpBackend 提供者添加到這個頂層組件的 providers 列表中,而不是該模塊的 providers 中。 回憶一下,Angular 會為每個組件實例創(chuàng)建一個子注入器,并使用組件自己的 providers 來配置這個注入器。

當(dāng)該組件的子組件想要一個 HttpBackend 服務(wù)時,Angular 會提供一個局部的 HttpBackend 服務(wù),而不是應(yīng)用的根注入器創(chuàng)建的那個。 子組件將正確發(fā)起 http 請求,而不管其它模塊對 HttpBackend 做了什么。

確保把模塊中的組件都創(chuàng)建成這個頂層組件的子組件。

你可以把這些子組件都嵌在頂層組件的模板中?;蛘?,給頂層組件一個 <router-outlet>,讓它作為路由的宿主。 定義子路由,并讓路由器把模塊中的組件加載進(jìn)該路由出口(outlet)中。

雖然通過在惰性加載模塊中或組件中提供某個服務(wù)來限制它的訪問都是可行的方式,但在組件中提供服務(wù)可能導(dǎo)致這些服務(wù)出現(xiàn)多個實例。因此,應(yīng)該優(yōu)先使用惰性加載的方式。

我應(yīng)該把全應(yīng)用級提供者添加到根模塊 AppModule 中還是根組件 AppComponent 中?

通過在服務(wù)的 @Injectable() 裝飾器中(例如服務(wù))指定 providedIn: 'root' 來定義全應(yīng)用級提供者,或者 InjectionToken 的構(gòu)造器(例如提供令牌的地方),都可以定義全應(yīng)用級提供者。 通過這種方式創(chuàng)建的服務(wù)提供者會自動在整個應(yīng)用中可用,而不用把它列在任何模塊中。

如果某個提供者不能用這種方式配置(可能因為它沒有有意義的默認(rèn)值),那就在根模塊 AppModule 中注冊這些全應(yīng)用級服務(wù),而不是在 AppComponent 中。

惰性加載模塊及其組件可以注入 AppModule 中的服務(wù),卻不能注入 AppComponent 中的。

只有當(dāng)該服務(wù)必須對 AppComponent 組件樹之外的組件不可見時,才應(yīng)該把服務(wù)注冊進(jìn) AppComponentproviders 中。 這是一個非常罕見的異常用法。

更一般地說,優(yōu)先把提供者注冊進(jìn)模塊中,而不是組件中。

討論

Angular 把所有啟動期模塊的提供者都注冊進(jìn)了應(yīng)用的根注入器中。 這些服務(wù)是由根注入器中的提供者創(chuàng)建的,并且在整個應(yīng)用中都可用。 它們具有應(yīng)用級作用域。

某些服務(wù)(比如 Router)只有當(dāng)注冊進(jìn)應(yīng)用的根注入器時才能正常工作。

相反,Angular 使用 AppComponent 自己的注入器注冊了 AppComponent 的提供者。 AppComponent 服務(wù)只在該組件及其子組件樹中才能使用。 它們具有組件級作用域。

AppComponent 的注入器是根注入器的子級,注入器層次中的下一級。 這對于沒有路由器的應(yīng)用來說幾乎是整個應(yīng)用了。 但對那些帶路由的應(yīng)用,路由操作位于頂層,那里不存在 AppComponent 服務(wù)。這意味著惰性加載模塊不能使用它們。

我應(yīng)該把其它提供者注冊到模塊中還是組件中?

提供者應(yīng)該使用 @Injectable 語法進(jìn)行配置。只要可能,就應(yīng)該把它們在應(yīng)用的根注入器中提供(providedIn: 'root')。 如果它們只被惰性加載的上下文中使用,那么這種方式配置的服務(wù)就是惰性加載的。

如果要由消費(fèi)方來決定是否把它作為全應(yīng)用級提供者,那么就要在模塊中(@NgModule.providers)注冊提供者,而不是組件中(@Component.providers)。

當(dāng)你必須把服務(wù)實例的范圍限制到某個組件及其子組件樹時,就把提供者注冊到該組件中。 指令的提供者也同樣照此處理。

例如,如果英雄編輯組件需要自己私有的緩存英雄服務(wù)實例,那就應(yīng)該把 HeroService 注冊進(jìn) HeroEditorComponent 中。 這樣,每個新的 HeroEditorComponent 的實例都會得到一份自己的緩存服務(wù)實例。 編輯器的改動只會作用于它自己的服務(wù),而不會影響到應(yīng)用中其它地方的英雄實例。

總是在根模塊 AppModule 中注冊全應(yīng)用級服務(wù),而不要在根組件 AppComponent 中。

為什么在共享模塊中為惰性加載模塊提供服務(wù)是個餿主意?

急性加載的場景

當(dāng)急性加載的模塊提供了服務(wù)時,比如 UserService,該服務(wù)是在全應(yīng)用級可用的。如果根模塊提供了 UserService,并導(dǎo)入了另一個也提供了同一個 UserService 的模塊,Angular 就會把它們中的一個注冊進(jìn)應(yīng)用的根注入器中(參見如果兩次導(dǎo)入了同一個模塊會怎樣?)。

然后,當(dāng)某些組件注入 UserService 時,Angular 就會發(fā)現(xiàn)它已經(jīng)在應(yīng)用的根注入器中了,并交付這個全應(yīng)用級的單例服務(wù)。這樣不會出現(xiàn)問題。

惰性加載場景

現(xiàn)在,考慮一個惰性加載的模塊,它也提供了一個名叫 UserService 的服務(wù)。

當(dāng)路由器準(zhǔn)備惰性加載 HeroModule 的時候,它會創(chuàng)建一個子注入器,并且把 UserService 的提供者注冊到那個子注入器中。子注入器和根注入器是不同的。

當(dāng) Angular 創(chuàng)建一個惰性加載的 HeroComponent 時,它必須注入一個 UserService。 這次,它會從惰性加載模塊的子注入器中查找 UserService 的提供者,并用它創(chuàng)建一個 UserService 的新實例。 這個 UserService 實例與 Angular 在主動加載的組件中注入的那個全應(yīng)用級單例對象截然不同。

這個場景導(dǎo)致你的應(yīng)用每次都創(chuàng)建一個新的服務(wù)實例,而不是使用單例的服務(wù)。

為什么惰性加載模塊會創(chuàng)建一個子注入器?

Angular 會把 @NgModule.providers 中的提供者添加到應(yīng)用的根注入器中…… 除非該模塊是惰性加載的,這種情況下,Angular 會創(chuàng)建一子注入器,并且把該模塊的提供者添加到這個子注入器中。

這意味著模塊的行為將取決于它是在應(yīng)用啟動期間加載的還是后來惰性加載的。如果疏忽了這一點,可能導(dǎo)致嚴(yán)重后果。

為什么 Angular 不能像主動加載模塊那樣把惰性加載模塊的提供者也添加到應(yīng)用程序的根注入器中呢?為什么會出現(xiàn)這種不一致?

歸根結(jié)底,這來自于 Angular 依賴注入系統(tǒng)的一個基本特征: 在注入器還沒有被第一次使用之前,可以不斷為其添加提供者。 一旦注入器已經(jīng)創(chuàng)建和開始交付服務(wù),它的提供者列表就被凍結(jié)了,不再接受新的提供者。

當(dāng)應(yīng)用啟動時,Angular 會首先使用所有主動加載模塊中的提供者來配置根注入器,這發(fā)生在它創(chuàng)建第一個組件以及注入任何服務(wù)之前。 一旦應(yīng)用開始工作,應(yīng)用的根注入器就不再接受新的提供者了。

之后,應(yīng)用邏輯開始惰性加載某個模塊。 Angular 必須把這個惰性加載模塊中的提供者添加到某個注入器中。 但是它無法將它們添加到應(yīng)用的根注入器中,因為根注入器已經(jīng)不再接受新的提供者了。 于是,Angular 在惰性加載模塊的上下文中創(chuàng)建了一個新的子注入器。

我要如何知道一個模塊或服務(wù)是否已經(jīng)加載過了?

某些模塊及其服務(wù)只能被根模塊 AppModule 加載一次。 在惰性加載模塊中再次導(dǎo)入這個模塊會導(dǎo)致錯誤的行為,這個錯誤可能非常難于檢測和診斷。

為了防范這種風(fēng)險,可以寫一個構(gòu)造函數(shù),它會嘗試從應(yīng)用的根注入器中注入該模塊或服務(wù)。如果這種注入成功了,那就說明這個類是被第二次加載的,你就可以拋出一個錯誤,或者采取其它挽救措施。

某些 NgModule(例如 BrowserModule)就實現(xiàn)了那樣一個守衛(wèi)。 下面是一個名叫 GreetingModuleNgModule 的 自定義構(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ù)組件類型命令式加載的組件是入口組件.

而通過組件選擇器聲明式加載的組件則不是入口組件。

Angular 會聲明式的加載組件,它使用組件的選擇器在模板中定位元素。 然后,Angular 會創(chuàng)建該組件的 HTML 表示,并把它插入 DOM 中所選元素的內(nèi)部。它們不是入口組件。

而用于引導(dǎo)的根 AppComponent 則是一個入口組件。 雖然它的選擇器匹配了 "index.html" 中的一個元素,但是 "index.html" 并不是組件模板,而且 AppComponent 選擇器也不會在任何組件模板中出現(xiàn)。

在路由定義中用到的組件也同樣是入口組件。 路由定義根據(jù)類型來引用組件。 路由器會忽略路由組件的選擇器(即使它有選擇器),并且把該組件動態(tài)加載到 RouterOutlet 中。

引導(dǎo)組件和入口組件有什么不同?

引導(dǎo)組件是入口組件的一種。 它是被 Angular 的引導(dǎo)(應(yīng)用啟動)過程加載到 DOM 中的入口組件。 其它入口組件則是被其它方式動態(tài)加載的,比如被路由器加載。

@NgModule.bootstrap 屬性告訴編譯器這是一個入口組件,同時它應(yīng)該生成一些代碼來用該組件引導(dǎo)此應(yīng)用。

不需要把組件同時列在 bootstrapentryComponent 列表中 —— 雖然這樣做也沒壞處。

什么時候我應(yīng)該把組件加到 entryComponents 中?

大多數(shù)應(yīng)用開發(fā)者都不需要把組件添加到 entryComponents 中。

Angular 會自動把恰當(dāng)?shù)慕M件添加到入口組件中。 列在 @NgModule.bootstrap 中的組件會自動加入。 由路由配置引用到的組件會被自動加入。 用這兩種機(jī)制添加的組件在入口組件中占了絕大多數(shù)。

如果你的應(yīng)用要用其它手段來根據(jù)類型引導(dǎo)或動態(tài)加載組件,那就得把它顯式添加到 entryComponents 中。

雖然把組件加到這個列表中也沒什么壞處,不過最好還是只添加真正的入口組件。 不要添加那些被其它組件的模板引用過的組件。

為什么 Angular 需要入口組件?

原因在于搖樹優(yōu)化。對于產(chǎn)品化應(yīng)用,你會希望加載盡可能小而快的代碼。 代碼中應(yīng)該僅僅包括那些實際用到的類。 它應(yīng)該排除那些從未用過的組件,無論該組件是否被聲明過。

事實上,大多數(shù)庫中聲明和導(dǎo)出的組件你都用不到。 如果你從未引用它們,那么搖樹優(yōu)化器就會從最終的代碼包中把這些組件砍掉。

如果Angular 編譯器為每個聲明的組件都生成了代碼,那么搖樹優(yōu)化器的作用就沒有了。

所以,編譯器轉(zhuǎn)而采用一種遞歸策略,它只為你用到的那些組件生成代碼。

編譯器從入口組件開始工作,為它在入口組件的模板中找到的那些組件生成代碼,然后又為在這些組件中的模板中發(fā)現(xiàn)的組件生成代碼,以此類推。 當(dāng)這個過程結(jié)束時,它就已經(jīng)為每個入口組件以及從入口組件可以抵達(dá)的每個組件生成了代碼。

如果該組件不是入口組件或者沒有在任何模板中發(fā)現(xiàn)過,編譯器就會忽略它。

有哪些類型的模塊?我應(yīng)該如何使用它們?

每個應(yīng)用都不一樣。根據(jù)不同程度的經(jīng)驗,開發(fā)者會做出不同的選擇。下列建議和指導(dǎo)原則廣受歡迎。

SharedModule

為那些可能會在應(yīng)用中到處使用的組件、指令和管道創(chuàng)建 SharedModule。 這種模塊應(yīng)該只包含 declarations,并且應(yīng)該導(dǎo)出幾乎所有 declarations 里面的聲明。

SharedModule 可以重新導(dǎo)出其它小部件模塊,比如 CommonModuleFormsModule 和提供你廣泛使用的 UI 控件的那些模塊。

SharedModule不應(yīng)該帶有 providers,原因在前面解釋過了。 它的導(dǎo)入或重新導(dǎo)出的模塊中也不應(yīng)該有 providers。 如果你要違背這條指導(dǎo)原則,請務(wù)必想清楚你在做什么,并要有充分的理由。

在任何特性模塊中(無論是你在應(yīng)用啟動時主動加載的模塊還是之后惰性加載的模塊),你都可以隨意導(dǎo)入這個 SharedModule。

特性模塊

特性模塊是你圍繞特定的應(yīng)用業(yè)務(wù)領(lǐng)域創(chuàng)建的模塊,比如用戶工作流、小工具集等。它們包含指定的特性,并為你的應(yīng)用提供支持,比如路由、服務(wù)、窗口部件等。 要對你的應(yīng)用中可能會有哪些特性模塊有個概念,考慮如果你要把與特定功能(比如搜索)有關(guān)的文件放進(jìn)一個目錄下,該目錄的內(nèi)容就可能是一個名叫 SearchModule 的特性模塊。 它將會包含構(gòu)成搜索功能的全部組件、路由和模板。

在 NgModule 和 JavaScript 模塊之間有什么不同?

在 Angular 應(yīng)用中,NgModule 會和 JavaScript 的模塊一起工作。

在現(xiàn)代 JavaScript 中,每個文件都是模塊(參見模塊)。 在每個文件中,你要寫一個 export 語句將模塊的一部分公開。

Angular 模塊是一個帶有 @NgModule 裝飾器的類,而 JavaScript 模塊則沒有。 Angular 的 NgModule 有自己的 importsexports 來達(dá)到類似的目的。

你可以導(dǎo)入其它 NgModules,以便在當(dāng)前模塊的組件模板中使用它們導(dǎo)出的類。 你可以導(dǎo)出當(dāng)前 NgModules 中的類,以便其它 NgModules 可以導(dǎo)入它們,并用在自己的組件模板中。

Angular 如何查找模板中的組件、指令和管道?什么是 模板引用 ?

Angular 編譯器在組件模板內(nèi)查找其它組件、指令和管道。一旦找到了,那就是一個“模板引用”。

Angular 編譯器通過在一個模板的 HTML 中匹配組件或指令的選擇器(selector),來查找組件或指令。

編譯器通過分析模板 HTML 中的管道語法中是否出現(xiàn)了特定的管道名來查找對應(yīng)的管道。

Angular 只查詢兩種組件、指令或管道: 1)那些在當(dāng)前模塊中聲明過的,以及 2)那些被當(dāng)前模塊導(dǎo)入的模塊所導(dǎo)出的。

什么是 Angular 編譯器?

Angular 編譯器會把你所編寫的應(yīng)用代碼轉(zhuǎn)換成高性能的 JavaScript 代碼。 在編譯過程中,@NgModule 的元數(shù)據(jù)扮演了很重要的角色。

你寫的代碼是無法直接執(zhí)行的。 比如組件。 組件有一個模板,其中包含了自定義元素、屬性型指令、Angular 綁定聲明和一些顯然不屬于原生 HTML 的古怪語法。

Angular 編譯器讀取模板的 HTML,把它和相應(yīng)的組件類代碼組合在一起,并產(chǎn)出組件工廠。

組件工廠為組件創(chuàng)建純粹的、100% JavaScript 的表示形式,它包含了 @Component 元數(shù)據(jù)中描述的一切:HTML、綁定指令、附屬的樣式等……

由于指令和管道都出現(xiàn)在組件模板中,*Angular 編譯器**也同樣會把它們組合進(jìn)編譯后的組件代碼中。

@NgModule 元數(shù)據(jù)告訴 Angular 編譯器要為當(dāng)前模塊編譯哪些組件,以及如何把當(dāng)前模塊和其它模塊鏈接起來。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號