Angular2 依赖注入

依赖注入介绍

控制反转概念最早在2004年由Martin Fowler提出,是针对面向对象设计不断复杂化而提出的一种设计原则,是一种利用面向对象编程法则来降低应用程序耦合的设计模式。IoC强调的对代码引用的控制权由调用方法转移到外部容器,在运行时通过某种方式注入进来,实现控制的反转,这大大降低了服务类之间的耦合度。依赖注入是一种最常用的实现IoC的方式。
在依赖注入模式中,应用组件无需关注所依赖对象的创建和初始化过程,可以认为框架已初始化好了,开发者只管调用即可。依赖注入有利于应用程序中各模块之间的解耦,使得代码更容易维护。这种优势可能一开始体现不出来,但随着项目复杂度的增加,各模块、组件、第三方服务等相互调用更频繁时,依赖注入的优点就体现出来了。开发者可以专注于所依赖对象的消费,无需关注这些依赖对象的产生过程,这将大大提升开发效率。
接下来通过一个机器人的例子来加深理解依赖注入的好处

//不使用依赖注入的实例
export class Head{

}
export class Arms{

}
export class Robot{
  public head:Head;
  public arms:Arms;
  constructor(){
    this.head=new Head();
    this.arms=new Arms();
  }
  move(){

  }

}

一个Robot类会包含Head、Arms、Feet等多各组件。此时,上面的代码存在哪些问题呢?

  • 扩展性差

Robot类通过Head和Arms创建了自己需要的组件,即头和胳膊。如果Head类的构造函数需要一个参数呢,此时只能通过this.haed=new Head(theNewParam)的方式修改Robot类


  • 难以测试

当需要测试Robot类时,需要考虑Robot类隐藏的其他依赖。比如Head组件本身是否依赖于其他组件,且它依赖的组件是否也依赖于另一个组件,另外Head组件的实例是否发送了异步请求到服务器。正是因为不能控制Robot的隐藏依赖,所以Robot很难被测试。

//使用依赖注入实例
export class Head{

}
export class Arms{

}
export class Robot{
  public head:Head;
  public arms:Arms;
  constructor(public head1:Head,public arms1:Arms){
    this.head=head1;
    this.arms=arms1;
  }
  move(){

  }

}

这里把依赖的对象作为参数传给构造函数,Robot类中不再创建Head和Arms。当创建Robot类实例时,只需要把创建好的Head和Arms实例传给它的构造函数即可。

var robot=new Robot(new Head(),new Arms());

到此,就实现了Robot类与Head类及Arms类的解耦,开发者可以注入任何Head和Arms实例到Robot类的构造函数。
依赖注入通过注入服务方式替代了在组件里初始化所以来的对象,从而避免了组件之间的紧耦合。但是这还不够,在使用Robot类时需要手动创建Head和Arms类,为了减少重复操作,可以通过创建一个工厂类来解决。

import {Robot, Head, Arms} from "./Robot";

export class RobotFactory{
  createRobot(){
    let robot=new Robot(this.createHead(),this.createArms());
  }

  createHead(){
    return new Head();
  }
  createArms(){
    return new Arms();
  }
}

上面代码只有三个方法,比较好维护,但是随着代码量的增加,维护这些代码就会变得很棘手。幸运的是,Angular的依赖注入框架替开发者解决了这个问题。有了它,开发者就不用去关心需要定义哪些依赖,以及把这个依赖注入给谁,因为依赖注入提供了注入器,它会帮开发者创建需要的类的实例。


Angular依赖注入

概述

首先介绍几个简单的概念。

  • 注入器(Inject):就像制造工厂,提供了一些列的接口用于创建依赖对象的实例
  • Provider:用于配置注入器,注入器通过它来创建被依赖对象的实例,Provider把标识映射到工厂方法中,被依赖的对象就是通过该方法创建的。
  • 依赖(Dependence):指定了被依赖对象的类型,注入器会根据此类型创建对应的对象。

在组件中注入服务

Angular在底层做了大量的初始化工作,这大大简化了创建依赖注入的过程,在组件中使用依赖注入需要完成以下三个步骤
- 通过import导入被依赖对象的服务
- 在组建中配置注入器。在启动组件时,Angular会读取@Component装饰器里的providers元数据,它是一个数组,配置了该组件需要使用到的所有依赖,Angular的依赖注入框架就会根据这个列表去创建对应对象的实例。
- 在组件构造函数中声明所注入的依赖。注入器就会根据构造函数上的声明,在组件初始化时通过第二步中的providers元数据配置依赖,为构造函数提供对应的依赖服务,最终完成注入过程。

