Angular4學習筆記之依賴注入

簡介

依賴注入是重要的程序設計模式。 Angular 有本身的依賴注入框架,離開了它,幾乎無法構建 Angular 應用。 
它使用得很是普遍,以致於幾乎每一個人都會把它簡稱爲 DI。

注入器與提供器

注入器

//注入器
Constructor(private productService: ProductService ){ }

說明:

通常是在組件或者是服務中寫入端代碼,若是一個組件或者服務中的 constructor 注入爲空。
就表明着這個組件或者服務沒有注入任何的服務。

提供器

//提供器寫法一
Providers:[ProductService]

//提供器寫法二
Providers:[{provide: ProductService, userClass:ProductService}]

//提供器寫法三
Providers:[{ provide: ProductService, userClass:AnotherProductService }]

//提供器寫法四
Providers:[{ provide: ProductService , userFactory: () => { /*新建服務的方法*/ } }]

說明:

寫法一:使用這種方法是默認的寫法(也是最精簡的寫法),直接提供一個 ProuctService。
寫法二:使用這種方法和默認的寫法是一個意思。 
        provide: ProductService就是提供的是 ProductService服務。
        userClass:ProductService 就是咱們new 這個服務對象的時候,new的是 ProductService。
寫法三:使用這種方式,就是提供器提供的是 ProductService,可是 new 服務對象的時候, 
        new 的是 AnotherProductService。
寫法四:使用工廠模式建立提供器

尋找依賴注入的邏輯

在代碼中找注入的方法:
1.在具體的組件或者服務中的 constructor 方法中找注入的服務
2.根據 1 中注入的服務找 提供器
3.跟據2 中的提供器找到對應的注入服務類

例:
注入器爲 :Constructor(private productService: ProductService ){ }
就去找對應的 providers(提供器)
若是提供器爲:Providers:[ProductService] 那麼注入的就是 ProductService
若是提供器爲:Providers:[{ provide: ProductService, userClass:AnotherProductService }] 
             那麼注入的就是 AnotherProductService

依賴注入的層級結構

一方面,NgModule 中的提供商是被註冊到根注入器。這意味着在 NgModule 中註冊的提供商能夠被整個應用訪問。
另外一方面,在應用組件中註冊的提供商只在該組件及其子組件中可用。

clipboard.png

關於 @Injectable()

@Injectable() 標識一個類能夠被注入器實例化。 一般,在試圖實例化沒有被標識爲@Injectable()的類時,注入器會報錯。

官方建議:
建議:爲每一個服務類都添加 @INJECTABLE()
建議:爲每一個服務類都添加@Injectable(),包括那些沒有依賴嚴格來講並不須要它的。由於:
    面向將來: 沒有必要記得在後來添加依賴的時候添加 @Injectable()。
    一致性:全部的服務都遵循一樣的規則,不須要考慮爲何某個地方少了一個。

"注意":
老是使用@Injectable()的形式,不能只用@Injectable。 若是忘了括號,應用就會神不知鬼不覺的報錯!

最簡單的例子

根模塊中注入

目的:在新建的工程中將數據從Service中注入到component中,而且在界面上面展現出來
1.新建一個工程: ng new di
2.新建 product1 組件: ng g c product1
3.新建 product 服務(在shared 路徑下面新建 product 服務): 
  ng g service shared/product   或者   ng g s shared/product

修改代碼 produc.service.ts:css

/*
 增長 class Product, 以及返回 Product對象供外部調用的方法 getProduct()
    getProduct方法須要返回一個 Product 。若是須要讓外部訪問到當前的 Service ,就須要加上一個註解  @Injectable()
*/
import { Injectable } from '@angular/core';

@Injectable()
export class ProductService {

  constructor() { }

  getProduct(): Product {
    return new Product(1, "IPhone X", "最牛逼的全面屏手機", 8388);
  }

}

export class Product{

  constructor(
    public id: number,
    public name: string,
    public desc: string,
    public price: number
  ){}
}

修改 product1.component.tshtml

/*
在當前的 Product 類中增長 變量 product 以及 注入 ProductService,在初始化的鉤子中 調用 ProductService 的 getProduct 方法,返回一個 Product
*/
import { Component, OnInit } from '@angular/core';
import {Product, ProductService} from "../shared/product.service";

@Component({
  selector: 'app-product1',
  templateUrl: './product1.component.html',
  styleUrls: ['./product1.component.css']
})
export class Product1Component implements OnInit {

  product: Product;

  constructor(private productService: ProductService) { }

