Angular4学习之依赖注入-阿里云开发者社区

开发者社区> 我是小助手> 正文

Angular4学习之依赖注入

简介:
+关注继续查看

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

为什么要使用依赖注入

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


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的构造函数中去更改代码。


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

这种代码是非常不灵活的。虽然我们可以进行如下结构调整


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。

基本使用

  • 在组件中使用


@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也就是构造函数中的)

  • 在服务中使用


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的参数就是一个providers

Providers

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

  • 使用类

{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) {}
}



原文发布时间为:2017年12月26日
原文作者:DJL箫氏

本文来源: 掘金 如需转载请联系原作者






版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
7238 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,大概有三种登录方式:
2503 0
如何理解深度学习分布式训练中的large batch size与learning rate的关系?
在深度学习进行分布式训练时,常常采用同步数据并行的方式,也就是采用大的batch size进行训练,但large batch一般较于小的baseline的batch size性能更差,请问如何理解调试learning rate能使large batch达到small batch同样的收敛精度和速度?
1843 0
Angular学习笔记
angular是面向未来的前端开发框架,学习她理解思想比代码重要,体现的是跨越前后端、大成、优秀的编程思想。她是一个完善的工具链和开发链。 写angular,你会感觉是在写java,更像写c#,面向对象和组件化的思想。
789 0
在AngularJS中学习javascript的new function意义及this作用域的生成过程
慢慢入门吧,不着急。 至少知道了controller和service的分工。 new function时,隐含有用this指向function的prototype之意。 这样,两个JAVASCRIPT难点,作用域及原型域得以统一。
819 0
angular4学习整理
快速入门https://segmentfault.com/a/1190000009733649 表单整理 https://segmentfault.com/a/1190000009652980
602 0
适合我胃口的angular.js学习资料
断断续续弄了半年的ANGULAR.JS学习资料,网上下载了N多资料,测试了很多次。 现在只能算是入门,因时间问题,现在要转入其它领域。 如果以后要拾起来,下面这个PDF比较对我胃口。 《AngularJS Essentials》 以后要用时,注意找来看哈。
657 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
5824 0
2013 年 AngularJS 学习资源精选
2014年被认为是AngularJS之年,现在是时候总结一下2013年的AngularJS优秀学习资源,希望能帮助你迎头赶上。只有最好的文章才能榜上有名。
31 0
+关注
我是小助手
云栖直播
384
文章
6
问答
文章排行榜
最热
最新
相关电子书
更多
《Nacos架构&原理》
立即下载
《看见新力量:二》电子书
立即下载
云上自动化运维(CloudOps)白皮书
立即下载