import {Component, OnInit} from '@angular/core';
import {ContactService} from '../shared/contact.service';

@Component({
  selector: 'call-record',
  templateUrl: './collection.component.html',
  styleUrls: ['./collection.component.css'],
  providers:[ContactService]
})
export class CollectionComponent implements OnInit {
  collections:any = [];
  contacts:any = {};

  constructor(private _constactService: ContactService) { }
}

在服务中注入服务

除了组件服务依赖,服务间的相互调用也很常见。

//logger.service.ts
import {Injectable} from "@angular/core";
@Injectable()
export class LoggerService{
  log(message:string){
    console.log(message);
  }
}


//contact.service.ts
import {Injectable} from "@angular/core";
import {LoggerService} from "./logger.service";
@Injectable()//添加装饰器@Injectable()
export class ContactService{
  //构造函数中注入所依赖服务
  constructor(private _logger:LoggerService){}
  getCollections(){
    this._logger.log('获取联系人...')
  }
}


//在组件的providers元数据中注册服务
providers:[LoggerService,ContactService]

在上述中,LoggerService和ContactService这两个服务都用了@Injectable()装饰器,实际上它并不是必须的,只有一个服务依赖其他服务时,才需要用@Injectable()显示装饰。上述的LoggerService服务并没有依赖其他服务,它可以不用@Injectable()装饰,而ContactService服务依赖了其他服务,则需要@Injectable()装饰。

Angular官方推荐无论是否有依赖其他服务,都应该使用@Injectable()来撞死服务。一方面,开发者在给某个组件注入其他服务时,无需再确认该服务是否添加了@Injectable();另一方面,这也是一种良好的团队协作方式,整个团队遵循相同的开发原则。

在模块中注入服务

在根组件中注入这个服务,所有子组件都能共享这个服务。
在模块中注入服务和之前的注入场景稍有不同。Angular在启动程序时会启动一个根模块,并加载它所依赖的其他模块,此时会生成一个全局的根注入器,由该注入器创建的依赖注入对象在整个应用程序级别可见,并共享一个实例。同时根模块会指定一个根组件并启动,由该根组件添加的依赖注入对象是组件树级别可见,在根组件以及子组件中共享一个实例。

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

import {rootRouterConfig} from "./app.routes";
import {AppComponent} from "./app.component";



import {ContactService, UtilService} from "./shared";


@NgModule({
  declarations: [
    AppComponent

  ],
  imports     : [BrowserModule, FormsModule, HttpModule, RouterModule.forRoot(rootRouterConfig)],
  providers   : [ContactService, UtilService],
  bootstrap   : [AppComponent]
})
export class AppModule {

}

层级注入

Angular以组件为基础,项目开发中自然会有层级嵌套的情况,这种组织关系组成了组件树。根组件下面的各层级的子组件,可以出现在任何层级的任何组件中,每个组件可以拥有一个或多个依赖对象的注入,每个依赖对象对于注入器而言都是单例。

//生成唯一标识服务
import {Injectable} from "@angular/core";
@Injectable()
export class Random{
  public num;
  constructor(){
    this.num=Math.random();
  }
}
//子组件A
import {Component} from "@angular/core";
import {Random} from "./Random";
@Component({
  selector:'contact-a',
  providers:[Random],
  template:`<div>ContactA:{{random.num}}</div>`
})
export class contactAComponent{
  random:Random;
  constructor(r:Random){
    this.random=r;
  }
}
//子组件B
import {Component} from "@angular/core";
import {Random} from "./Random";
@Component({
  selector:'contact-b',
  providers:[Random],
  template:`<div>ContactB:{{random.num}}</div>`
})
export class contactBComponent{
  random:Random;
  constructor(r:Random){
    this.random=r;
  }
}
//父组件
import {Component} from "@angular/core";
@Component({
  selector:'contact-list',
  template:`
      <h1>Contact-List</h1>
      <contact-a></contact-a>
      <contact-b></contact-b>
`
})
export class ContactListComponent{
  constructor(){}
}

结果将输出:
Contact-List
ContactA:0.4500488165839276
ContactB:0.5389674473022938

上述的结果说明,每个子组件都创建了自己独立的注入器,也就是说通过依赖注入的Random服务都是独立的,如果把注入器提升到父组件中,则结果将会不一样。

