【问题标题】:How to inject a factory dependency into Angular provider factory method如何将工厂依赖注入 Angular 提供者工厂方法
【发布时间】:2020-05-25 04:55:42
【问题描述】:

我有一个 Angular 9 (9.1.4) 项目,并且有一个 BASE_URL 的自定义工厂提供程序(该项目基于 .NET Core Angular 模板)

// main.ts

export function getBaseUrl() {
  return document.getElementsByTagName('base')[0].href;
}

const providers = [
  { provide: 'BASE_URL', useFactory: getBaseUrl, deps: [] }
];

platformBrowserDynamic(providers).bootstrapModule(AppModule)
  .catch(err => console.error(err));

如果我想在类中使用 BASE_URL,超级简单,我可以要求将它注入到构造函数中

export class SomeService {

  constructor(
    private _httpClient: HttpClient,
    @Inject('BASE_URL') private _baseUrl: string  // here we go
  ) { }

它按预期工作。到目前为止一切顺利...

我最近在应用程序中添加了一个APP_INITIALIZER,因此我可以预先从服务器加载一些配置。这是通过使用工厂函数注册提供者来完成的,也可以将依赖项注入其中。

但是,由于某种原因,使用@Inject 的简单方法在那里不起作用:

// app.module.ts
@NgModule({
  // other stuff
  providers: [
    SomeService, // here the @Inject works
    {
      provide: APP_INITIALIZER,
      useFactory: (httpClient: HttpClient, @Inject('BASE_URL') baseUrl: string) => {
        // httpClient is OK, but baseUrl is undefined
        // app init implementation
      },
      deps: [HttpClient],
      multi: true
    }],


因为在运行时未定义 baseUrl。

我可以让它工作的唯一方法是从 @angular/core 注入 Injector:

{
  provide: APP_INITIALIZER,
  useFactory: (httpClient: HttpClient, injector: Injector) => {
    cont baseUrl = injector.get('BASE_URL'); // works, but deprecated
    // app init implementation
  },
  deps: [HttpClient, Injector],
  multi: true
}

但问题在于 get 方法的弃用警告(自 v4.0.0 起已弃用)。

让这种工厂注入工作的正确方法是什么?

【问题讨论】:

    标签: angular


    【解决方案1】:

    可以像使用HttpClient 一样提供所需的值。

    由于该值是一个简单的字符串,因此需要自定义 InjectionToken

    base-url.ts(我使用了一个单独的文件来避免循环依赖)

    import { InjectionToken } from "@angular/core";
    
    export const BASE_URL = new InjectionToken<string>('BaseUrl');
    
    export function getBaseUrl() {
      return document.getElementsByTagName('base')[0].href;
    }
    

    app.module.ts

    import { BASE_URL } from "../base-url";
    // ...
      providers: [{
        provide: APP_INITIALIZER,
        useFactory: (httpClient: HttpClient, baseUrl: string) => {
          return () => {
            console.log(baseUrl);
            return Promise.resolve();
          };
        },
        deps: [HttpClient, BASE_URL], // <-- Here
        multi: true
      }],
    // ...
    

    编辑
    是的,实际上,可以将字符串用作标记,因此将其作为第二个值添加到 deps 数组会起到作用。最初的问题实际上是无法装饰函数(而不是方法)的参数,这就是为什么在注入工厂的情况下使用deps 数组。
    所以,这行得通:

      providers: [{
        provide: APP_INITIALIZER,
        useFactory: (httpClient: HttpClient, baseUrl: string) => {
          return () => {
            console.log(baseUrl);
            return Promise.resolve();
          };
        },
        deps: [HttpClient, 'BASE_URL'], // <-- Here
        multi: true
      }],
    

    但我真的建议改用InjectionToken,因为它更安全。另请注意,injector.get('BASE_URL'); 的弃用警告与字符串标记有关,因为only the signature of get that accepts stings is depricated

    【讨论】:

    • 是的,这可行,但有没有办法在不创建我自己的令牌的情况下做到这一点?又名。有没有办法使用最初提供的字符串令牌'BASE_URL'注入它(类似于它在构造函数中的工作方式)? main.ts 是由模板生成的,所以我想知道我是否可以以某种方式直接使用已经提供的令牌/提供者......
    • @yohny,我编辑了答案,添加了一个替代选项和一个小解释。
    猜你喜欢
    • 2020-11-18
    • 2016-02-18
    • 1970-01-01
    • 2015-03-04
    • 1970-01-01
    • 1970-01-01
    • 2012-08-09
    • 1970-01-01
    • 2016-10-20
    相关资源
    最近更新 更多