掌握Angular2的依赖注入

更好阅读体验,请看原文html

在读这篇文章以前,你要先了解一下什么是依赖注入,网上关于这个的解释不少,你们能够自行Google.git

article-cover

咱们这一篇文章仍是以QuickStart项目为基础,从头开始讲解怎么在Angular2中使用依赖注入,若是你按照本篇文章中讲解的示例亲自走一遍的话,你必定可以掌握如何在Angular2中使用依赖注入.好,废话很少说,开始咱们今天的旅行吧!github

咱们首先将项目中的内联模板替换为一个模板文件,使用templateUrl替换template:typescript

@Component({
    selector: 'my-app',
    //template: '<h1>My First Angular2 Travel</h1>',
    templateUrl: 'app/templates/app.html'
})

接下来咱们给本身的页面添加一些展现的数据,咱们首先新建一个文件app/classes/User.ts,用来建立咱们的User实例:segmentfault

export class User{
    constructor(
        private name:string,
        private age:number,
        private email:string
    ){}
}

而后咱们在组件中引入这个类,而后建立咱们的显示数据:api

import {User} from "./classes/User";
// ...
export class AppComponent {
    users: User[] = [
        new User('dreamapple', 22, '2312674832@qq.com'),
        new User('dreamapplehappy', 18, '2313334832@qq.com')
    ]
}

别忘了在模板中添加一些展现数据使用的html代码:数组

<h1>依赖注入</h1>
<ul>
    <li *ngFor="let user of users">
        用户的姓名: {{user.name}}; 用户的年龄: {{user.age}}; 用户的邮箱: {{user.email}}
    </li>
</ul>

而后咱们就会看到,在页面中显示出来了咱们想要的那些数据:
ng2-di-1浏览器

Angular2的依赖注入服务器

通常状况下在Web应用中,咱们要展现的数据都是从后台服务器动态获取的,因此咱们来模拟一下这个过程;咱们在这里就要使用服务的依赖注入了,咱们首先建立文件user-data.mock.ts,路径是app/mock/user-data.mock.ts;angular2

import {User} from "../classes/User";

export var Users:User[] = [
    new User('dreamapple1', 21, '2451731631@qq.com'),
    new User('dreamapple2', 22, '2451731632@qq.com'),
    new User('dreamapple3', 23, '2451731633@qq.com'),
    new User('dreamapple4', 24, '2451731634@qq.com'),
    new User('dreamapple5', 25, '2451731635@qq.com'),
    new User('dreamapple6', 26, '2451731636@qq.com')
]

咱们使用了User类来建立咱们的数据,而后把建立的数据导出.

接下来咱们要建立一个获取用户数据的服务,咱们建立一个新的文件user.service.ts,路径app/services/user.service.ts:

import {Injectable} from '@angular/core';
import {Users} from "../mock/user-data.mock";


@Injectable()
export class UserService {
    getUsers() {
        return Users;
    }
}

你们关于上面的代码部分会有一些疑问,咱们来给你们解释一下:首先咱们使用了刚才咱们创造的模拟数据Users;而后咱们从@angular/core中导出了Injectable,就像咱们从中导出Component同样;@Injectable()标志着一个类能够被一个注入器实例化;一般来说,在试图实例化一个没有被标识为@Injectable()的类时候,注入器将会报告错误.上面的解释如今不明白没关系,咱们先学会如何使用;就像你不懂计算机原理同样能够把计算机玩得很溜同样.

咱们接下来要在AppComponent组件中使用UserService了,须要注意的地方是:咱们要在@Component的元数据中使用providers声明咱们所须要的依赖,还要引入User类来帮助咱们声明数据的类型.

import {UserService} from "./services/user.service";
import {User} from "./classes/User";
//...
@Component({
    selector: 'my-app',
    //template: '<h1>My First Angular2 Travel</h1>',
    templateUrl: 'app/templates/app.html',
    providers: [
        UserService
    ]
})
export class AppComponent {
    users: User[];

    constructor(private userService: UserService) {
        this.users = userService.getUsers();
    }
}

