28

我希望 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 {

}

应用程序.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);
                });
        });
    }
}

其他一些服务.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' 的请求得到解决后才被调用?

4

7 回答 7

12

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

AppConfig服务将如下所示:

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();
    }
}

我在 rxjs 中使用新的可管道运算符,这是 Google 为 Angular 5 推荐的。该tap运算符等效于旧do运算符。

我还在 stackblitz.com 上创建了一个工作示例,因此您可以确保它正常工作。示例链接

于 2018-03-21T23:42:09.513 回答
3
  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。它只能在异步函数内部使用。

它工作正常。

于 2018-03-27T11:32:16.027 回答
2

Injector 不会等待 observables 或 Promise,也没有代码可以实现它。

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

于 2018-03-22T08:04:10.150 回答
2

我认为您不应该订阅 http get 调用,而是在解决 loadConfig 承诺之前将其转换为承诺,因为订阅的回调可能会在请求返回之前被调用,因此会提前解决承诺。尝试:

@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()是在正确的位置,因为我并没有真正使用地图功能。

于 2018-03-28T08:46:18.733 回答
2

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

但在我解释之前,让我告诉你,使用subscribe服务通常是一种代码味道。

也就是说,如果您查看APP_INITALIZER 源代码,它只是Promise.all在所有可用的初始化程序上运行。Promise.all 在继续之前等待所有的 Promise 完成,因此,如果你希望 Angular 在启动应用程序之前等待它,你应该从你的函数中返回一个 Promise。

所以@AlesD答案绝对是正确的方法。
(我只是想解释更多原因)

我最近在我的一个项目中进行了这样的重构(使用),如果你愿意,可以在这里APP_INITALIZER查看 PR 。

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

app.module.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 {}

应用程序.config.ts;

@Injectable()
export class AppConfig {
  config: any;

  constructor(private http: HttpClient) {}

  // note: instead of any you should put your config type
  public loadConfig(): Observable<any> {
    return this.http.get('http://mycoolapp.com/env').pipe(
      map(res => res),
      tap(configData => (this.config = configData)),
      catchError(err => {
        console.log('ERROR getting config data', err);
        return _throw(err || 'Server error while getting environment');
      })
    );
  }
}
于 2018-03-22T09:50:15.143 回答
1

我面临着类似的问题。我认为这里没有公布的差异导致在其他答案示例中工作正常,但对于作者来说不是 SomeOtherService 被注入的地方。如果它被注入到其他一些服务中,初始化器可能还没有被解析。我认为初始化程序会延迟将服务注入组件,而不是其他服务,这将解释为什么它在其他答案中起作用。就我而言,由于https://github.com/ngrx/platform/issues/931 ,我遇到了这个问题

于 2020-01-23T13:02:49.043 回答
0

我认为您可以检查在调用堆栈中调用“SomeOtherService”的位置。就我而言,除了 APP_INITIALIZER 之外,我还添加了 HTTP_INTERCEPTORS,其中“SomeOtherService”被注入其中。这使得服务在 APP_INITIALIZER 完成之前被调用。

于 2021-10-28T08:07:33.053 回答