Angular4学习之依赖注入

在一个项目中,组件和服务之间存在错综复杂的关系,为了最小程度的耦合,咱们须要来管理组织这种关系,依赖注入就是管理这种关系的一种方式。javascript

为何要使用依赖注入

在学习一个概念以前,咱们必需要知道咱们为何要学习这个东西,这个东西究竟解决了什么问题。就比如这里讲到的,依赖注入究竟解决了什么问题。要解决这个问题,咱们先来看看示例代码:css

export class Car {
  public engine: Engine;
  public tires: Tires;
  public description = 'No DI';

  constructor() {
    this.engine = new Engine();
    this.tires = new Tires();
  }

  // Method using the engine and tires
  drive() {
    return `${this.description} car with ` +
      `${this.engine.cylinders} cylinders and ${this.tires.make} tires.`;
  }
}
复制代码

以上是来自angular官网的一段代码,咱们能够看到一个Car类依赖于EngineTires这两个类,咱们在Car的构造函数中去实例这两个依赖类。这有什么问题?若是有一天咱们的Tires构造函数须要一个参数,那么咱们必需要在Car的构造函数中去更改代码。html

// ...
constructor() {
   this.engine = new Engine();
   this.tires = new Tires(params);
 }
]
// ...
复制代码

这种代码是很是不灵活的。虽然咱们能够进行以下结构调整java

export class Car {
  public engine: Engine;
  public tires: Tires;
  public description = 'No DI';

  constructor(engine, tires) {
    this.engine = engine;
    this.tires = tires;
  }

  // Method using the engine and tires
  drive() {
    return `${this.description} car with ` +
      `${this.engine.cylinders} cylinders and ${this.tires.make} tires.`;
  }
}

const car = new Car(new Engine(), new Tires())
复制代码

这样彷佛解决了不灵活的问题,可是若是依赖项不少的话,咱们都要去手动建立这些实例,也不太方便。其实建立依赖实例的过程彻底能够交给一个专门的'工厂'来作,这就是angular里面的Injector。app

基本使用

  • 在组件中使用
@Component({
  selector: 'app-heroes',
  providers: [Engine, Tires],
  template: ` <h2>Heroes</h2> <app-hero-list></app-hero-list> `
})
export class HeroesComponent {
  construtor(private engine: Engine) {
    this.engine.start();
  }
}
复制代码

在Angular中,通常咱们将这些公共的依赖都会一些一个服务里面。在上面的用法咱们能够看到多了一个providers,另外就是在类的构造函数中增长了private engine: Engine咱们就能够去使用engine这个实例了,在这个过程当中,咱们并无去手动去建立依赖项的实例。这是由于angular的Injector帮咱们自动建立了。在这里有一个比较形象的比喻就是,一个厨子(Injector)根据菜谱(providers)去作菜(依赖的实例),可是究竟作哪些菜呢,客人说了算(private engine: Engine也就是构造函数中的)ide

  • 在服务中使用
import { Injectable } from '@angular/core';

@Injectable()
export class HeroService {
  constructor(private engine: Engine) { }
}
复制代码

若是咱们的一个服务自己就依赖于其余依赖项,那么咱们使用@Injectable()装饰器(即便一个服务并无依赖于其余服务,咱们也推荐加上@Injectable()装饰器),咱们依然要提供providers。这里因为服务一般跟视图是没有具体的关系,因此这里咱们不会引入@component装饰器,那么咱们在哪里肯定这个providers呢?咱们能够在一个module中的providers属性中去定义,那么这个module中的全部组件都会去共用这一个实例,可是咱们有时候咱们不但愿共用一个实例,而是一个新的实例,那么咱们能够在这个组件中的providers中从新定义,这样咱们就会获得一个新的实例。实际上这就是层级注入。利用层级注入咱们既能够共用实例,也能够不共用实例很是方便。通常全局使用的服务,咱们会注册在app.module模块之下,这样在整个应用中均可以使用。函数

在上面咱们说过经过依赖注入建立的实例是能够实现共享的,咱们证实一下。学习

import { Component, OnInit, ReflectiveInjector } from '@angular/core';
import {DependenceComponent} from './dependence.component';

