Angular复兴
Angular

依赖提供者

Guo Eagle
#Angular

Angular 官方文档对于依赖提供者,也就是providers的解释如下:

通过配置提供者,你可以把服务提供给那些需要它们的应用部件。 By configuring providers, you can make services available to the parts of your application that need them.

依赖提供者会使用 DI 令牌来配置注入器,注入器会用它来提供这个依赖值的具体的、运行时版本 A dependency provider configures an injector with a DI token, which that injector uses to provide the runtime version of a dependency value.

简单总结依赖提供者做了两件事:

在之前的示例中,当使用@Inject(HeroService)注入一个服务时,Angular 注入器会通过new HeroService()实例化一个类返回依赖值,类实例化其实就是一种如何提供依赖值的方式,那么 Angular 中除了类实例化提供依赖值外还提供给了如下类型的Provider,每种Provider都有其使用场景。

export declare type Provider = TypeProvider | ValueProvider | ClassProvider 
| ConstructorProvider | ExistingProvider | FactoryProvider | any[];

export declare type StaticProvider = ValueProvider | ExistingProvider | 
  StaticClassProvider | ConstructorProvider | FactoryProvider | any[];

关于限制服务可使用的范围就更好理解了,满足只能在某个模块或者组件注入HeroService的场景。

如何定义提供者

在组件或者模块中通过装饰器元数据providers定义提供者。

比如:类提供者

image.png

image.png

类提供者

类型: TypeProviderClassProvider,类提供者应该是最常用的一种,在依赖注入简介章节的示例就是,简写和全写的配置如下:

{
  provides: [ Logger ]   // 简写
  provides: [{ provide: Logger, useClass: Logger }]  // 全写  
}

使用场景:

[{ provide: Logger, useClass: BetterLogger }] 
// 当使用 Logger 令牌请求 Logger 时,返回一个 BetterLogger

别名类提供者

类型: ExistingProvider,在下面的例子中,当组件请求新的或旧的 Logger 时,注入器都会注入一个 NewLogger 的实例。 通过这种方式, OldLogger就成了NewLogger的别名。

[ 
  NewLogger,
  // Alias OldLogger reference to NewLogger
  { provide: OldLogger, useExisting: NewLogger}
]

那么别名类提供者和类提供者有什么区别呢?

[ 
  NewLogger,
  { provide: OldLogger, useClass: NewLogger}
]

使用场景:

// Class used as a "narrowing" interface that exposes a minimal logger
// Other members of the actual implementation are invisible
export abstract class MinimalLogger {
  abstract logs: string[];
  abstract logInfo: (msg: string) => void;
}

{ provide: MinimalLogger, useExisting: LoggerService },
// parent.ts
class abstract Parent {
     ...
}

// alex.component.ts
providers: [{ provide: Parent, useExisting: forwardRef(() => AlexComponent) }]
class AlexComponent {
   // ChildComponent
}

// child.component.ts
class ChildComponent {
    constructor(parent: Parent)
}

对象提供者

类型: ValueProvider,要注入一个对象,可以用useValue选项来配置注入器,下面的提供者定义对象使用useValue作为 key 来把该变量与Logger令牌关联起来。

// An object in the shape of the logger service
function silentLoggerFn() {}

export const silentLogger = {
  logs: ['Silent logger says "hahaha!". Provided via "useValue"'],
  log: silentLoggerFn
};

[{ provide: Logger, useValue: silentLogger }]

使用场景:

通过对象提供者注入一个配置对象,一般推荐使用InjectionToken作为令牌。

// src/app/app.config.ts
import { InjectionToken } from '@angular/core';

export const APP_CONFIG = new InjectionToken<AppConfig>('app.config');

export const HERO_DI_CONFIG: AppConfig = {
  apiEndpoint: 'api.heroes.com',
  title: 'Dependency Injection'
};
// src/app/app.module.ts (providers)
providers: [
  { provide: APP_CONFIG, useValue: HERO_DI_CONFIG }
],

当使用InjectionToken作为令牌时,在组件或者服务中必须借助参数装饰器@Inject(),才可以把这个配置对象注入到构造函数中。

// src/app/app.component.ts
constructor(@Inject(APP_CONFIG) config: AppConfig) {
  this.title = config.title;
}

Inject, 类构造函数中依赖项参数上的参数装饰器,用于指定依赖项的自定义提供者,参数传入 DI Token,映射到要注入的依赖项。

同时在定义InjectionToken的时候还可以设置providedInfactory

export const TOKEN_FACTORY = new InjectionToken('factory-token', {
    providedIn: 'root',
    factory: () => {
        return 'I am from InjectionToken factory';
    }
});

工厂提供者

类型: FactoryProvider,要想根据运行前尚不可用的信息创建可变的依赖值,可以使用工厂提供者。也就是完全自己决定如何创建依赖。

比如:只有授权用户才能看到HeroService中的秘密英雄。

// src/app/heroes/hero.service.ts (excerpt)
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);
}
// src/app/heroes/hero.service.provider.ts (excerpt)

const heroServiceFactory = (logger: Logger, userService: UserService) => {
  return new HeroService(logger, userService.user.isAuthorized);
};

export const heroServiceProvider =
  { 
    provide: HeroService,
    useFactory: heroServiceFactory,
    deps: [Logger, UserService]
  };

理解了工厂提供给者后再回过头看,useValueuseClassuseExisting的区别就更简单了,provider就是在装饰器中通过providers数组配置的元数据对象。

image.png

接口和依赖注入

虽然 TypeScript 的AppConfig接口可以在类中提供类型支持,但它在依赖注入时却没有任何作用。在 TypeScript 中,接口只能作为类型检查,它没有可供 DI 框架使用的运行时表示形式或令牌。

// Can't use interface as provider token
[{ provide: AppConfig, useValue: HERO_DI_CONFIG })]

// Can't inject using the interface as the parameter type
constructor(private config: AppConfig){ }

multi 多个依赖值

当配置提供者时设置multi为 true 时,通过@Inject(DIToken)参数注入获取的依赖值就会返回一个数组。

export declare interface ClassProvider extends ClassSansProvider {
    /**
     * An injection token. (Typically an instance of `Type` or `InjectionToken`, but can be `any`).
     */
    provide: any;
    /**
     * When true, injector returns an array of instances. This is useful to allow multiple
     * providers spread across many files to provide configuration information to a common token.
     */
    multi?: boolean;
}

使用场景:

内置 API ,比如:NG_VALUE_ACCESSORHTTP_INTERCEPTORSAPP_INITIALIZER

@Component({
    selector: 'select',
    templateUrl: './select.component.html',
    changeDetection: ChangeDetectionStrategy.OnPush,
    providers: [
        {
            provide: NG_VALUE_ACCESSOR,
            useExisting: forwardRef(() => ThySelectComponent),
            multi: true
        }
    ]
})
export class ThySelectComponent implements ControlValueAccessor {}

以上就是依赖提供者 provider 相关的介绍,理解了 factory 提供依赖值后再看其他类型就会简单很多,其他的类型就是 factory 之上高级的 API 而已,满足不同的场景需要,这是 Angular 依赖注入入门比较难懂的知识,那么接下来的多级注入器是另一个重要的知识点,这两部分都深入理解那么 Angular 依赖注入就不在是难点了。

← 返回博客