首页 > 编程学习 > Angular4 - 依赖注入

Angular4 - 依赖注入

发布时间:2021/9/4 22:35:47

Angular4 - 依赖注入

1. 例子 (不是很恰当)

第一版

//car.ts
export class Car {
  engine: Engine;
  doors: Doors;
  body: Body;

  constructor() {
    this.engine = new Engine();
    this.body = new Body();
    this.doors = new Doors();
  }

  run() {
    this.engine.start();
  }
}

/*车身*/
export class Body { }
/*车门*/
export class Doors { }
/*引擎*/
export class Engine {
  start() {
    console.log('start run');
  }
}
上面的这个文件只是一个简单的文件,内部输出四个类文件,汽车,车身,车门,引擎。然后将这个文件中的四个对象当作是一个资源对象在Angular应用中使用,为什么叫做资源对象,因为这个文件的四个对象是为了Angular程序运行过程中独立存在的一种对象,不依赖于模块组件。

然后我们在Angular程序中去引用创建car对象
//angular.component.ts
import {Component, OnInit} from '@angular/core';
import { Car } from '../../common/class/car';

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

export class AngularComponent implements OnInit {
  constructor() {
  }

  ngOnInit(): void {
    let car1 = new Car(); // 创建Car对象
    let car2 = new Car(); // 创建Car对象
    console.log(car1 === car2); //false
  }
}

第一个问题是什么呢?假如我想在调用的时候自定义一些参数,调用这决定自己的汽车使用那些配件,那么上面的代码就需要改了。


第二版

//car.ts
export class Car {
  engine: Engine;
  doors: Doors;
  body: Body;

  constructor(engine, body, doors) {
    this.engine = engine;
    this.body = body;
    this.doors = doors;
  }

  run() {
    this.engine.start();
  }
}

/*车身*/
export class Body { }
/*车门*/
export class Doors { }
/*引擎*/
export class Engine {
  start() {
    console.log('start run');
  }
}

//angular.component.ts
import {Component, OnInit} from '@angular/core';
import {Body, Car, Doors, Engine} from '../../common/class/car';

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

export class AngularComponent implements OnInit {
  constructor() {
  }

  ngOnInit(): void {
    let engine = new Engine();
    let body = new Body();
    let doors = new Doors();
    let car = new Car(engine, body, doors);
    car.run();
  }
}

现在的问题是什么吗?刚才Car 类发生了变化,Angular程序中所有调用的地方都需要变化。对于Angular程序来说,按照Car的接口创建对象也就认了。可是Car对象发生变化了,Angular程序也要发生变化。对于追求开发效率来说,不能忍啊,有本事就Car你发生了变化,就变化,Angular程序不需要发生变化才牛逼嘛。好像是这个道理,我们现在看到当我们想要自定义传入car构造函数的参数时,我们在调用的地方就更复杂了。好像这二者之间有点不对头,想要自定义,还想调用的地方不变化。先来解决第一个问题: 就是调用的地方不发生变化。


第三版

//car.ts
import {Injectable} from '@angular/core';

/*车身*/
@Injectable()
export class Body { }
/*车门*/
@Injectable()
export class Doors { }
/*引擎*/
@Injectable()
export class Engine {
  start() {
    console.log('start run');
  }
}

@Injectable()
export class Car {
  constructor(
    private engine: Engine,
    private body: Body,
    private doors: Doors) {}

  run() {
    this.engine.start();
  }
}

//angular.component.ts
import {Component, OnInit, ReflectiveInjector} from '@angular/core';
import {Body, Car, Doors, Engine} from '../../common/class/car';

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

export class AngularComponent implements OnInit {
  constructor() {
  }

  ngOnInit(): void {
    let injector = ReflectiveInjector.resolveAndCreate([Engine, Body, Doors, Car]);
    let car = injector.get(Car);
    car.run();
  }
}
通过调用 ReflectiveInjector 抽象类的 resolveAndCreate() 方法,创建注入器。然后通过调用注入器的 get() 方法,获取 Token 对应的对象。注入到什么地方,当然是IOC容器。 继续变化,上面这种方式不是常用的手段,我们现在使用常用的手法。


第四版

//angular.component.ts
import {Component, OnInit, ReflectiveInjector} from '@angular/core';
import {Body, Car, Doors, Engine} from '../../common/class/car';

