【问题标题】:Angular (v5) service is getting constructed before APP_INITIALIZER promise resolves在 APP_INITIALIZER 承诺解决之前构建 Angular (v5) 服务
【发布时间】:2018-08-16 05:47:01
【问题描述】:

我希望 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);
                });
        });
    }
}

some-other-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 的请求得到解决后才被调用?

【问题讨论】:

  • 您在哪里使用 SomeOtherService。可以复制吗?
  • 你能在 stackblitz 上发布一个示例吗? @yurzi 发布了一个似乎与您拥有的代码完全相同的代码并且它可以正常工作。另外,您的代码中是否有任何 HttpInterceptors?
  • 不要在那里使用.catch,因为 APP_INITIALIZER 有它自己的 catch 可以停止 APP。另外,使用来自@AlesD 答案的.toPromise()
  • 我遇到了类似的问题:(我试图在 stackblitz 中重现它,但没有成功。这令人沮丧。你终于解决了你的问题吗?你找到任何可能的原因了吗?如何解决?

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


【解决方案1】:

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

【讨论】:

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

    【讨论】:

    • 不需要地图。谢谢,你的解决方案很完美
    【解决方案3】:

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

    ,我遇到了这个问题

    【讨论】:

    【解决方案4】:
      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。它只能在异步函数中使用。

    一切正常。

    【讨论】:

    • 不适合我。在mycoolapp.com/env 的响应到达之前,仍在我的应用中实例化服务
    • 调用res.json() 不能与新的HttpClient 一起使用,结果不会是Response 类型
    • 是的,你是对的。这个例子来自 Angular4。我会更新它。谢谢大卫
    【解决方案5】:

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

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

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

    所以@AlesDanswer 绝对是正确的选择。
    (我只是想解释更多原因)

    我最近在我的一个项目中进行了这样的重构(使用APP_INITALIZER),如果需要,可以查看 PR here

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

    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 {}
    

    app.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');
          })
        );
      }
    }
    

    【讨论】:

    • 我认为 AlesD 有一个堆栈闪电战,以及为什么我在回答中详细说明了这一点(不返回承诺,但订阅 --> 不行)
    • 好的,那我要问问AlesD:)
    • 但是……我刚刚告诉过你。他不回报承诺,仅此而已。除了这个 + 一个有效的代码示例之外,您还期望什么?
    • 那是什么? return new Promise 然后他打电话给resolve()
    • 这是正确答案。如果initializeConfig 函数返回一个promise,APP_INITIALIZER 将等待直到promise 解决。将 observable 转化为 Promise 的正确方法是使用 .toPromise()。
    【解决方案6】:

    Injector 不会等待 observables 或 Promise,也没有任何代码可以让它发生。

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

    【讨论】:

      【解决方案7】:

      我也有一个类似的问题,解决这个问题的方法是使用 Observable 方法和运算符来做所有事情。然后最后只使用ObservabletoPromise 方法返回一个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 中使用了新的 pipeable operators,这是 Google 为 Angular 5 推荐的。tap 运算符等同于旧的 do 运算符。

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

      【讨论】:

      • 嘿,你能详细说明为什么他的代码不起作用吗?这是一个例子stackblitz.com/edit/angular-3zszwn?file=app%2Fapp.component.ts
      • 如果您按照我建议的方式编写它,它是否适用于您的应用程序?在 stackblitz 上,您的代码运行正常。如果您查看日志,则会在服务中的消息之前记录 configData 对象。我创建了一个 fork,在其中添加了 observable 的延迟,您可以清楚地看到应用程序在启动前等待了 10 秒,并且配置在 SomeOtherService 中可用。 link。检查您是否在代码中执行其他操作。您是否也在初始化程序中使用某些服务?
      • 嘿@AlesD 抱歉耽搁了。我测试了这个解决方案,但它不起作用。它进入了loadConfig,但在返回HTTP请求之前,仍然在我的应用程序中加载了其他服务。
      • 那你肯定在做别的事情。您是否有可能通过发布更多代码在 stackblitz.com 上重现该问题?
      • 我试过这个,但它对我不起作用。我也在使用 ngrx 并在 app,module.ts 中有 StoreModule.forRoot({}) 和 EffectsModule.forRoot([]) 调用,理想情况下应该没有任何区别。
      猜你喜欢
      • 2015-06-24
      • 1970-01-01
      • 1970-01-01
      • 2018-12-14
      • 1970-01-01
      • 1970-01-01
      • 2020-01-16
      • 2019-04-13
      • 2021-08-11
      相关资源
      最近更新 更多