对上面代码的一些解释:咱们使用providers: [UserService]来声明咱们这个组件的依赖,若是没有这个选项,咱们的程序会报错;而后咱们给这个类添加一个属性users,同时声明这个属性的类型是一个含有User类实例的数组;最后咱们在构造函数中又声明了一个私有的属性userService,它是UserService服务类的一个实例,咱们能够用这个实例来获取users数据.

运行一下,而后咱们就会看到下面的页面,表示一切成功.
ng2-di-2

若是这个时候你试图把user.service.ts@Injectable注释掉的话,整个程序是没有报错的,可是咱们建议为每一个服务类都添加@Injectable(),包括那些没有依赖因此技术上不须要它的.由于:(1)面向将来,没有必要记得在后来添加了一个依赖的时候添加@Injectable().(2)一致性,全部的服务都遵循一样的规则,而且咱们不须要考虑为何少一个装饰器.

这是由于,咱们的UserService服务如今尚未什么依赖,若是咱们给UserService添加一个依赖的话,若是这时候把@Injectable()注释掉的话,程序就会报错;咱们来试试看吧.

不少Web程序都会须要一个日志服务,因此咱们来新建一个服务Logger,路径以下:app/services/logger.service.ts:

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

@Injectable()
export class Logger{
    logs: string[] = [];

    log(msg) {
        this.logs.push(msg);
        console.warn('From logger class: ' + msg);
    }
}

而后咱们在UserService服务中使用这个服务:

import {Injectable} from '@angular/core';
import {Users} from "../mock/user-data.mock";
import {Logger} from "./logger.service";


@Injectable()
export class UserService {
    constructor(private logger: Logger) {

    }

    getUsers() {
        this.logger.log('get users');
        return Users;
    }
}

能够看到,咱们把Logger当作UserService服务的一个依赖,由于咱们在UserService类的构造函数中声明了一个logger属性,它是Logger类的一个实例;还有别忘了在AppComponent中添加这个Logger依赖:

@Component({
    selector: 'my-app',
    //template: '<h1>My First Angular2 Travel</h1>',
    templateUrl: 'app/templates/app.html',
    providers: [
        Logger, // 添加Logger依赖
        UserService
    ]
})

而后咱们能够在页面中看到:
ng2-di-3

若是这个时候,咱们注释掉UserService@Injectable()的话,程序就会报错:
ng2-di-4

因此,就像上面所说的;咱们仍是给每个服务类添加@Injectable(),以避免出现没必要要的麻烦.

接下来咱们来讨论一下在Angular2中服务的提供商们,若是你对所谓的提供商不理解的话,不要紧;能够这样理解,每当咱们使用一个服务的时候,Angular2都会经过提供商来建立或者获取咱们想要的服务的实例.

咱们上面所说的那种提供服务的方式实际上是最简单的一种方式,接下来咱们讨论注册不一样的服务提供商的方法;首先第一种就是咱们上面所说的那种了,其实它是一种简写的方式;详细的方式应该是这样的:

[{ provide: Logger, useClass: Logger }]

其中provide做为键值key使用,用于定位依赖,用于注册这个提供商;这个其实就是在后面的程序中使用的服务的名字;useClass表示咱们使用哪个服务类去建立实例,固然咱们可使用不一样的服务类,只要这些服务的类知足咱们相应的需求就行.

咱们能够试着替换Logger类为BetterLogger类,咱们首先建立BetterLogger类:

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

@Injectable()
export class BetterLogger{
    logs: string[] = [];

    log(msg) {
        this.logs.push(msg);
        console.warn('From better logger class: ' + msg);
    }
}

而后在AppComponent中使用这个BetterLogger类:

@Component({
    selector: 'my-app',
    //template: '<h1>My First Angular2 Travel</h1>',
    templateUrl: 'app/templates/app.html',
    providers: [
        //Logger,
        [{provide: Logger, useClass: BetterLogger}],
        UserService
    ]
})

咱们能够看到,控制台的输出是:
ng2-di-5

从中能够看到,咱们使用了BetterLogger类替换了Logger类.若是咱们的提供商须要一些依赖,咱们应该怎么办呢?不用怕,咱们可使用下面这种形式:

[ LoggerHelper,{ provide: Logger, useClass: Logger }]

接下来咱们来建立一个LoggerHelper类,它的路径是app/services/logger-helper.service.ts:

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

