Angular4 模塊說明以及劃分

Angular模塊 (NgModule)

Angular 模塊是帶有 @NgModule 裝飾器函數的類。 @NgModule接收一個元數據對象,該對象告訴 Angular 如何編譯和運行模塊代碼。 它標記出該模塊擁有的組件、指令和管道, 並把它們的一部分公開出去,以便外部組件使用它們。 它能夠嚮應用的依賴注入器中添加服務提供商。html

每一個 Angular 應用都有一個根模塊類。 按照約定,它的類名叫作AppModule,被放在app.module.ts文件中。bootstrap

src/app/app.module.ts (minimal)

import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent }  from './app.component';

@NgModule({
  imports:      [ BrowserModule ],
  declarations: [ AppComponent ],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }
  • 這個元數據只導入了一個輔助模塊,BrowserModule,每一個運行在瀏覽器中的應用都必須導入它
  • BrowserModule註冊了一些關鍵的應用服務提供商。 它還包括了一些通用的指令,例如NgIf和NgFor,因此這些指令在該模塊的任何組件模板中都是可用的

下面範例AppComponent顯示被綁定的標題:瀏覽器

src/app/app.component.ts (minimal)

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

@Component({
  selector: 'app-root',
  template: '<h1>{{title}}</h1>',
})
export class AppComponent {
  title = 'Minimal NgModule';
}
  • @NgModule.bootstrap屬性把這個AppComponent標記爲引導 (bootstrap) 組件。 當 Angular 引導應用時,它會在 DOM 中渲染AppComponent,並把結果放進index.html的<my-app>元素標記內部

Angular編譯(JIT和AOT)

  • 即時 (JiT) 編譯
src/main.ts (dynamic)

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule);

  • 預編譯器 (AoT - Ahead-Of-Time) 進行靜態引導網絡

    • 靜態方案能夠生成更小、啓動更快的應用,建議優先使用它,特別是在移動設備或高延遲網絡下。
    • 使用靜態選項,Angular 編譯器做爲構建流程的一部分提早運行,生成一組類工廠。它們的核心就是AppModuleNgFactory。
    • 引導預編譯的AppModuleNgFactory的語法和動態引導AppModule類的方式很類似。
src/main.ts (static)

// The browser platform without a compiler
import { platformBrowser } from '@angular/platform-browser';

// The app module factory produced by the static offline compiler
import { AppModuleNgFactory } from './app/app.module.ngfactory';

// Launch with the app module factory.
platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);
  • 因爲整個應用都是預編譯的,因此咱們不用把 Angular 編譯器一塊兒發到瀏覽器中,也不用在瀏覽器中進行編譯。
  • 下載到瀏覽器中的應用代碼比動態版本要小得多,而且能當即執行。引導的性能能夠獲得顯著提高。
  • 不管是 JiT 仍是 AoT 編譯器都會從同一份AppModule源碼中生成一個AppModuleNgFactory類。 JiT 編譯器動態地在瀏覽器的內存中建立這個工廠類。 AoT 編譯器把工廠輸出成一個物理文件,也就是咱們在靜態版本main.ts中導入的那個。

導入支持性模塊

src/app/title.component.html (ngIf)

<p *ngIf="user">
  <i>Welcome, {{user}}</i>
<p>
  • 雖然AppModule沒有聲明過NgIf指令,但該應用仍然能正常編譯和運行。爲何這樣沒問題呢?Angular 的編譯器遇到不認識的 HTML 時應該不是忽略就是報錯纔對。
    Angular 能識別NgIf指令,是由於咱們之前導入過它。最第一版本的AppModule就導入了BrowserModule。
src/app/app.module.ts (imports)

imports: [ BrowserModule ],
  • 更準確的說,NgIf是在來自@angular/common的CommonModule中聲明的。
  • CommonModule提供了不少應用程序中經常使用的指令,包括NgIf和NgFor等。
  • BrowserModule導入了CommonModule而且從新導出了它。最終的效果是:只要導入BrowserModule就自動得到了CommonModule中的指令。
  • 不少熟悉的 Angular 指令並不屬於CommonModule。 例如,NgModel和RouterLink分別屬於 Angular 的FormsModule模塊和RouterModule模塊。在使用那些指令以前,咱們也必須導入那些模塊。

解決指令衝突

若是有兩個同名指令,都叫作HighlightDirective,該怎麼辦呢?咱們只要在 import 時使用as關鍵字來爲第二個指令建立個別名就能夠了。app

src/app/app.module.1b.ts
 
import {
  HighlightDirective as ContactHighlightDirective
} from './contact/highlight.directive';