@Component({
  selector: 'app-angular',
  templateUrl: './angular.component.html',
  styleUrls: ['./angular.component.css'],
  providers: [Body, Car, Doors, Engine]
})


export class AngularComponent implements OnInit {
  constructor(private car: Car) {
  }

  ngOnInit(): void {
    this.car.run();
  }
}
这个调用方式是最经常见到的方式。走到了这里,现在我们需要将依赖注入说一下了。

2. 依赖注入

我们先说上面第一版的情况

如上图所示: Car在被由angular程序(使用Car对象的模块组件创建,然后调用者需要follow Car对象的构造函数)。这是使用者自身控制对象的产生。

然后我们看第三版,就是用注入器的方式,现在使用者不在直接去创建对象了,而是通过注入器来产生新的对象,第三版就是在通知angular IOC容器我需要哪些对象,你去帮我创建吧。



第四版其实已经将通知的内容都省略,IOC容器自己会去根据对象来产生相应的对象。前提是我们angular程序提供了这些可被注入的对象。



控制反转是相对于应用程序(也就是angular.component.ts)来说的,它需要谁就创建谁。现在不一样,创建的事情交给了IOC容器来做。这也就叫做控制反转。


然后再说依赖注入:相对于IOC容器和资源对象来说的(Car),理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”:

谁依赖于谁:当然是应用程序依赖于IoC容器;
为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

相对于angular来说,Angular IOC容器来控制这些资源对象,维护,创建完对象之后注入到应用程序中去。但是IOC本身不知道我可以控制的对象有哪些,就需要通过providers 来告知。这也是上面第四版中加了providers 元数据的原因。

走到这里,其实之前说的两个问题已经说了一个,但是另外一个还没有解决,就是使用者想要自定义参数创建对象。其实大家反过来想一下,现在使用者都已经不在乎外部资源怎么创建了,还需要纠结这个问题吗?因为我们的这个例子是创建一个对象,就会出现自定义传入的参数,这也是为什么在上面我会表明不太恰当的原因。angular的服务其实更多的是一种应用到多个模块,多个组件的对象,很少回事这种创建一个实体对象。
至于说我们通过IOC容器创建的对象是不是单例的呢?其实也是需要考虑的,关于这方面请看文章Angular4 - 共享模块


现在我们知道了providers 的作用,接着看一下providers吧。

3. providers

(1)Provider

import {NgModule, TemplateRef} from '@angular/core';
import { AngularComponent } from './angular.component';
import {RouterModule, Routes} from '@angular/router';
import {CommonModule as CommonPrivateModule} from '../../common/common.module';
import {CommonModule} from '@angular/common';
import { RouteComponent } from './route/route.component';
import {NgZorroAntdModule} from 'ng-zorro-antd';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {HttpServiceService} from '../../common/service/http-service.service';

const routes: Routes = [
  {path: 'module', component:  AngularComponent},
  {path: 'route', component:  RouteComponent}
];

@NgModule({
  imports: [
    CommonModule,
    RouterModule.forChild(routes),
    CommonPrivateModule,
    CommonModule,
    FormsModule,
    ReactiveFormsModule,
    NgZorroAntdModule
  ],
  declarations: [AngularComponent, RouteComponent],
  providers: [{provide: HttpServiceService, useClass: HttpServiceService}]
})
export class AngularModule { }

providers: [{provide: HttpServiceService, useClass: HttpServiceService}]
第一个是令牌 (token),它作为键值 (key) 使用,用于定位依赖值和注册提供商。
第二个是一个提供商定义对象。 可以把它看做是指导如何创建依赖值的配方。 有很多方式创建依赖值,也有很多方式可以写配方。
一般我们会直接写providers: [HttpServiceService],这种书写方式和上面的方式一样,token与class同名。

(2). 四种提供方式

a) 类提供商(useClass)

providers: [{provide: HttpServiceService, useClass: HttpServiceService}]
这种方式是token为HttpServiceService, 使用class HttpServiceService进行实例化。
当然这个地方的useClass可以使用别名类提供商,假如现在HttpServiceService现在不能满足新的开发需求,但是这个来在其他组件中还在使用,所以我们可以新开发一个新的类HttpServiceService1,与HttpServiceService实现同样的接口,然后只在这个地方使用,那我们就可以使用如下:
providers: [{provide: HttpServiceService, useClass: HttpServiceService1}]