@Injectable()
export class LoggerHelper {
    constructor() {
        console.warn('Just a logger helper!');
    }
}

咱们在AppComponent中注册提供商:

@Component({
    selector: 'my-app',
    //template: '<h1>My First Angular2 Travel</h1>',
    templateUrl: 'app/templates/app.html',
    providers: [
        //Logger,
        //[{provide: Logger, useClass: BetterLogger}],
        [LoggerHelper, {provide: Logger, useClass: BetterLogger}], // 带有依赖的注册商
        UserService
    ]
})

而后咱们在BetterLogger服务中使用这个依赖:

import {Injectable} from '@angular/core';
import {LoggerHelper} from "./logger-helper.service";

@Injectable()
export class BetterLogger{
    logs: string[] = [];

    constructor(private loggerHelper: LoggerHelper) {
    }

    log(msg) {
        this.logs.push(msg);
        console.warn('From better logger class: ' + msg);
    }
}

而后能够看到咱们的控制台的输出结果是:
ng-di-6

说明咱们正确的使用了依赖;还有咱们可使用别名来使用相同的提供商,这种方式能够解决一些问题;尤为是当咱们想让某个老的组件使用一个新的服务,就比如咱们想让AppComponent使用BetterLogger类来打印日志,而不是使用Logger类,假如咱们不可以改变AppComponent类,而且咱们还想让其余的组件也是用新的BetterLogger类的话,那么咱们就能够像下面这样注册这个提供商

[{ provide: BetterLogger, useClass: BetterLogger}],
[{ provide: Logger, useExisting: BetterLogger}]

看到了吗,咱们使用useExisting而不是useClass;由于使用useClass或致使咱们的应用中出现两个BetterLogger类的实例.咱们能够试验一下,在AppComponent中:

@Component({
    selector: 'my-app',
    //template: '<h1>My First Angular2 Travel</h1>',
    templateUrl: 'app/templates/app.html',
    providers: [
        //Logger,
        //[{provide: Logger, useClass: BetterLogger}],
        [LoggerHelper, {provide: BetterLogger, useClass: BetterLogger}],
        [LoggerHelper, {provide: Logger, useExisting: BetterLogger}],
        UserService
    ]
})

而后咱们在BetterLogger类的构造函数中添加一个打印语句:

console.warn('BetterLogger Constructor');

咱们还要在UserService类的构造函数中声明一个属性betterLogger,它是BetterLogger类的一个实例:

constructor(private logger: Logger, private betterLogger: BetterLogger) {

    }

最后咱们能够看到控制台的打印结果是:

Just a logger helper!
BetterLogger Constructor
From better logger class: get users

可是一旦咱们使用了useClass而不是useExisting,那么控制台的打印结果就变成了:

Just a logger helper!
BetterLogger Constructor
BetterLogger Constructor
From better logger class: get users

说明咱们建立了两个BetterLogger的实例.因此当咱们的多个服务想使用同一个提供商的时候,咱们应该使用useExisting,而不是useClass.

[2016-8-20:续写]

值提供商:咱们可使用更简便的方法来注册一个提供商,那就是使用,所谓的值能够是任何一种有效的TypeScript的基本的数据类型.咱们来首先使用一个对象吧.首先咱们新建立一个文件logger.value.ts,路径是app/values/logger.value.ts;咱们写一个基本的loggerValue对象以下:

let loggerValue = {
    logs: ['Hello', 'World'],
    log: (msg) => {
        console.warn('From values: ' + msg);
    },
    hello: () => {
        console.log('Just say hello!');
    }
};

export {loggerValue};

那咱们如何注册这个提供商呢?咱们使用useValue选项来注册咱们这种提供商;以下所示:

// ...
providers: [
        //Logger,
        //[{provide: Logger, useClass: BetterLogger}],
        [LoggerHelper, {provide: BetterLogger, useClass: BetterLogger}],
        //[LoggerHelper, {provide: Logger, useClass: BetterLogger}],
        {provide: Logger, useValue: loggerValue},
        //{provide: Logger, useValue: loggerValue1}, // 咱们使用了useValue选项
        UserService
    ]
// ...