當兩個指令在同一個元素上爭相設置顏色時,後聲明的那個會勝出,由於它對 DOM 的修改覆蓋了前一個ide

src/app/highlight.directive.ts

import { Directive, ElementRef } from '@angular/core';

@Directive({ selector: '[highlight]' })
/** Highlight the attached element in gold */
export class HighlightDirective {
  constructor(el: ElementRef) {
    el.nativeElement.style.backgroundColor = 'gold';
    console.log(
      `* AppRoot highlight called for ${el.nativeElement.tagName}`);
  }
}
src/app/contact/highlight.directive.ts

import { Directive, ElementRef } from '@angular/core';

@Directive({ selector: '[highlight], input' })
/** Highlight the attached element or an InputElement in blue */
export class HighlightDirective {
  constructor(el: ElementRef) {
    el.nativeElement.style.backgroundColor = 'powderblue';
    console.log(
      `* Contact highlight called for ${el.nativeElement.tagName}`);
  }
}
  • 在該例子中,聯繫人的HighlightDirective把應用標題的文本染成了藍色,而咱們本來指望它保持金色。
  • 真正的問題在於,有兩個不一樣的類試圖作同一件事。屢次導入同一個指令是沒問題的,Angular 會移除重複的類,而只註冊一次。從 Angular 的角度看,兩個類並無重複。Angular 會同時保留這兩個指令,並讓它們依次修改同一個 HTML 元素。
  • 若是咱們使用相同的選擇器定義了兩個不一樣的組件類,並指定了同一個元素標記,編譯器就會報錯說它沒法在同一個 DOM位置插入兩個不一樣的組件,咱們能夠經過建立特性模塊來消除組件與指令的衝突。 特性模塊能夠把來自一個模塊中的聲明和來自另外一個的區隔開

特性模塊

  • 特性模塊是帶有@NgModule裝飾器及其元數據的類,就像根模塊同樣。 特性模塊的元數據和根模塊的元數據的屬性是同樣的
  • 根模塊和特性模塊還共享着相同的執行環境。它們共享着同一個依賴注入器,這意味着某個模塊中定義的服務在全部模塊中也都能用

它們在技術上有兩個顯著的不一樣點:函數

  • 咱們引導根模塊來啓動應用,但導入特性模塊來擴展應用
  • 特性模塊能夠對其它模塊暴露或隱藏本身的實現

用路由器實現惰性 (lazy) 加載

應用路由

  • 該應用有三個特性模塊:聯繫人 (Contact) 、英雄 (Hero) 和危機 (Crisis)
  • ContactComponent組件是應用啓動時的默認頁
  • ContactModule仍然會在應用啓動時被主動加載
  • HeroModule和CrisisModule會被惰性加載
src/app/app.component.ts (v3 - Template)

template: `
  <app-title [subtitle]="subtitle"></app-title>
  <nav>
    <a routerLink="contact" routerLinkActive="active">Contact</a>
    <a routerLink="crisis"  routerLinkActive="active">Crisis Center</a>
    <a routerLink="heroes"  routerLinkActive="active">Heroes</a>
  </nav>
  <router-outlet></router-outlet>
`
src/app/app-routing.module.ts
 
import { NgModule }             from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

