什么是依赖注入
依赖注入(DI)是一种设计模式, 也有相应的框架,比如InversifyJS
Angular 有自己的 DI 框架, DI 框架会在实例化该类时向其提供这个类所声明的依赖项
带修饰符的参数
在ts中,一个类的参数如果带上修饰符,那个参数就变成了类的实例属性
class Mobile {
constructor(readonly name: string = '小米') {}
logName() {
console.log(this.name);
}
}
上面的name有修饰符,那么它就是Mobile类的实例属性,等同于下面写法:
class Mobile {
readonly name: string;
constructor() {
this.name = '小米';
}
logName() {
console.log(this.name);
}
}
创建服务
cli创建 ng g c xxx
被@Injectable装饰的类,就是一个可以被注入的类,也称为服务(为其它服务或者组件、指令、管道提供服务)
import { Injectable } from '@angular/core';
import { HEROES } from './mock-heroes';
@Injectable({
providedIn: 'root',
})
export class HeroService {
constructor() { }
getHeroes() { return HEROES; }
}
使用(注入)服务的方式
直接在参数中注入
import { Component } from '@angular/core';
import { Hero } from './hero';
import { HeroService } from './hero.service';
@Component({
selector: 'app-hero-list',
template: `
<div *ngFor="let hero of heroes">
{{hero.id}} - {{hero.name}}
</div>
`
})
export class HeroListComponent {
heroes: Hero[];
//heroServe:HeroService;
constructor(heroService: HeroService) {
//this.heroServe = new Hervice();
this.heroes = heroService.getHeroes();
//console.log(this.heroServe.getHeroes());
}
}
提供服务的地方
- 在服务本身的 @Injectable() 装饰器中。
@Injectable({ providedIn: 'root', })
- 在 NgModule 的 @NgModule() 装饰器中。
@NgModule({
providers: [
LoggerService,
// { provide: LoggerService, useClass: LoggerService }
//与上面写法相同,如果provide = useClass
]
})
- 在组件的 @Component() 装饰器中。
@Component({
providers: [
LoggerService,
// { provide: LoggerService, useClass: LoggerService }
//与上面写法相同,如果provide = useClass
//子组件和自己使用
]
})
类提供者
上节课演示的就是类提供者的创建和使用 假设有logger类:
import { Injectable } from '@angular/core';
@Injectable()
export class LoggerService {
logs: string[] = [];
constructor() { }
log(message: string) {
this.logs.push(message);
console.log(message);
}
}
类提供者,使用useClass在NgModule中提供该服务
providers: [
LoggerService,
// { provide: LoggerService, useClass: LoggerService } 与上面写法相同
]
提供不同于令牌(provide)的类
//app.moudle.ts===========================================
providers: [
//{ provide: LoggerService, useClass: LoggerService }
{ provide: LoggerService, useClass: BetterLoggerService }
]
//LoggerService===========================================
constructor(){}
log(msg:string){
consolg.log('this is flower log')
}
//BetterLoggerService=====================================
constructor(){}
log(msg?:string){
consolg.log('this is better log ' + msg)
}
//注入===============================================
constructor(private loggerServe:LoggerServe){}
ogOnInit():void{
this.loggerServe.log('flower')
}
别名提供者
useExisting指向的服务一定是已经注册过的,这是和useClass的区别之一
providers: [
UserLoggerService,
// 如果用useClass, 则会得到两份UserLoggerService实例
{ provide: LoggerService, useExisting: UserLoggerService }
]
值提供者
对于很简单的值,没必要把它做成一个类,可用useValue提供简单的值
providers: [{ provide: LoggerService, useValue: 'simpleValue' }]
constructor(private loggerServe:LoggerServe){
this.loggerServe //simpleValue
}
非类令牌
上面每个provide都是一个类,那么也可以用其它数据类型作为令牌
providers: [{ provide: 'httpApi', useValue: '123.com' }]
注入方式
constructor(@Inject('httpApi') private uri:string){ }
ogOnInit():void{
console.log('uri:',this.uri) //uri:123.com
}
InjectionToken
一般情况下无法使用ts接口作为令牌
interface AppConfig {
apiEndpoint: string;
title: string;
}
无法用AppConfig作为令牌:
[
{
provide: AppConfig,
useValue: {
apiEndpoint: 'api.heroes.com',
title: 'Dependency Injection'
}
}]
但又想要限制值的类型,可以借助InjectionToken
import { InjectionToken } from '@angular/core';
// 参数是该令牌的一个描述,可选
export const APP_CONFIG = new InjectionToken<AppConfig>('app.config');
//小括号里面是描述,中括号为令牌
provides:[
{
provide: APP_CONFIG,
useValue: {
apiEndpoint: 'api.heroes.com',
title: 'Dependency Injection'
}
}]
注入方式
class AppComponent {
constructor(@Inject(APP_CONFIG) config: AppConfig) {
this.title = config.title;
}
}
工厂提供者
自定义实例化服务 user-logger.service
import { Injectable } from '@angular/core';
import {UserService} from './user.service';
import {LoggerService} from './logger.service';
@Injectable()
export class UserLoggerService extends LoggerService {
constructor(private userService: UserService, extra: string) {
super();
console.log('UserLoggerService', extra);
}
log(message: string) {
const name = this.userService.user.name;
super.log(`Message to ${name}: ${message}`);
}
}
{
provide: UserLoggerService,
useFactory(userServe: UserService) {
return new UserLoggerService(userServe, 'factory msg');
},
deps: [UserService] // 依赖其它服务的话,要列在这里
}
多级注入器
ElementInjector
-
前两节,在@NgModule() 或 @Injectable() 提供的服务称为 ModuleInjector。
-
在 @Directive() 或 @Component()里提供的服务称为 ElementInjector。
服务查找规则(令牌解析规则)
- 优先查找ElementInjector,如果当前组件找不到提供者,将会去父组件中的ElementInjector(组件自己的providers,以及他爹他爹的爹。。。的providers,直到找到)
- 当所有的ElementInjector都找不到,就会去ModuleInjector中找(自己的模块,以及父模块向上查找)
- 如果ModuleInjector也找不到,就会抛出一个错误
- 对于同名的令牌,只会解析遇到的第一个依赖(就近原则)
解析修饰符
默认情况下,Angular 始终从当前的 Injector 开始,并一直向上搜索。修饰符使你可以更改开始(默认是自己)或结束位置。
- 如果 Angular 找不到你要的服务怎么办,用 @Optional()阻止报错
- 用 @SkipSelf()跳过自身,从父组件(指令)开始找
- 用@Self()只在当前组件(指令)找
- 用@Host()只在当前组件(指令)宿主上找
指定NgModule的注入
之前providedIn: 'root'指定从根模块中提供该服务 也可以像下面这样指定其它模块提供该服务
@Injectable({
providedIn: ComponentsModule
})
如果这么做,意味着这个服务无法在模块该模块内部使用,会报一个循环依赖的错误 只能在其它引入了ComponentsModule的NgModule中,才能使用该服务
viewProviders
上面说了,组件会从自身开始寻找服务,然后遍历父组件的ElementInjector,直到找到为止 viewProviders也有这个特性,区别是,对投影内容不可见
<app-mobile-list>
<app-mobile-content></app-mobile-content>
</app-mobile-list>
MobileContent无法找到MobileList里,用viewProviders提供的服务
单例与多例
对于ModuleInjector来讲,不论服务在哪个NgModule提供,所有注入的地方获得的都是同一份实例
对于ElementInjector来讲,每个元数据中注入的服务都是一份单独的实例
@Host()
上节课并未看出与@self的区别,其实它们的区别表现在投影上 当一个组件投影进某个父组件时,那个父组件就会变成宿主。即用@Host依然能访问父组件的ElementInjector