还要记住把loggerValue导入进来;而后咱们稍微修改一下user.service.ts的代码:

// ...
getUsers() {
        this.logger.log('get users');
        //noinspection TypeScriptUnresolvedFunction
        this.logger.hello();
        return Users;
    }
// ...

而后咱们会看到控制台的输出是:

// ...
From values: get users
Just say hello!
// ...

代表咱们这种方式注册提供商成功了. 固然咱们也可使用一个字符串了,这些读者能够自行尝试;或者观看这个示例.

工厂提供商:有时咱们须要动态建立这个依赖值,由于它所须要的信息咱们直到最后一刻才能肯定;咱们如何注册一个工厂提供商呢?不着急,咱们一步一步来:咱们首先来建立一个验证权限的文件,authorize.ts,路径是:app/services/authorize.ts,咱们暂且在里面放置一些简单的逻辑,来断定当前用户有没有获取Users的权限:

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

@Injectable()
export class Authorize {
    isAuthorized: boolean;
    constructor(){
        this.isAuthorized = Math.random() > 0.5 ? true: false;
    }
    getIsAuthorized() {
        return this.isAuthorized;
    }
}

好吧,我认可这样写有点随意,暂时先这样吧;咱们的目的是为了告诉你们如何使用工厂提供商,暂时简化权限验证这一块;从上面的代码咱们能够大概了解到,这个服务就是为了获取当前用户的权限状况;而后咱们来配置咱们的UserService2Provider,为了方便咱们暂时直接在app.component.ts中书写咱们的配置:

// ...
let UserService2Provider = (logger: Logger, authorize: Authorize) => {
    return new UserService2(logger, authorize.getIsAuthorized());
};
// ...

能够看到,咱们的UserService2Provider其实就是一个返回了类的实例的一个函数;咱们给这个函数传递了两个参数,分别是LoggerAuthorize类的实例,而后咱们根据这两个实例,建立出了咱们新的服务实例;奥,忘了告诉你们,咱们还要建立一个新的UserService2类,路径:app/services/user-service2.ts:

import {Injectable} from '@angular/core';
import {Users} from "../mock/user-data.mock";
import {Logger} from "./logger.service";


@Injectable()
export class UserService2 {
    isAuthorized: boolean;
    constructor(private logger: Logger, isAuthorized: boolean) {
        this.isAuthorized = isAuthorized;
    }

    getUsers() {
        if(this.isAuthorized){
            this.logger.log('get users');
            return Users;
        }
        else {
            this.logger.log('not isAuthorized!');
            return [];
        }
    }
}

能够看到这个服务类和UserService差很少,就是多了一个条件验证,若是当前用户有获取Users的权限,咱们就会返回这些Users;若是没有,咱们就返回一个空数组.接下来就是很重要的一步了,咱们须要在app.component.tsproviders中使用UserService2Provider:

// ...
providers: [
        //Logger,
        //[{provide: Logger, useClass: BetterLogger}],
        [LoggerHelper, {provide: BetterLogger, useClass: BetterLogger}],
        //[LoggerHelper, {provide: Logger, useClass: BetterLogger}],
        {provide: Logger, useValue: loggerValue},
        //{provide: Logger, useValue: loggerValue1},
        UserService,
        Authorize, // 不可缺乏
        {
            provide: UserService2,
            useFactory: UserService2Provider,
            deps: [Logger, Authorize]
        }
    ]
// ...

还要记住,要添加Authorize依赖;咱们在app.component.ts中使用新的服务:

// ...
export class AppComponent {
    users: User[];

    constructor(private userService: UserService, private userService2: UserService2) {
        this.users = userService.getUsers();

        console.log(this.userService2.getUsers());
    }
}

刷新浏览器,你会看到有时它会输出:

From values: not isAuthorized!
[]

有时它会输出:

From values: get users
[User, User, User, User, User, User]

那么说明,咱们这种方式注册工厂提供商的方式也成功了.

也许你们会有一些疑问,咱们在类的构造函数中使用private userService2: UserService2怎么就获取了这个服务的一个示例,Angular2是怎么知道咱们要的是UserService2类,它又是如何获取这个类的实例的呢?在Angular2中咱们经过注射器来进行依赖注入,其实上面的形式只是一种简写;详细一点的写法是这样的:

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

