4

简介

依赖注入是重要的程序设计模式。 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:

/*
 增加 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.ts

/*
在当前的 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.html

<!-- 修改界面,用于界面展示 -->
<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.ts

/*添加 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.ts

/*
实现 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. ts

/*
在 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.html

<!-- 用于展示数据 -->
<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}
  }
]

写在最后

我是一步一步编写代码,边整理博客,按照上面的顺序,应该是可以把例子跑起来。如果需要本例子的代码,可以联系我。

Wayfreem
241 声望33 粉丝

一个后端工程师,偏偏喜欢前端。