@Component({
  selector: 'app-service',
  templateUrl: './service.component.html',
  styleUrls: ['./service.component.scss'],
})


@Injectable()
export class ServiceComponent implements OnInit {
  
  constructor() {
    let injector = ReflectiveInjector.resolveAndCreate([Dependence]);
    let dependence1 = injector.get(Dependence);
    let dependence2 = injector.get(Dependence);
    console.log('dependence1 === dependence2', dependence1 === dependence2); // true
  }
  
  ngOnInit() {}
}
复制代码

在这里咱们能够看见打印出来的是true,这里咱们采用的是手动建立实例,因此咱们并不须要在providers中提供“菜谱”,实际上resolveAndCreate的参数就是一个providersui

Providers

咱们有四种配置注入过程,即便用类、使用工厂、使用值、使用别名this

  • 使用类
{provide: MyService, useClass: MyService}
复制代码

这是咱们最多见的情形在angular中,一般若是provide的值和useclass的值同样,咱们能够简化为[MyService]

  • 使用值 显然并非每种状况,咱们都须要注入一个类,有时候能够仅仅是一个值
{provide: MyValue, useValue: 12345}
复制代码
  • 使用别名
{provide: OldService, useClass: NewService}
复制代码

若是咱们有两个服务OldServiceNewService接口都一致,出于某种缘由,咱们不得不使用OldService做为Token,可是咱们又想使用NewService中的接口,那么咱们就可使用别名。

  • 使用存在的值
[ NewLogger,
  // Not aliased! Creates two instances of `NewLogger`
  { provide: OldLogger, useClass: NewLogger}]
复制代码

这种状况下会建立两个NewLogger的实例,这显然不是咱们想要的结果,这时咱们就可使用存在的

[ NewLogger,
  // Alias OldLogger w/ reference to NewLogger
  { provide: OldLogger, useExisting: NewLogger}]
复制代码
  • 使用工厂 若是咱们的服务须要根据不一样的输入值,作出不一样的响应,那么就必需要接受一个参数,那么咱们就必须使用工厂
{provide: MyService, useFactory: (user: User) => {
    user.isAdmin ? new adminService : customService,
    deps: [User]
}}
复制代码

当使用工厂时,咱们能够经过变量的不一样值,去实例不一样的类。也就是说咱们须要根据不一样的值返回不一样的依赖实例的时候,那么咱们就须要使用工厂。

@Options 、@Host

目前为止咱们的依赖都是存在的,可是实际状况并非老是这样。那么咱们能够经过@Optional装饰器来解决这个问题。

import { Optional } from '@angular/core';
// ....
constructor(
    @Optional() private dependenceService: DependenceService
) {}
复制代码

可是这里DependenceService这个服务类的定义仍是存在的,只是没有准备好,例如没有在providers中使用

依赖查找的规则是按照注入器从当前组件向父级组件查找,直到找到这个依赖为止,可是若是限定查找路径截止在宿主组件,那么若是宿主组件中没有就会报错,咱们能够经过@Host修饰器达到这一功能。

若是一个组件注入了依赖项,那么这个组件就是这个依赖项的宿主组件,可是若是这个组件经过ng-content被嵌入到宿主组件,那么这个宿主组件就是该依赖项的宿主组件。

Token

当咱们在构造函数中使用private dependenceService: DependenceService,injector就能够正确的知道咱们要实例哪个类,这是由于在这里DependenceService充当了Token的角色(也就是说类名是能够充当Token的),咱们只须要在providers中去寻找具备相同Token的值就行,可是每每咱们注入不是一个类,而是一个字符串,function或者对象。而这里string、方法名和对象是不可以充当Token的,那么这时咱们就须要来手动建立一个Token:

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

export const APP_CONFIG = new InjectionToken<AppConfig>('app.config');

providers: [{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }]
复制代码
constructor(@Inject(APP_CONFIG) config: AppConfig) {
  this.title = config.title;
}
复制代码

Inject 装饰器显示的声明所依赖对象的类型

@Injectable()
class A {
    constructor(private buffer: Buffer) {}
}
复制代码

等同于

class A {
    constructor(@Inject(Buffer) private buffer: Buffer) {}
}
复制代码

更过精彩

相关文章
相关标签/搜索