  ngOnInit() {
    this.product = this.productService.getProduct();
  }

}

修改 product1.component.htmlbootstrap

<!-- 修改界面,用於界面展現 -->
<div>
  <div>商品編碼:{{product.id}}</div>
  <div>商品名稱:{{product.name}}</div>
  <div>商品描述:{{product.desc}}</div>
  <div>商品價格:{{product.price}}</div>
</div>

修改 app.conponent.html設計模式

<!-- 將 product.html 加入到 當前界面 -->
<h1>
  依賴注入的例子
</h1>


<div>
  <app-product1></app-product1>
</div>

修改 app.modules.tsapp

/*添加 Product.service.ts 到 providers 中,在這個地方注入是叫作 「從根組件中注入」,而後全部的均可以訪問到。*/
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';
import { Product1Component } from './product1/product1.component';
import {ProductService} from "app/shared/product.service";

@NgModule({
  declarations: [
    AppComponent,
    Product1Component
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  providers: [ProductService],
  bootstrap: [AppComponent]
})
export class AppModule { }

圖示:框架

clipboard.png

在組件中注入服務

在具體的某個組件中去注入服務,而不是經過"根模塊"去注入。
因爲在具體的組件中去注入服務,這樣子能夠"覆蓋根模塊的注入",從而使本身的子模塊擁有該服務,這樣子就能夠作服務的多級注入。

目的:我新建一個 product2 組件,而後在 product2 組件中注入 anotherProductService
1.新建一個組件 product2
  ng g c product2
2.在 shared目錄下新建一個service anotherProduct
  ng g s shared/anotherProduct

修改 another-product.service.tsdom

/*
實現 ProductService 服務,共同的擁有一個返回 Product 的方法,這個地方 用不用 implements 實現 ProductService都不影響,在 component中的提供器上面 使用這個服務
*/
import { Injectable } from '@angular/core';
import {Product, ProductService} from "./product.service";

@Injectable()
export class AnotherProductService implements ProductService{

  constructor() { }

  getProduct(): Product {

    return new Product(2, "小米 MIX2", "小米最牛逼的全屏手機,陶瓷機身", 3600);
  }
}

修改product2.conponent. tside

/*
在 Component 語法糖中添加 服務的提供器 provide, 這樣子作會覆蓋根模塊中注入的服務。而且在 Component中注入的服務就能夠不用在 app.modules.ts 中的 provide中添加
*/

import { Component, OnInit } from '@angular/core';
import {Product, ProductService} from "../shared/product.service";
import {AnotherProductService} from "../shared/another-product.service";

@Component({
  selector: 'app-product2',
  templateUrl: './product2.component.html',
  styleUrls: ['./product2.component.css'],
  providers:[{
    provide: ProductService, useClass: AnotherProductService
  }]
})
export class Product2Component implements OnInit {

  product: Product;

  constructor(private productService: ProductService) { }

  ngOnInit() {
    this.product = this.productService.getProduct();
  }

}

修改product2.component.htmlthis

<!-- 用於展現數據 -->
<div>
  <div>商品編碼:{{product.id}}</div>
  <div>商品名稱:{{product.name}}</div>
  <div>商品描述:{{product.desc}}</div>
  <div>商品價格:{{product.price}}</div>
</div>

修改 app.component.html編碼

<!-- 將 product2 的組件添加到 根頁面中,讓頁面展現 product2 的數據 -->

<h1>
  依賴注入的例子
</h1>

<div>
  <app-product1></app-product1>
  <hr>
  <app-product2></app-product2>
</div>

圖示:

clipboard.png

給服務中注入服務

上面咱們是介紹了怎麼去在 組件中注入服務,其實在"服務"中能夠"注入服務"
新建服務 logger.service.ts
ng g s shared/logger

修改 logger.service.ts

/*
增長一個打印日誌的方法
*/
import { Injectable } from '@angular/core';

@Injectable()
export class LoggerService {

  constructor() { }

  log(msg: string){
    console.log(msg);
  }
}

修改app.module.ts

/*增長 loggerService的服務到提供器中*/
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';
import { Product1Component } from './product1/product1.component';
import {ProductService} from "app/shared/product.service";
import { Product2Component } from './product2/product2.component';
import {LoggerService} from "./shared/logger.service";

@NgModule({
  declarations: [
    AppComponent,
    Product1Component,
    Product2Component
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  providers: [ProductService, LoggerService],
  bootstrap: [AppComponent]
})
export class AppModule { }

修改another-product.service.ts

/*增長 logger 服務到當前的 服務中,調用的時候就打印日誌*/
import { Injectable } from '@angular/core';
import {Product, ProductService} from "./product.service";
import {LoggerService} from "./logger.service";

@Injectable()
export class AnotherProductService implements ProductService{

  constructor(private logger: LoggerService) { }

  getProduct(): Product {
    this.logger.log("product2 getProduct() 方法被調用");
    return new Product(2, "小米 MIX2", "小米最牛逼的全屏手機,陶瓷機身", 3600);
  }

}

圖示:

clipboard.png

使用工廠模式注入

說明:
有的時候,咱們須要動態建立某一個依賴值,由於它所須要的信息直到最後一刻才能肯定。而後根據這個依賴值去建立咱們所須要的類。
當遇到這樣子的狀況,就可使用工廠模式。

修改 product2.component.ts

/*
目的:是爲了經過工廠模式注入,而不是在 組件 Component中注入
去掉在 @component 語法糖中的提供器 providers,讓product2 和 product1 共用一個提供器
*/
import { Component, OnInit } from '@angular/core';
import {Product, ProductService} from "../shared/product.service";

@Component({
  selector: 'app-product2',
  templateUrl: './product2.component.html',
  styleUrls: ['./product2.component.css']
})
export class Product2Component implements OnInit {

  product: Product;

  constructor(private productService: ProductService) { }

  ngOnInit() {
    this.product = this.productService.getProduct();
  }
}

圖示:

clipboard.png

修改 app.module.ts

/*
修改app.module.ts 中的提供器,修改成使用工廠模式啓動。
須要說明的是,由於在 AnotherProductService 中的構造器注入了 LoggerService 服務,須要在這個地方傳入一個logger對象進去。

咱們也能夠將工廠模式的這段代碼寫入到單獨的一個ts中。(見官方的例子)
*/
providers: [{
  provide:ProductService,
  useFactory: () => {
    let logger = new LoggerService();

    let random = Math.random();
    let flag = random < 0.5;
    logger.log("生成的隨機數爲: "+random);
    if(flag){
      return new ProductService();
    }else{
      return new AnotherProductService(logger);
    }

  }
}, LoggerService]

完整的 app.module.ts 的代碼

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';
import { Product1Component } from './product1/product1.component';
import {ProductService} from "app/shared/product.service";
import { Product2Component } from './product2/product2.component';
import {LoggerService} from "./shared/logger.service";
import {AnotherProductService} from "./shared/another-product.service";

@NgModule({
  declarations: [
    AppComponent,
    Product1Component,
    Product2Component
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  providers: [{
    provide:ProductService,
    useFactory: () => {
      let logger = new LoggerService();

      let random = Math.random();
      let flag = random < 0.5;

      logger.log("生成的隨機數爲: "+random);
      if(flag){
        return new ProductService();
      }else{
        return new AnotherProductService(logger);
      }

    }
  }, LoggerService],
  bootstrap: [AppComponent]
})
export class AppModule { }

圖示:

clipboard.png

clipboard.png

工廠模式下傳遞參數

修改 provides 的寫法,增長 deps 節點,這個節點就是提供 工廠模式傳遞參數,而且能夠把數據傳入進去。
在providers 中增長 {provide: "IS_DEV_ENV", useValue: false}, 就是在 provider 中增長一個變量爲 IS_DEV_ENV,值爲 false。
經過 useFactory 的方法傳入參數進去,就能夠直接使用這個參數的值

傳入值爲 Boolean

providers: [{
  provide:ProductService,
  useFactory: (logger: LoggerService, is_dev) => {

    if(is_dev){
      return new ProductService();
    }else{
      return new AnotherProductService(logger);
    }
  },
  deps:[LoggerService, "IS_DEV_ENV"]
}, LoggerService,
  {provide:"IS_DEV_ENV", useValue :false}
]

傳入值爲對象

providers: [{
  provide:ProductService,
  useFactory: (logger: LoggerService, config) => {
    if( config.isDev ){
      return new ProductService();
    }else{
      return new AnotherProductService(logger);
    }
  },
  deps:[LoggerService, "APP_CONFIG"]
}, LoggerService,
  {
    provide:"APP_CONFIG", useValue :{isDev: false}
  }
]

寫在最後

我是一步一步編寫代碼,邊整理博客,按照上面的順序,應該是能夠把例子跑起來。若是須要本例子的代碼,能夠聯繫我。
相關文章
相關標籤/搜索