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
のコンストラクターが確実に呼び出されるようにするにはどうすればよいですか?
私も同様の問題を抱えていましたが、この問題を解決したのは、Observableメソッドと演算子を使用してすべてを実行することでした。最後に、toPromise
のObservable
メソッドを使用して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();
}
}
Googleが推奨するrxjsの新しい pipeable演算子 を使用していますAngular 5. 5. 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を待機するために使用されます。非同期関数内でのみ使用できます。
正常に動作しています。
リクエストを返す前にサブスクライブするコールバックが呼び出される可能性があるため、http getコールをサブスクライブするのではなく、promiseに変換してから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()
が正しい位置にあることを願っています。
まず第一に、あなたは本当に正しい解決策に本当に近づいていました!
しかし、説明する前に、サービスにsubscribe
を使用することは、多くの場合コードのにおいであることをお伝えします。
そうは言っても、 APP_INITALIZERソースコード を見ると、Promise.all
利用可能なすべての初期化子。 Promise.all自体は続行する前にすべてのpromiseが完了するのを待機しているため、Angularでアプリをブートストラップする前にそれを待機する場合は、関数からpromiseを返す必要があります。
だから @ AlesD の answer は間違いなく正しい方法です
(そして、私はその理由をもう少し説明しようとしています)
私はそのようなリファクタリングをしました(APP_INITALIZER
)ごく最近、私のプロジェクトの1つに参加しました。必要に応じて、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');
})
);
}
}
同様の問題に直面しています。ここで発表されなかった違いと他の回答の例でうまく機能する原因になると思いますが、著者にとってはそうではなく、SomeOtherServiceが挿入されます。他のサービスに注入された場合、初期化子がまだ解決されていない可能性があります。イニシャライザは、他のサービスではなくコンポーネントへのサービスの注入を遅らせ、それが他の回答で機能する理由を説明すると思います。私の場合、 https://github.com/ngrx/platform/issues/931 が原因でこの問題が発生しました