Angular(v5)服务是在APP_INITIALIZER承诺解析之前构建的



我希望Angular等到我的loadConfig()函数解析后再构建其他服务,但事实并非如此。

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

其他一些服务。ts

@Injectable()
export class SomeOtherService {
constructor(
private appConfig: AppConfig
) {
console.log("This is getting called before appConfig's loadConfig method is resolved!");
}
}

SomeOtherService的构造函数在从服务器接收数据之前被调用。这是一个问题,因为SomeOtherService中的字段没有被设置为它们的正确值。

如何确保只有在loadConfig的请求得到解决后才能调用SomeOtherService的构造函数

我还有一个类似的问题,对我来说,解决这个问题的是使用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中使用了谷歌为Angular 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;
});
}

wait运算符用于等待Promise。它只能在异步函数内部使用。

它运行良好。

Injector不等待可观察性或承诺,也没有代码可以实现这一点。

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

首先,您非常接近正确的解决方案!

但在我解释之前,让我告诉您,在服务中使用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 {}

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 promise之前将其转换为promise,因为订阅的回调可能在请求返回之前被调用,因此会提前解析promise。尝试:

@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

我想你可以检查一下哪里"SometherService";在调用堆栈中被调用。在我的情况下,除了APP_INITIALIZER之外,我还添加了HTTP_INTERCEPTORS,其中";SometherService";在那里注射。这使得服务在APP_INITIALIZER完成之前被调用。

相关内容

  • 没有找到相关文章

最新更新