我期望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();
}
}
我正在rxjs中使用新的pipeable operators,这是Google为Angular 5推荐的。tap
运算符等效于旧的do
运算符。
我还在stackblitz.com上创建了一个工作示例,因此您可以确保它正常工作。 Sample link
Injector不会等待可观察到的或应许的,并且没有代码可以实现它。
您应该使用自定义Guard或Resolver来确保在初始导航完成之前已加载配置。
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;
});
}
等待操作符用于等待Promise。它只能在异步函数中使用。
工作正常。
首先,您真的很接近正确的解决方案!
但是在我解释之前,让我告诉您,在服务中使用subscribe
通常是代码的味道。
也就是说,如果您查看APP_INITALIZER source code,它只是在所有可用的初始化程序上运行Promise.all
。 Promise.all本身在等待所有诺言完成之后再继续,因此,如果希望Angular在启动应用程序之前等待它,则应从函数中返回一个诺言。
所以@AlesD的answer绝对是正确的选择。(我只是想解释更多原因)
我最近在我的一个项目中进行了这样的重构(使用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');
})
);
}
}
我认为您不应该订阅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函数。