咱们先从Ng2中获取Injector注射器,而后使用这个注射器来进行咱们所需服务的实例化:

// ...
export class AppComponent {
    users: User[];
    private userService2: UserService2;
    
    //constructor(private userService: UserService, private userService2: UserService2) {
    //    this.users = userService.getUsers();
    //
    //    console.log(this.userService2.getUsers());
    //}

    constructor(private userService: UserService, private injector: Injector) {
        this.users = userService.getUsers();
        this.userService2 = this.injector.get(UserService2);
        console.log(this.userService2.getUsers());
    }
}

因此能够看出来,这些繁琐的活咱们所有都让injector去作了,只须要咱们提供一些简单的说明,聪明的Ng2就知道如何进行依赖注入.

非类依赖

咱们上面的讲解所有都是把一个类做为一个依赖来进行服务的依赖注入,可是假如咱们想要的不是一个类,而是一些值,或者对象;咱们应该怎么办?咱们先来写出这么一个文件app-config.ts,路径是:app/config/app-config.ts:

export interface AppConfig {
    title: string,
    apiEndPoint: string
}

export const AppConf: AppConfig = {
    title: 'Dreamapple',
    apiEndPoint: 'https://hacking-with-angular.github.io/'
};

按照上面的使用的方法,咱们应该能够这样作:

// ...
{provide: AppConfig, useValue: AppConf}
// ...
constructor(private userService: UserService, private userService2: UserService2, private appConf: AppConfig) {
        this.users = userService.getUsers();

        console.log(this.userService2.getUsers());

        console.log(this.appConf);
    }
// ...

可是咱们这样作却没有达到咱们想要的效果;控制台报错:

Error: ReferenceError: AppConfig is not defined(…)

由于接口interface不可以被当作一个类class来处理,因此咱们须要换一种新的方式,使用OpaqueToken(不透明的令牌):

// 首先导入 OpaqueToken和Inject
import {Component, Injector, OpaqueToken, Inject} from '@angular/core';
// 引入AppConf,而且使用OpaqueToken
import {AppConf} from "./config/app-config";
let APP_CONFIG = new OpaqueToken('./config/app-config');
// 在providers中进行配置
{provide: APP_CONFIG, useValue: AppConf}
// 在类中使用
constructor(private userService: UserService, private userService2: UserService2, @Inject(APP_CONFIG) appConf: AppConfig) {
        this.users = userService.getUsers();

        console.log(this.userService2.getUsers());

        console.log(appConf);
    }

对上面的代码的一些解释首先咱们使用OpaqueTokean对象注册依赖的提供商,而后咱们在@Inject的帮助下,咱们把这个配置对象注入到须要它的构造函数中,最后咱们就可使用最初的那个对象了.虽然AppConfig接口在依赖注入过程当中没有任何做用,但它为该类中的配置对象提供了强类型信息.

最后咱们来说解一下可选依赖(Optional),有些服务的依赖也许不是必须的;咱们就可使用@Optional()来对这些参数作标记;记住,当使用@Optional()时,若是咱们想标记logger这个服务是可选的;那么若是咱们不在组件或父级组件中注册一个logger的话,注入器会设置该logger的值为空null;咱们的代码必需要为一个空值作准备.来看一下例子吧,咱们在user.service.ts中作一些改动:

// 导入 Optonal
import {Injectable, Optional} from '@angular/core';
import {Users} from "../mock/user-data.mock";
import {Logger} from "./logger.service";
import {BetterLogger} from "./better-logger.service";


@Injectable()
export class UserService {
    constructor(private logger: Logger,
                // 使用@Optional标记
                @Optional()private betterLogger: BetterLogger) {
    }

    getUsers() {
        this.logger.log('get users');
        //noinspection TypeScriptUnresolvedFunction
        this.logger.hello();

        // 存在betterLogger时的处理
        if(this.betterLogger) {
            this.betterLogger.log('optional');
        }

        //console.log(this.logger);
        return Users;
    }
}

至此,整篇文章已经结束了;若是你坚持读到了这里,那说明你也是一个颇有耐心的人;若是你有什么问题能够在这里提出.

相关文章
相关标签/搜索