b) 值提供商(userValue)

有时,提供一个预先做好的对象会比请求注入器从类中创建它更容易。
import { Component, Inject, InjectionToken, OnInit} from '@angular/core';

export let APP_CONFIG  = new InjectionToken<string>('injectionToken');

@Component({
  selector: 'app-angular',
  templateUrl: './angular.component.html',
  styleUrls: ['./angular.component.css'],
  providers: [
    { provide: APP_CONFIG, useValue: 'Test' }
  ]
})

export class AngularComponent implements OnInit {
  constructor(@Inject(APP_CONFIG) config: string) {
    console.log(config);
  }

  ngOnInit(): void {
  }
}


c) 工厂提供商(useFactory)

有时,我们需要动态创建这个依赖值,因为它所需要的信息直到最后一刻才能确定。 也许这个信息会在浏览器的会话中不停地变化。还假设这个可注入的服务没法通过独立的源访问此信息。这种情况下,请调用工厂提供商。

FactoryProvider 用于告诉 Injector (注入器),通过调用 useFactory 对应的函数,返回 Token 对应的依赖对象。

FactoryProvider 的使用
function serviceFactory() { 
 return new Service();
}
 
const provider: FactoryProvider = {
 provide: 'someToken', useFactory: serviceFactory, deps: []
};

FactoryProvider 接口
export interface FactoryProvider {
 // 用于设置与依赖对象关联的Token值,Token值可能是Type、InjectionToken、
 // OpaqueToken的实例或字符串
 provide: any;
 // 设置用于创建对象的工厂函数
 useFactory: Function;
 // 依赖对象列表
 deps?: any[];
 // 用于标识是否multiple providers,若是multiple类型,则返回与Token关联的依赖对象列表
 multi?: boolean;
}

d) 别名-提供商(useExisting)

使用useExisting,提供商可以把一个令牌映射到另一个令牌上。实际上,第一个令牌是第二个令牌所对应的服务的一个别名,创造了访问同一个服务对象的两种方法。
 { provide: OldLogger, useExisting: NewLogger}]


(3). Multi Providers

我们以值提供器作为例子:
import {Component, Inject, InjectionToken, Injector, OnInit} from '@angular/core';

export let APP_CONFIG  = new InjectionToken<string>('injectionToken');

@Component({
  selector: 'app-angular',
  templateUrl: './angular.component.html',
  styleUrls: ['./angular.component.css'],
  providers: [
    { provide: APP_CONFIG, useValue: 'Test' },
    { provide: APP_CONFIG, useValue: 'Test Second'}
  ]
})

export class AngularComponent implements OnInit {
  constructor(@Inject(APP_CONFIG) config: string) {
    console.log(config);
  }

  ngOnInit(): void {
  }
}
此时的输出值为‘Test Second’, 因为使用同一个 token 注册 provider,后面注册的 provider 将会覆盖前面已注册的 provider。如果我们加上multi,那么我们得到的就是一个数组。
import {Component, Inject, InjectionToken, Injector, OnInit} from '@angular/core';

export let APP_CONFIG  = new InjectionToken<string>('injectionToken');

@Component({
  selector: 'app-angular',
  templateUrl: './angular.component.html',
  styleUrls: ['./angular.component.css'],
  providers: [
    { provide: APP_CONFIG, useValue: 'Test', multi: true },
    { provide: APP_CONFIG, useValue: 'Test Second',  multi: true }
  ]
})

export class AngularComponent implements OnInit {
  constructor(@Inject(APP_CONFIG) config: string) {
    console.log(config); //["Test", "Test Second"]
  }

  ngOnInit(): void {
  }
}

3. @Injectable() 是必须的么

如果所创建的服务不依赖于其他对象,是可以不用使用 Injectable 类装饰器。但当该服务需要在构造函数中注入依赖对象,就需要使用 Injectable 装饰器。不过比较推荐的做法不管是否有依赖对象,在创建服务时都使用 Injectable 类装饰器。
Copyright © 2010-2022 ngui.cc 版权所有 |关于我们| 联系方式| 豫B2-20100000