【Angular学习】(六)依赖注入

概念

维基百科:
在软件工程中,依赖注入是种实现控制反转用于解决依赖性设计模式。一个依赖关系指的是可被利用的一种对象(即服务提供端) 。依赖注入是将所依赖的传递给将使用的从属对象(即客户端)。该服务是将会变成客户端的状态的一部分。 传递服务给客户端,而非允许客户端来建立或寻找服务,是本设计模式的基本要求。

一般情况下,如果服务 A 需要服务 B,那就意味着服务 A 要在内部创建服务 B 的实例,也就说服务 A 依赖于服务 B:

在这里插入图片描述

Angular 利用依赖注入机制改变了这一点,在该机制下,如果服务 A 中需要服务 B,即服务 A 依赖于服务 B,那么我们期望服务 B 能被自动注入到服务 A 中,如下图所示:

在这里插入图片描述

在 Angular 中,依赖注入包括以下三个部分:

提供者负责把一个令牌(可能是字符串也可能是类)映射到一个依赖的列表。它告诉 Angular 该如何根据指定的令牌创建对象。
注入器负责持有一组绑定;当外界要求创建对象时,解析这些依赖并注入它们。
依赖就是将被用于注入的对象。
三者的关系图如下:
在这里插入图片描述

服务

为什么使用服务

组件不应该直接获取或保存数据,它们不应该了解是否在展示假数据。 它们应该聚焦于展示数据,而把数据访问的职责委托给某个服务。

创建 Angular 组件

@Component({
  selector: 'app-root',
  template: `
    <h1>{{title}}</h1>
  `
})
export class AppComponent {
  title: string = 'App Works';
}

创建 Angular 服务

export class DataService {
  getData() {
    return ['Angular', 'React', 'Vue'];
  }
}

组件中注入服务

按照官方文档的示例,我们创建一个HeroComponent,它用来显示英雄的信息,具体实现如下:

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-hero',
  template: `
    <ul>
      <li *ngFor="let hero of heros">
        ID: {{hero.id}} - Name: {{hero.name}}
      </li>
    </ul>
  `
})
export class HeroComponent implements OnInit {
  heros: Array<{ id: number; name: string }>;

  ngOnInit() {
     this.heros = [
       { id: 11, name: 'Mr. Nice' },
       { id: 12, name: 'Narco' },
       { id: 13, name: 'Bombasto' },
       { id: 14, name: 'Celeritas' },
       { id: 15, name: 'Magneta' }
     ];
  }
}

HeroComponent 组件中,我们在 ngOnInit 钩子中进行数据初始化,然后利用 ngFor 指令来显示英雄列表的信息。创建完 HeroComponent 组件,我们要来验证一下该组件的功能。首先在 AppModule 中导入 HeroComponent 组件,具体如下:

import { HeroComponent } from './hero/hero.component';

@NgModule({
  declarations: [
    AppComponent,
    HeroComponent
  ],
  ...
})
export class AppModule { }

然后更新一下 AppComponent 组件,具体如下:

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

@Component({
  selector: 'app-root',
  template: `
    <app-hero></app-hero>
  `
})
export class AppComponent {}

访问 http://localhost:4200/ 页面,将看到:

ID: 11 - Name: Mr. Nice
ID: 12 - Name: Narco
ID: 13 - Name: Bombasto
ID: 14 - Name: Celeritas
ID: 15 - Name: Magneta

在目前的 HeroComponent 组件,我们的英雄列表信息是固定的,在实际的开发场景中,一般需要从远程服务器获取相应的信息。但我们暂不考虑这个问题,假设另外一个组件也需要利用同样的英雄列表信息,我们就需要创建一个 HeroService 服务,从而实现数据共享。具体如下:

export class HeroService {
    heros: Array<{ id: number; name: string }> = [
        { id: 11, name: 'Mr. Nice' },
        { id: 12, name: 'Narco' },
        { id: 13, name: 'Bombasto' },
        { id: 14, name: 'Celeritas' },
        { id: 15, name: 'Magneta' }
    ];

    getHeros() {
        return this.heros;
    }
}

HeroService 服务中,我们定义了一个 heros 属性和一个 getHeros() 方法:

heros - 用于保存英雄的列表信息
getHeros() - 用于获取英雄的列表信息

组件中使用 HeroService

导入 HeroService 服务

import { HeroService } from '../hero.service';

声明 HeroService 服务

@Component({
  selector: 'app-hero',
  ...
  providers: [HeroService]
})

注入 HeroService 服务

export class HeroComponent implements OnInit {
  constructor(private heroService: HeroService) { }
}

完整代码如下:

import { Component, OnInit } from '@angular/core';
import { HeroService } from '../hero.service';

@Component({
  selector: 'app-hero',
  template: `
    <ul>
      <li *ngFor="let hero of heros">
        ID: {{hero.id}} - Name: {{hero.name}}
      </li>
    </ul>
  `,
  providers: [HeroService]
})
export class HeroComponent implements OnInit {

  constructor(private heroService: HeroService) { }

  heros: Array<{ id: number; name: string }>;

  ngOnInit() {
    this.heros = this.heroService.getHeros();
  }
}

Provider

作用

