0%

angular-di-define-dependency-providers

There are many ways to define a dependency provider in Angular. In this article, we will discuss the following ways:

useClass

useClass是依赖注入中提供provider最常见的方式,我们项目中90%用的都是这种方式,下面来看看怎么使用这种方式,假设我们有一个ProductComponent组件,它依赖一个OrderService服务,我们可以像下面这样提供OrderService:

1
2
3
4
5
6
7
8
9
@Component({
selector: 'app-product',
standalone: true,
providers: [OrderService], // <-- Provide OrderService
templateUrl: './product.component.html',
styleUrl: './product.component.scss'
})
export class ProductComponent {
}

上面的例子其实是简写形式,它等价于下面的完整形式:也就是当provide对应的值和useClass对应的值相同时,我们可以采用这种简写形式。

1
providers: [{ provide: OrderService, useClass: OrderService }],

还有一点需要注意的是:如果OrderServiceprovidedIn: 'root'的话,那么我们就不需要在providers中再次提供OrderService了,直接在构造函数中注入即可。

1
2
3
export class ProductComponent {
constructor(private orderService: OrderService) {}
}

useClass在单元测试中特别有用,如果要测试的Service很复杂,我们可以提供一个mockService来代替他,这个mockService只要满足最小测试范围内即可,这样就可以保证测试的独立性。

1
providers: [{ provide: UserService, useClass: mockUserService}]

useExisting

useExisting通常用于提供一个别名,也就是说,useExisting提供了多种访问同一Service的方式,下面来看一个例子:
假设我们的Order分为线下订单和线上订单,那么线上订单我们可以单独定义一个Service,叫做OnlineOrderService,这个Service需要的功能OrderService中都有,所以我们可以如下使用它。

1
2
3
4
5
6
7
8
9
@Component({
selector: 'app-product',
standalone: true,
providers: [OrderService, {provide: OnlineOrderService, useExisting: OrderService}],
templateUrl: './product.component.html',
styleUrl: './product.component.scss'
})
export class ProductComponent {
}

这里:OnlineOrderServiceOrderService的别名,我们可以通过OnlineOrderService来访问OrderService的实例。

这种方式通常发生在旧系统到新系统的迁移时,比如迁移的时候,我们可能会更改一些Service的名字,但是为了兼容旧系统,我们可以通过useExisting来提供别名。

以Logger为例,假设旧系统使用的是OldLoggerService, 新系统使用的是NewLoggerService,那么我们可以这样定义:

1
providers: [{provide: OldLoggerService, useExisting: NewLoggerService}]

useFactory

useFactory,顾名思义,这种方式使用一个工厂方法来提供依赖的实例,通常用在需要参数的Service中,上面的例子中,我们提供的Service都是无参数的,下面我们来看一个需要参数的Service。

下面这个HeroService需要一个Logger和一个isAuthorized参数,无论是useClass,还是useExisting都无法满足这个需求,这时我们就可以使用useFactory来提供这个Service。

1
2
3
4
5
6
7
8
9
10
11
class HeroService {
constructor(
private logger: Logger,
private isAuthorized: boolean) { }

getHeroes() {
const auth = this.isAuthorized ? 'authorized' : 'unauthorized';
this.logger.log(`Getting heroes for ${auth} user.`);
return HEROES.filter(hero => this.isAuthorized || !hero.isSecret);
}
}

useFactory后面跟的是一个函数,所以我们先定义这个函数。(注意Factory函数是一个injection context,在这个函数内部可以使用inject()方法来注入其他依赖。)

1
2
const heroServiceFactory = (logger: Logger, userService: UserService) =>
new HeroService(logger, userService.user.isAuthorized);

然后使用useFactory来提供HeroService

1
2
3
4
5
6
7
8
9
providers: [
Logger,
UserService,
{
provide: HeroService,
useFactory: heroServiceFactory,
deps: [Logger, UserService] // deps是用来给工厂方法传递参数的
}
]

注意观察,上面的工厂函数heroServiceFactory中的参数loggeruserService是通过deps传递进来的。

useValue

useValue允许你绑定一个静态值到一个token上,这种方式通常用于提供一些常量,比如配置信息等。如果我们需要运行时动态取值,就可以使用这种方式了。

useValue的使用步骤如下:

  1. 定义一个token
  2. 使用useValue来提供这个token
  3. 在构造函数中注入这个token

token是简单值

定义一个token

首先新建一个文件src/app/config/AppConfig.ts用来存储token。这里我们创建一个token用来存储主题信息。

1
2
3
import {InjectionToken} from "@angular/core";

export const AppTheme = new InjectionToken<string>('appTheme');

使用useValue来提供这个token

由于我们这个token比较简单,就是字符串类型的,所以直接用字符串’light’来提供这个token。

1
providers: [{provide: AppTheme, useValue: 'light'}],

在构造函数中注入这个token

1
2
3
constructor(@Inject(AppTheme) private theme: string) {
console.log(theme);
}

token是对象

对于复杂的token,我们将其封装到一个JS对象中,如果一个app中有多个配置项,我们可以将这些配置项封装到一个对象AppConfig中,然后使用useValue来提供这个对象。

定义一个token

首先定义token对应的对象类型,这里使用typescript中的interface来定义。

1
2
3
4
5
6
7
8
// src/app/config/AppConfig.ts
export interface AppConfig {
theme: string;
version: string;
production: boolean;
baseUrl: string;
apiEndpoint: string;
}

然后定义该类型对应的token实例。

1
2
// src/app/config/AppConfig.ts
export const APP_CONFIG = new InjectionToken<AppConfig>('app config');

使用useValue来提供这个token

先创建一个AppConfig对象,然后使用useValue来提供这个对象。

1
2
3
4
5
6
7
8
9
10
// app.component.ts
const currentAppConfig: AppConfig = {
theme: 'light',
version: '1.0.0',
production: true,
baseUrl: 'https://example.com',
apiEndpoint: 'https://example.com/api',
}

providers: [{provide: APP_CONFIG, useValue: currentAppConfig}],

在构造函数中注入这个token

1
2
3
constructor(@Inject(APP_CONFIG) private config: AppConfig) {
console.log(config);
}

这样在Component中,就可以使用this.config来访问这个配置对象了。

References

  1. https://angular.dev/guide/di/dependency-injection-providers
  2. https://medium.com/@matsal.dev/angular-usevalue-useclass-useexisting-and-usefactory-in-a-nutshell-97db8d206084