import {Component} from "@angular/core";
import {Random} from "./Random";
@Component({
  selector:'contact-a',
  //providers:[Random],
  template:`<div>ContactA:{{random.num}}</div>`
})
export class contactAComponent{
  random:Random;
  constructor(r:Random){
    this.random=r;
  }
}
import {Component} from "@angular/core";
import {Random} from "./Random";
@Component({
  selector:'contact-b',
  //providers:[Random],
  template:`<div>ContactB:{{random.num}}</div>`
})
export class contactBComponent{
  random:Random;
  constructor(r:Random){
    this.random=r;
  }
}
import {Component} from "@angular/core";
import {Random} from "./Random";
@Component({
  selector:'contact-list',
  providers:[Random],
  template:`
      <h1>Contact-List</h1>
      <contact-a></contact-a>
      <contact-b></contact-b>
`
})
export class ContactListComponent{
  constructor(){}
}

此时,结果变为
Contact-List
ContactA:0.6257492668005642
ContactB:0.6257492668005642
上述的输出结果说明了子组件继承了父组件的注入器,所以子组件使用了相同的Random实例,输出了相同的结果。
那么,该如何选择在根组件还是在子组件中注入服务呢?
这取决于想让注入的依赖服务具有局限性还是全局性,由于每个注入器总是将它提供的服务维持单例,因此,如果不需要针对每个组件都提供独立的服务单例,就可以在根组件中注入,整个组件树共享根注入器提供的服务实例;如果需要针对每个组件提供不同的服务实例,就应该在格子组件中配置providers元数据来注入服务。
Angular如何查找到合适的服务实例呢?
在组件的构造函数视图注入某个服务的时候,Angular会先从当前组件的注入器中查找,找不到就继续往父组件的注入器查找,直到根组件注入器,最后到应用根注入器,此时找不到的话就会报错。

注入到派生组件

一个组件可以派生与另一个组件,对于有继承关系的组件,当父类组件和派生类组件有相同的依赖注入时,如果父类组件注入了这些依赖,派生组件也需要注入这些相同的依赖,并在派生类组件的构造函数中通过super()往上传递。

/**
 * Created by Administrator on 2017/4/26.
 */
import {Component, OnInit} from "@angular/core";
import {ContactService} from "./contact.service";
@Component({
  selector:'contact-app',
  providers:[ContactService],
  templateUrl:'./app/contact-app.html'
})
export class ContactAppComponent implements OnInit{
  collections:any={};
  constructor(protected _contatcService:ContactService){}
  ngOnInit(): void {
    this._contatcService.getCollections().subscribe(data=>{
      this.collections=data;
      this.afterGetContacts();
    });
  }
  protected afterGetContacts(){}
}
/**
 * Created by Administrator on 2017/4/26.
 */
import {Component, OnInit} from "@angular/core";
import {ContactService} from "./contact.service";
import {ContactAppComponent} from "./contactApp.component";
@Component({
  selector:'contact-app',
  providers:[ContactService],
  templateUrl:'./app/contact-app.html'
})
export class SortedContactComponent extends ContactAppComponent{
  protected afterGetContacts() {
    this.collections=this.collections.sort((h1,h2)=>{
      return h1.name<h2.name?-1:(h1.name>h2.name?1:0);
    })
  }
  constructor(protected _contatcService:ContactService){
    super(_contatcService);
  }

}

限定方式的依赖注入

到目前为止,注入都是假定依赖对象存在的,然而实际情况往往并非如此,比如,上层提供的Provider被移除,导致之前注入的依赖可能已经不存在了,此时,再按照前面讲的依赖注入方式进行相关服务的调用,应用就会出错。Angular依赖注入框架提供了@Optional和@Host装饰器来解决上面提到的问题。Angular的限定注入方式使得开发者能够修改默认的额依赖查找规则,@Optional可以兼容依赖不存在的情况,提高系统的健壮性。@Host可以限定查找规则,明确实例初始化位置,避免一些莫名的共享对象问题。
在Angular中实现可选注入很简单,在宿主组件的构造函数中增加@Optional()装饰器即可

import {Injectable, Optional} from "@angular/core";
import {LoggerService} from "./logger.service";
@Injectable()
export class ContactService{
  constructor(@Optional()private _logger:LoggerService){
    if(this._logger){
      this._logger.log("ContactService")
    }
  }
  getCollections(){
    this._logger.log('获取联系人...')
  }
}

依赖查找的规则是按照注入器从当前组件向父组件查找,直到找到要注入的依赖为止。但有时候想限制默认的查找规则,@Host 装饰器将把往上搜索的行为截止在宿主组件。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值