export const routes: Routes = [
  { path: '', redirectTo: 'contact', pathMatch: 'full'},
  { path: 'crisis', loadChildren: 'app/crisis/crisis.module#CrisisModule' },
  { path: 'heroes', loadChildren: 'app/hero/hero.module#HeroModule' }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}
  • 第一個路由把空白 URL(例如http://host.com/)重定向到了另外一個路徑爲contact的路由(例如http://host.com/contact
  • contact路由並非在這裏定義的,而是定義在聯繫人特性區本身的路由文件contact.routing.ts中。 對於帶有路由組件的特性模塊,其標準作法就是讓它們定義本身的路由
    另外兩個路由使用惰性加載語法來告訴路由器要到哪裏去找這些模塊
src/app/app-routing.module.ts
 
{ path: 'crisis', loadChildren: 'app/crisis/crisis.module#CrisisModule' },
{ path: 'heroes', loadChildren: 'app/hero/hero.module#HeroModule' }
  • 惰性加載模塊的位置是字符串而不是類型。 在本應用中,該字符串同時標記出了模塊文件和模塊類,二者用#分隔開。

RouterModule.forRoot和forChild方法

  • 只在根模塊當中調用RouterModule.forRoot()
  • 老是在特性路由模塊中調用RouterModule.forChild()

共享模塊

咱們添加SharedModule來存放這些公共組件、指令和管道,而且共享給那些須要它們的模塊工具

  1. 建立src/app/shared目錄
  2. 把AwesomePipe和HighlightDirective從src/app/contact移到src/app/shared中
  3. 從src/app/和src/app/hero目錄中刪除HighlightDirective類
  4. 建立SharedModule類來管理這些共享的素材
  5. 更新其它特性模塊,導入SharedModule
    下面就是這個SharedModule:
import { NgModule }            from '@angular/core';
import { CommonModule }        from '@angular/common';
import { FormsModule }         from '@angular/forms';
 
import { AwesomePipe }         from './awesome.pipe';
import { HighlightDirective }  from './highlight.directive';
 
@NgModule({
  imports:      [ CommonModule ],
  declarations: [ AwesomePipe, HighlightDirective ],
  exports:      [ AwesomePipe, HighlightDirective,
                  CommonModule, FormsModule ]
})
export class SharedModule { }

值得注意的有:性能

  • 它導入了CommonModule,這是由於它的組件須要這些公共指令
  • 正如咱們所期待的,它聲明並導出了工具性的管道、指令和組件類
  • 它從新導出了CommonModule和FormsModule

從新導出其它模塊

  • 當回顧應用程序時,咱們注意到不少須要SharedModule的組件也同時用到了來自CommonModule的NgIf和NgFor指令, 而且還經過來自FormsModule的[(ngModel)]指令綁定到了組件的屬性,那些聲明這些組件的模塊將不得不一樣時導入CommonModule、FormsModule和SharedModule
  • 經過讓SharedModule從新導出CommonModule和FormsModule模塊,咱們能夠消除這種重複,因而導入SharedModule的模塊也同時免費得到了CommonModule和FormsModule
  • 實際上,SharedModule自己所聲明的組件沒綁定過[(ngModel)],那麼,嚴格來講SharedModule並不須要導入FormsModule
  • 這時SharedModule仍然能夠導出FormsModule,而不須要先把它列在imports中

爲何 Service 沒有被共享

  • 雖然不少組件都共享着同一個服務實例,但它們是靠Angular 的依賴注入體系實現的,而不是模塊體系
  • 不要在共享模塊中把應用級單例添加到providers中。 不然若是一個惰性加載模塊導入了此共享模塊,就會致使它本身也生成一份此服務的實例

核心 (Core) 模塊

咱們能夠把一些組件收集到單獨的CoreModule中,而且只在應用啓動時導入它一次,而不會在其它地方導入它code

用 CoreModule.forRoot 配置核心服務

模塊的靜態方法forRoot能夠同時提供並配置服務,它接收一個服務配置對象,並返回一個ModuleWithProviders。這個簡單對象具備兩個屬性:

  • ngModule - CoreModule類
  • providers - 配置好的服務提供商

根模塊AppModule會導入CoreModule類並把它的providers添加到AppModule的服務提供商中,更精確的說法是,Angular 會先累加全部導入的提供商,而後才把它們追加到@NgModule.providers中, 這樣能夠確保咱們顯式添加到AppModule中的那些提供商老是優先於從其它模塊中導入的提供商

禁止屢次導入CoreModule

src/app/core/core.module.ts
 
constructor (@Optional() @SkipSelf() parentModule: CoreModule) {
  if (parentModule) {
    throw new Error(
      'CoreModule is already loaded. Import it in the AppModule only');
  }
}
  • 這個構造函數會要求 Angular 把CoreModule注入自身。這看起來像一個危險的循環注入。
  • 確實,若是 Angular 在當前注入器中查閱CoreModule,這確實會是一個循環引用。 不過,@SkipSelf裝飾器意味着「在當前注入器的全部祖先注入器中尋找CoreModule。
  • 若是該構造函數在咱們所指望的AppModule中運行,就沒有任何祖先注入器可以提供CoreModule的實例,因而注入器會放棄查找。
  • 默認狀況下,當注入器找不到想找的提供商時,會拋出一個錯誤。 但@Optional裝飾器表示找不到該服務也無所謂。 因而注入器會返回null,parentModule參數也就被賦成了空值,而構造函數沒有任何異常。
  • 若是咱們錯誤的把CoreModule導入了一個惰性加載模塊(例如HeroModule)中,那就不同了。
  • Angular 建立一個惰性加載模塊,它具備本身的注入器,它是根注入器的子注入器。 @SkipSelf讓 Angular 在其父注入器中查找CoreModule,此次,它的父注入器倒是根注入器了(而上次父注入器是空)。 固然,此次它找到了由根模塊AppModule導入的實例。 該構造函數檢測到存在parentModule,因而拋出一個錯誤。
相關文章
相關標籤/搜索