iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 6
0
Modern Web

Angular新手村學習筆記(2019)系列 第 6

Day06_NgModule & DI

  • 分享至 

  • xImage
  •  

[S06E05] NgModule & DI

https://www.youtube.com/watch?v=B42zNPN2NvA&list=PL9LUW6O9WZqgUMHwDsKQf3prtqVvjGZ6S&index=8

S06 由 Kevin Yang 大大主講,藉由大大導讀文件後,我們就有能力能自己查文件了

NgModules很重要,因為幾乎所有的東西都要透過NgModules來封包
所有東西的最上層都要透過NgModules來管理

首先,先開好片頭導讀的文章,再跟著影片一起看,以後才有能力自己查文件

  1. 開啟 https://angular.io
  2. FUNDAMENTALS / NgModules / NgModules Introduction
    https://angular.io/guide/ngmodules

今天內容有:(方便search)

  • Feature Modules的種類

  • 簡單看一下啟動順序
    簡介@NgModule裡面各項目:declarations、imports、exports、providers、entryComponents、bootstrap
    尤其是providers的各種用法:
    useClass、useExisting、useValue
    useFactory、deps

  • SharedModule小技巧

  • Routing modules 跟 Routed feature modules

  • @NgModule裡的providers補充

  • providers 的 useValue 的例子補充

  • provider多service instance及multi: true範例

  • service sigleton

  • 參考文章:跨Component的互動

  • 討論:NgModule怎麼切Module?

Feature Modules的種類

  • Domain feature modules
  • Routed feature modules
  • Routing modules // 路由
  • Service feature modules
  • Widget feature modules

簡單看一下啟動順序

  1. main.ts
建立要準備執行Angular App的平台
產生platformBrowserDynamic()物件,呼叫該物件的bootstrapModule(),啟動AppModule
platformBrowserDynamic().bootstrapModule(AppModule)
platformBrowserDynamic().bootstrapModule(AppModule2) // 可以啟動第2個AppModule2
  1. app.module.ts
// AppComponent 為 Root Component,所有畫面的起點
// bootstrap 會被視為放入entryComponents:[]避免打包時被移除
bootstrap: [AppComponent] 

以前總是亂加,try到東西跑得出來,有的東西亂放編譯也會過

@NgModule({
  declarations: [ // 宣告,要用的component、directive、pipe要加進來才能用
    AppComponent,
    MyAppComponent, 
    HelloComponent
  ],
  imports: [ // 必需@NgModule({有exports的module})才能import進來
    BrowserModule,
    AppRoutingModule,
    xxModule
  ],
  exports: [
    xxModule // exports出來,才能被其他module來imports
  ]
  // 跟DI(Dependency Injection)有關的,全部加在providers
  // Service的相依注入符合SOILD原則
  // 哪些Service允許被注入,也可在@Service中用providedIn設定
  // 一個@NgModule會有1份Service instance,若有多個instance會取最近的那一個
  providers: [
      DataXXXService,
      // 寫法2,provide是別名,useClass是實際用的Service
      // 用途:很好抽換其他Service實作(但實作介面要一樣),對外的名稱不變
      {provide: DataService, useClass: Data01Service},
      // {provide: DataService, useClass: Data02Service},
      
      // useExisting: 使用既有的Service instance
      // 使用情境:DataXXXService實作很多方法,可用Interface來限制外面看得到的方法
      {provide: DataServiceInterface1, useExisting: DataXXXService},
      // {provide: DataServiceInterface2, useExisting: DataXXXService},
      
      // useFactory: xxFunction() 會去呼叫function()回傳一個Service
      {
          provide: DataServiceInterface2, 
          useFactory: funFactory,
          deps:[HttpClient] // 注入funFactory()需要用到的service
      },
      
      // useValue: 可用 數字 | 字串 | Object
      // 數字 | 字串 要再使用 InjectionToken 強制產生 token
      // 注入時用@Inject,如:
      // constructor(@Inject(CONFIG_TOKEN) private config: Config){}
      // 個人覺得這比較難,建議研讀 Mike 大大的文章:
      // https://ithelp.ithome.com.tw/articles/10208240
      // https://wellwind.idv.tw/blog/2018/11/07/mastering-angular-23-injection-tokens/
      { provide: token, useValue: 'A'}
  ], 
  entryComponents, // 加進來可避免被ng build --prod移除,如:動態載入的元件-Dialog
  bootstrap: [AppComponent] // 只有最上層的Root Module才有
})

// useFactory用的
function funFactory(){
    return new DataService();
}

// useValue用的
// 要import {Injectiontoken} from '@angular/core';
// 替 interface 產生 token
export const token = new Injectiontoken<Config>('abc');
export const token2 = new Injectiontoken<string>('abc');
export interface Config { LogLevel: string }

export class AppModule { }

SharedModule小技巧

請參考Mike大大的「[Angular 大師之路] Day 06 - 模組化的基本觀念」
https://ithelp.ithome.com.tw/articles/10203876
share.module.ts

@NgModule({
  declarations: [aComponent,bComponent,...], // pipe跟directive應該也可以吧
  imports: [aModule,bModule,...]
  exports: [aComponent,bComponent,aModule,bModule,...]
})
export class SharedModule { }

your.module.ts

@NgModule({
    ...
    imports: [SharedModule]
    ...
})

Routing modules 跟 Routed feature modules

建個專案來觀察

  1. ng new day06
  2. ng g module feature --routing
  3. ng g c feature

app-routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule, Route } from '@angular/router';

const routes: Route[] = [
]

