簡體   English   中英

在 APP_INITIALIZER 承諾解決之前構建 Angular (v5) 服務

[英]Angular (v5) service is getting constructed before APP_INITIALIZER promise resolves

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

}

應用配置文件

@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的請求得到解決后才被調用?

我還有一個類似的問題,為我解決的問題是使用 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();
    }
}

我使用的是新pipeable運營商在由谷歌建議對角5 rxjs的tap運營商就相當於老do運營商。

我還在 stackblitz.com 上創建了一個工作示例,以便您可以設置它工作。 示例鏈接

  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。 它只能在異步函數中使用。

它工作正常。

Injector 不會等待 observables 或 promises 並且沒有代碼可以讓它發生。

您應該使用自定義 Guard 或 Resolver 來確保在初始導航完成之前加載配置。

首先,您真的很接近正確的解決方案!

但在我解釋之前,讓我告訴你,使用subscribe服務通常是一種代碼味道。

也就是說,如果您查看APP_INITALIZER 源代碼,它只是在所有可用的初始化Promise.all上運行Promise.all Promise.all 本身在繼續之前等待所有承諾完成,因此,如果您希望 Angular 在引導應用程序之前等待它,您應該從您的函數返回一個承諾。

所以@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 {}

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

我認為您不應該訂閱 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()處於正確的位置,因為我並沒有真正使用 map 函數。

我面臨着類似的問題。 我認為這里沒有宣布並導致在其他答案示例中工作正常但不適用於作者的差異是注入 SomeOtherService 的地方。 如果它被注入到其他服務中,則初始化程序可能還沒有被解析。 我認為初始化程序會延遲將服務注入組件,而不是其他服務,這將解釋為什么它在其他答案中有效。 就我而言,由於https://github.com/ngrx/platform/issues/931 ,我遇到了這個問題

我認為您可以檢查在調用堆棧中調用“SomeOtherService”的位置。 就我而言,除了 APP_INITIALIZER 之外,我還添加了 HTTP_INTERCEPTORS,其中注入了“SomeOtherService”。 這使得服務在 APP_INITIALIZER 完成之前被調用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM