Angular(v5)服务在APP_INITIALIZER promise解析之前构建

时间:2018-03-08 00:29:51

标签: angular dependency-injection angular-services bootstrapping angular-di

我期待Angular等到我的loadConfig()函数在构造其他服务之前解析,但事实并非如此。

app.module.ts

export function initializeConfig(config: AppConfig){
    return () => config.loadConfig();
}

@NgModule({
     declarations: [...]
     providers: [
          AppConfig,
         { provide: APP_INITIALIZER, useFactory: initializeConfig, deps: [AppConfig], multi: true }
     ] })
export class AppModule {

}

app.config.ts

@Injectable()
export class AppConfig {

    config: any;

    constructor(
        private injector: Injector
    ){
    }

    public loadConfig() {
        const http = this.injector.get(HttpClient);

        return new Promise((resolve, reject) => {
            http.get('http://mycoolapp.com/env')
                .map((res) => res )
                .catch((err) => {
                    console.log("ERROR getting config data", err );
                    resolve(true);
                    return Observable.throw(err || 'Server error while getting environment');
                })
                .subscribe( (configData) => {
                    console.log("configData: ", configData);
                    this.config = configData;
                    resolve(true);
                });
        });
    }
}

一些-另一service.ts

@Injectable()
export class SomeOtherService {

    constructor(
        private appConfig: AppConfig
    ) {
         console.log("This is getting called before appConfig's loadConfig method is resolved!");
    }
 }

在从服务器接收数据之前调用SomeOtherService的构造函数。这是一个问题,因为SomeOtherService中的字段没有设置为正确的值。

如何确保在SomeOtherService的请求解决后才调用loadConfig的构造函数?

6 个答案:

答案 0 :(得分:5)

我还有一个类似的问题,解决了我的问题是使用Observable方法和运算符来做所有事情。然后,最后使用toPromise的{​​{1}}方法返回Observable。这也更简单,因为您不需要自己创建承诺。

Promise服务将看起来像这样:

AppConfig

我在rxjs中使用了新的pipeable operators,这是Google为Angular 5推荐的。import { Injectable, Injector } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs/Observable'; import { tap } from 'rxjs/operators/tap'; @Injectable() export class AppConfig { config: any = null; constructor( private injector: Injector ){ } public loadConfig() { const http = this.injector.get(HttpClient); return http.get('https://jsonplaceholder.typicode.com/posts/1').pipe( tap((returnedConfig) => this.config = returnedConfig) ).toPromise(); //return from([1]).toPromise(); } } 运算符等同于旧的tap运算符。

我还在stackblitz.com上创建了一个工作示例,以便您可以使用它。 Sample link

答案 1 :(得分:4)

Injector不会等待可观察量或承诺,也没有代码可以实现它。

您应该使用自定义Guard或Resolver来确保在初始导航完成之前加载配置。

答案 2 :(得分:2)

  async loadConfig() {
        const http = this.injector.get(HttpClient);

        const configData = await http.get('http://mycoolapp.com/env')
                    .map((res: Response) => {
                        return res.json();
                    }).catch((err: any) => {
                        return Observable.throw(err);
                    }).toPromise();
                this.config = configData;
        });
    }

await运算符用于等待Promise。它只能在异步函数中使用。

工作正常。

答案 3 :(得分:1)

首先,你真的很接近正确的解决方案!

但在我解释之前,请允许我告诉您,在服务中使用<?xml version="1.0" encoding="UTF-8"?> <facelet-taglib ... <function> <function-name>createArray</function-name> <function-class>package.UIHelper</function-class> <function-signature>Object[] createArray(int)</function-signature> </function> </facelet-taglib> 通常会产生代码异味。

也就是说,如果您查看APP_INITALIZER source code,它只是在所有可用的初始化程序上运行subscribe。 Promise.all本身等待所有承诺在继续之前完成,因此,如果你希望Angular在引导应用程序之前等待它,你应该从你的函数返回一个promise。

所以@AlesDanswer绝对是正确的方法。
(我只是想解释一下为什么)

我最近在我的一个项目中完成了这样的重构(使用Promise.all),如果需要,可以查看PR here

现在,如果我必须重写你的代码,我会这样做:

app.module.ts

APP_INITALIZER

app.config.ts;

export function initializeConfig(config: AppConfig) {
  return () => config.loadConfig().toPromise();
}

@NgModule({
  declarations: [
    //  ...
  ],
  providers: [
    HttpClientModule,
    AppConfig,
    {
      provide: APP_INITIALIZER,
      useFactory: initializeConfig,
      deps: [AppConfig, HttpClientModule],
      multi: true,
    },
  ],
})
export class AppModule {}

答案 4 :(得分:1)

我认为您不应该订阅http get调用,而是在解析loadConfig promise之前将其转换为promise,因为可以在请求返回之前调用subscribe的回调,因此可以提前解析。尝试:

@Injectable()
export class AppConfig {

    config: any;

    constructor(
        private injector: Injector
    ){
    }

    public loadConfig() {
        const http = this.injector.get(HttpClient);

        return new Promise((resolve, reject) => {
            http.get('http://mycoolapp.com/env')
                .map((res) => res )
                .toPromise()
                .catch((err) => {
                    console.log("ERROR getting config data", err );
                    resolve(true);
                    return Observable.throw(err || 'Server error while getting environment');
                })
                .then( (configData) => {
                    console.log("configData: ", configData);
                    this.config = configData;
                    resolve(true);
                });
        });
    }
}

我只是在超时时尝试过,但这很有效。我希望toPromise()处于正确的位置,因为我并没有真正使用地图功能。

答案 5 :(得分:0)

我正面临类似的问题。我认为未在此处宣布的差异导致在其他答案示例中效果很好,但不适用于作者,因此注入SomeOtherService。如果将其注入到其他服务中,则可能无法解析初始化程序。我认为初始化器将延迟将服务注入组件而不是其他服务,这将解释其在其他答案中的作用。就我而言,由于https://github.com/ngrx/platform/issues/931

,我遇到了此问题