Angular NgModule 做用域相關問題

NgModule 是你使用 Angular 編寫應用程序時遇到的第一個基本結構,但因爲涉及不一樣的做用域範圍,它也是最微妙和最複雜的。若是你想詳細瞭解 NgModule 的相關知識,能夠直接參考 Angualr NgModule FAQAngular FAQ 文章中 NgModule 版塊的內容 。html

Why NgModule?

咱們可使用 Angular CLI,自動完成不少工做,但咱們必需要作的第一件事就是加載根模塊。git

platformBrowserDynamic().bootstrapModule(AppModule);

NgModule 的目的是聲明咱們在 Angular 模塊中建立的內容,主要有如下兩種結構:github

  • declarations - 聲明模板中使用的內容,大部分是組件,也包括指令和管道。typescript

  • providers - 用於聲明服務。bootstrap

import { NgModule } from '@angular/core';

import { SomeComponent } from './some.component';
import { SomeDirective } from './some.directive';
import { SomePipe } from './some.pipe';
import { SomeService } from './shared/some.service';

@NgModule({
  declarations: [SomeComponent, SomeDirective, SomePipe],
  providers: [SomeService]
})
export class SomeModule {}

NgModule 是 Angular RC 階段新增的一個功能,由於 ES 6 中已經新增 import/export 功能,在引入 NgModule 彷佛增長了沒必要要的複雜性。但引入了 NgModule 讓咱們可使用 AOT 編譯,這大大提升了應用的性能。在 Angular 2 早期 Beta 版本中,咱們的組件在使用指令時,都必須每次導入相應的指令,這大大增長開發成本。框架

NgModule and scopes/visibility

declarationsproviders 屬性最使人困惑的是,它們沒有相同的做用域和可見性 (scope / visibility):ide

  • declarations / components 是本地做用域 (private visibility)性能

  • providers / services 是全局做用域 (public visibility)flex

這意味着你聲明的組件只能在當前模塊中使用。若是你想要在外面使用聲明的組件,你必須導出它們:code

import { NgModule } from '@angular/core';

import { SomeComponent } from './some.component';
import { SomeDirective } from './some.directive';
import { SomePipe } from './some.pipe';

@NgModule({
  declarations: [SomeComponent, SomeDirective, SomePipe],
  exports: [SomeComponent, SomeDirective, SomePipe]
})
export class SomeModule {}

反之,模塊中的聲明的服務,咱們能夠在全部模塊中使用。

When to import a NgModule?

components 和 services 擁有不一樣的做用域,瞭解這個區別很重要。若是咱們的應用程序不只僅包含一個模塊,事情可能會變得很糟糕。Angular 框架內部也拆分紅多個不一樣的模塊,如 core、common、http 等等。

在 Angular 模塊中咱們須要作的一件主要的事情是導入其它模塊:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpModule } from '@angular/http';

import { FeatureModule } from '../feature/feature.module';

@NgModule({
  imports: [CommonModule, HttpModule, FeatureModule]
})
export class SomeModule {}

如今的主要問題是,你必須知道爲何須要導入這些模塊:

  • 爲了使用導入模塊中聲明的組件、指令或管道?

  • 爲了使用模塊中定義的服務?

由於組件和服務,擁有不一樣的做用域:

  • 若是咱們須要在模塊中使用導入模塊中聲明的組件,那咱們須要在每一個使用的模塊中導入對應的模塊

  • 若是咱們只是使用模塊中定義的服務,那咱們只須要在主模塊中導入對應的模塊

若是你不瞭解這些區別,你可能因爲忘記導入某個模塊,而出現組件不可用的錯誤。或者你爲了使用某個模塊中定義的服務,而屢次導入同一個模塊。

When to import main Angular Modules?

Modules to import each time you need them

  • CommonModule (包含 Angular 中定義的內建指令,如 ngIf、ngFor 等),除了在主模塊以外,不須要導入,由於咱們已經在主模塊中導入了 BrowserModule (此模塊已導入了 CommonModule)。其它模塊都必須手動導入該模塊。

  • FormsModule / ReactiveFormsModule

  • BrowserAnimationsModule

  • FlexLayoutModule

  • MaterialModule 和 UI Modules (如 PrimeNg)

  • Other Modules (定義 components、directives 或 pipes)

Modules to import only once

  • HttpModule

  • Other Modules (僅提供服務)

The ShareModule good practice

若使用 Angular CLI 建立新的模塊,它會自動幫咱們導入 CommonModule。若是你須要使用與組件相關的模塊如 animations 、flex layout 或 Material 模塊,你必須每次手動導入上述模塊。所以一個好的實踐是建立 SharedModule 模塊。

管理 SharedModule 時,須要很當心。若是你只是導入模塊,它將沒法正常工做:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FlexLayoutModule } from '@angular/flex-layout';
import { MaterialModule } from '@angular/material';

@NgModule({
  imports: [CommonModule, FlexLayoutModule, MaterialModule]
})
export class SharedModule {}

爲何呢?緣由還是做用域的問題:組件只在 SharedModule 中可用,在其它導入 SharedModule 的模塊中,仍然是不可用的。所以咱們須要導出相應的組件:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FlexLayoutModule } from '@angular/flex-layout';
import { MaterialModule } from '@angular/material';

@NgModule({
  imports: [CommonModule, FlexLayoutModule, MaterialModule],
  exports: [CommonModule, FlexLayoutModule, MaterialModule]
})
export class SharedModule {}

若是咱們組件不包含其它共享的資源,咱們能夠省略 imports 屬性:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FlexLayoutModule } from '@angular/flex-layout';
import { MaterialModule } from '@angular/material';

@NgModule({
  exports: [CommonModule, FlexLayoutModule, MaterialModule]
})
export class SharedModule {}

Mixed NgModule

如何同時管理具備組件和服務的模塊?這是一個比較複雜的問題。你可能已經接觸過 RouterModule,該模塊不只提供了 <router-outlet>routerLink 指令,並且還提供了 ActivedRouter 服務 (用於獲取 URL 參數)、Router 服務 (用於頁面導航) 。

幸運的是,這個問題是由模塊自己來解決。 Angular CLI 會爲咱們自動生成路由文件,但你可能已經注意到,應用程序主模塊的路由和子模塊路由之間存在細微差異。

對於 AppModule,咱們這樣使用:

RouterModule.forRoot(routes)

對於子模塊,咱們這樣使用:

RouterModule.forChild(routes)

爲何呢?由於在 AppModule 中,forRoot() 方法會導入路由模塊中的指令和服務。但對於子模塊來講,forChild() 方法僅會導入路由模塊中定義的指令,而不會再次導入模塊中定義的服務。

Lazy-loaded modules

若是你須要實現模塊懶加載,能夠簡單地使用 Angular CLI 來實現:

const routes: Routes = [
  { path: 'admin', loadChildren: './admin/admin.module#AdminModule' }
];

由於它將是一個不一樣的包和模塊,默認狀況下是按需加載,它不會包含在您的應用程序的全局範圍內。

對於組件來講,你不須要更改任何內容:就像在任何子模塊中同樣,你須要再次導入 CommonModule 或 SharedModule 模塊。

但對於服務來講,有一些區別:

  • 你仍然能夠訪問應用程序中提供的服務 (如Http和已定義的服務)。

  • 延遲加載模塊中定義的服務,只能在延遲加載的模塊中使用,應用程序的其它模塊是沒法使用的。

參考資源

相關文章
相關標籤/搜索