注入器,Provider和对象间的关系
在这里插入图片描述
在 Angular 中我们通过 Provider 来描述与 Token 相关联的依赖对象的创建方式。
在 Angular 中依赖对象的创建方式分为以下四种:

useClass
useValue
useExisting
useFactory

使用

1.创建 Token

2.根据实际需求选择依赖对象的创建方式,如 useClass 、useValue、useExisting、useFactory

3.在 NgModule 或 Component 中注册 providers

4.使用构造注入的方式,注入与 Token 关联的依赖对象

分类

在 Angular 中 Provider 主要分为:

ClassProvider
ValueProvider
ExistingProvider
FactoryProvider

ClassProvider

不同的类都可用于提供相同的服务。
比如,下面的代码告诉注入器,当组件使用 FoodService 令牌请求日志对象时,给它返回一个 FoodService 实例。

@Component({
  selector: 'drink-viewer',
  providers: [
    { provide: FoodService, useClass: FoodService }
  ],
})

ValueProvider

有时候,提供一个现成的对象会比要求注入器从类去创建更简单一些。 如果要注入一个你已经创建过的对象,请使用 useValue 选项来配置该注入器。

@NgModule({
  declarations: [
    AppComponent,
  ],
  providers: [
    { provide: 'api', useValue: '/api/pizzas' }
  ]
})
export class AppModule {}

ExistingProvider

@Component({
  selector: 'drink-viewer',
  providers: [
    FoodService,
    { provide: DrinkService, useExisting: FoodService }
  ]
})

FactoryProvider

有时候你需要动态创建依赖值,创建时需要的信息你要等运行期间才能拿到。 比如,你可能需要某个在浏览器会话过程中会被反复修改的信息,而且这个可注入服务还不能独立访问这个信息的源头。

这种情况下,可以使用工厂提供商。

export function SideFactory(http) {
  return new FoodService(http, '/api/sides');
}

@Component({
  selector: 'side-viewer',
  providers: [
    {
      provide: FoodService,
      useFactory: SideFactory,
      deps: [Http]
    }
  ],
})

在 ValueProvider 的中,使用字符串作为 token,在大多数情况下,是不会存在问题的。

{ provide: 'api', useValue: '/api/pizzas' }

但假设某一天我们引入了一个第三方库,该库内部也是使用 ‘api’ 作为 token,这时候就会导致系统出现异常。

为了解决 token 冲突问题,Angular 引入了 InjectionToken 来避免出现 token 冲突。对于上面的示例,我们可以使用 InjectionToken 来创建一个唯一的 token:

export const API_TOKEN = new InjectionToken<string>('api');

使用的时候也很简单,只需要导入 API_TOKEN,然后更新 Provider 对象的 provide 属性,具体如下:

providers: [
  { provide: API_TOKEN, useValue: '/api/pizzas' }
]

Injectable

我们创建的类提供了一个服务。Injectable装饰器把它标记为可供注入的服务。

该注入器负责创建服务实例,并把它们注入到类中。Angular 会在执行应用时为你创建注入器,第一个注入器是根注入器,创建于启动过程中。

提供商会告诉注入器如何创建该服务。 要想让注入器能够创建服务(或提供其它类型的依赖),你必须使用某个Provider配置好注入器。

注入器是可继承的,这意味着如果指定的注入器无法解析某个依赖,它就会请求父注入器来解析它。

如果所创建的服务不依赖于其他对象,是可以不用使用 Injectable 类装饰器。但当该服务需要在构造函数中注入依赖对象,就需要使用 Injectable 装饰器。

用法

继续用上文官方示例的HeroComponent组件

@Component({
  selector: 'app-hero',
  template: `
    <ul>
      <li *ngFor="let hero of heros">
        ID: {{hero.id}} - Name: {{hero.name}}
      </li>
    </ul>
  `
})
export class HeroComponent implements OnInit {
  heros: Array<{ id: number; name: string }>;

  constructor(private heroService: HeroService,
    private loggerService: LoggerService) { }

  ngOnInit() {
    this.loggerService.log('Fetching heros...');
    this.heros = this.heroService.getHeros();
  }
}

然后是HeroService

import { LoggerService } from './logger.service';

export class HeroService {
    constructor(private loggerService: LoggerService) { }

    heros: Array<{ id: number; name: string }> = [
        { id: 11, name: 'Mr. Nice' },
        { id: 12, name: 'Narco' },
        { id: 13, name: 'Bombasto' },
        { id: 14, name: 'Celeritas' },
        { id: 15, name: 'Magneta' }
    ];

    getHeros() {
        this.loggerService.log('Fetching heros...');
        return this.heros;
    }
}

以上代码运行后会抛出以下异常信息:

Uncaught Error: Can't resolve all parameters for HeroService: (?).

上面异常信息说明无法解析 HeroService 的所有参数,而 HeroService 服务的构造函数如下:

export class HeroService {
   constructor(private loggerService: LoggerService) { }
}

这是因为构造函数中的类型信息并没有保存。
这时就要用到注入器

import { Injectable } from '@angular/core';
import { LoggerService } from './logger.service';

@Injectable()
export class HeroService {
  // ...
}

这样就可以成功运行了。

参考

http://www.semlinker.com/categories/angular/
https://www.angular.cn/guide/dependency-injection

©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值