@NgModule({
    imports: [
        RouterModule.forRoot(routes) // 只有最上層的路由用forRoot,其他用forChild
    ]
})
  1. feature-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { FeatureModule } from './feature.module';
import { FeatureComponent } from './feature.component';

const routes: Routes = [
  { path: '', component: FeatureComponent},
  // lazy loading的寫法
  // 1.要延遲截入的模組,有自己的子路由
  // 2.改用loadChildren
  { path: 'feature', loadChildren: './feature/feature.module#FeatureModule'}
];

@NgModule({
  imports: [RouterModule.forChild(routes), FeatureModule],
  exports: [RouterModule]
})
export class FeatureRoutingModule { }

@NgModule裡的providers

1.data-service-interface.ts

export abstract class DataServiceInterface{
    displayName(){}
}
// 在要使用DataService的xxx.module.ts裡,要import { DataServiceInterface }
// 因為interface不會輸出成javascript

2.data.service.ts

import { Injectable } from '@angular/core';
import { DataServiceInterface } from './data-service-interface';

@Injectable(
    // providedIn,類似寫在providers裡
    // 不同在於,providedIn是Tree-shakable Dependencies,若Service沒用到可被移除
    { providedIn: 'root' } // 註冊到最上層,只會有一份instance
)
// 在ts裡implements xxx,就會把xxx視為interface,就一定要實作裡面的function,否則會error
export class DataService implements DataServiceInterface {
    constructor(){}
    displayName(){}
}

providers 的 useValue 的例子補充

Mike老師的 [Angular 大師之路] Day 21 - 在 @NgModule 的 providers: [] 自由更換注入內容 (2)
https://ithelp.ithome.com.tw/articles/10208240

好處:感覺就只有減少程式碼產生。有利於「測試」嗎?

如果原本有個Service,要用一個Object來取代

@Injectable({
  providedIn: 'root'
})
export class DataService {
  getData() {
    return 100;
  }
}

Object裡面定義一個function()

const newData = {
  getData: () => {
    return 999;
  }
}
@NgModule({
  providers: [
    {
      provide: DataServiceInterface,
      useValue: newData, // Object不需要InjectionToken強制產生token
    }
  ]
})

provider多service instance及multi: true範例

Kevin老師親手打的範例
data-service-interface.ts

export abstract class DataServiceInterface{
    name // 當 multi: true ,用name來分辨instance
    displayName(){}
}

data1.service.ts、data2.service.ts

...
export class DataService implement DataServiceInterface {
    name = 'DataService1'; // 當 multi: true 時用來分辨instance
    // name = 'DataService2'; // data2.service.ts
...

app.module.ts

import { DataServiceInterface } from './data-service-interface';
@NgModule({
    ...
    providers: [
        // 因為 provide 同名字,會適用比較上面的那個
        { provide: DataServiceInterface, useClass: Data1Service },
        { provide: DataServiceInterface, useClass: Data2Service }
        
        // 如果加了 multi: ture 會取到instance的陣列
        // 情境:form的validator
{ provide: DataServiceInterface, useClass: Data1Service, multi: true },
{ provide: DataServiceInterface, useClass: Data2Service, multi: true }
        // [ Data1Service, Data2Service]
        // 0: Data1Service{ name: "DataService1" , __proto__:Data1Service}
        // 1: Data2Service{ name: "DataService2" , __proto__:Data2Service}
    ],
    ...
})
...

app.component.ts

import { Component } from '@angular/core';
import { DataServiceInterface } from './data-service-interface';
@Component({...})
export class Appcomponent {
    // 實際會注入 Data1Service 那個 instance
    constructor(private service: DataServiceInterface){
        service.displayName();
    }
}

service sigleton

angular service singleton or not
預設service的instance只會有一份,所以angular的status管理很方便
// DI 的 service 設 public,別的 component 才取得到
constructor(public service: DataServiceInterface){}

參考文章:跨Component的互動

1.https://angular.io的DOCS
2.FUNDAMETALS / Components & Templates / Component Interaction
https://angular.io/guide/component-interaction

  • @Input、@Ouput
    使用情境:子Component只負責顯示(畫面互動),不負責商業邏輯,的時候
    例如:angular material的datapicker-input.ts

  • Service
    使用情境:購物車
    當「複雜度高」或「需要高度互動性」時,可使用RxJS
    例用Observable (當有值發生時)-推送-> Subject

import { Injectable } from '@angular/core';
import { Subject }    from 'rxjs';

@Injectable()
export class MissionService {

  // Observable string sources
  private missionAnnouncedSource = new Subject<string>();
  private missionConfirmedSource = new Subject<string>();

  // Observable string streams
  missionAnnounced$ = this.missionAnnouncedSource.asObservable();
  missionConfirmed$ = this.missionConfirmedSource.asObservable();

  // Service message commands
  announceMission(mission: string) {
    this.missionAnnouncedSource.next(mission);
  }

  confirmMission(astronaut: string) {
    this.missionConfirmedSource.next(astronaut);
  }
}
  • 使用其他狀態管理的Library
    NgRX

討論:NgModule怎麼切Module?

  1. 依網址切,有共用的Service再抽出來(例如:SharedModule)
    可參考 Leo大大 的大作,[Angular 深入淺出三十天] Day 20 - 路由(三)
    https://ithelp.ithome.com.tw/articles/10207918

例如:https://angular.io/guide/component-interaction
^^^^^ ^^^^^^^^^^^^^^^^^^^^
整合成一個Module 一堆Component

  1. 若「功能明確」且「再使用率高」,適合包成一個Module

上一篇
Day05_Directive
下一篇
Day07_Router
系列文
Angular新手村學習筆記(2019)33
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言