我正在将可观察的主题从服务传递给解析器。当http请求完成或发现它需要的数据已缓存时,该服务可能会完成可观察项。在第二种情况下,在数据被缓存的情况下,可观察到的内容似乎过早完成,因为路由转换不会发生。如果我将requestSubject.complete()放入具有某些超时的setTimeout内,则可以正常工作,否则无法正常工作。
//resolve function from resolver
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): any {
const id = +route.paramMap.get('id');
const language = route.queryParamMap.get('language');
const request = this.service.get(id, language);
request.pipe(share()).subscribe(
() => {},
() => { this.router.navigateByUrl(AppRoutingConfig.search); });
return request;
}
//the service get function
get(id: number, language: string): Observable<any> {
const requestSubject = new Subject();
if (!isDataCached(id)) {
const url = `some url`;
const sub = this.http.get(url).subscribe((item: any) => {
requestSubject.next(1);
}, () => {
requestSubject.error(0);
}, () => {
requestSubject.complete();
});
} else {
requestSubject.complete();
}
return requestSubject.asObservable();
}
答案 0 :(得分:3)
由于可观察到的路线似乎未完成,因此可观察的时间太早了
我会说主题没有做任何事情。
如果未缓存数据,将运行requestSubject.complete();
,它将同步向其所有订阅者发出完整的通知。
这意味着在之后返回requestSubject
不会做任何事情,因为requestSubject
不会发出值。
它在不缓存数据时有效,因为到Subject发出并完成该数据时,它已经具有一个订户,该订户可以决定是否继续路由转换。
例如,这是Angular内部订阅resolve
属性中提供的所有可观察对象的方式:
return from(keys).pipe(
mergeMap(
(key: string) => getResolver(resolve[key], futureARS, futureRSS, moduleInjector)
.pipe(tap((value: any) => {
data[key] = value;
}))),
takeLast(1),
mergeMap(() => {
// Ensure all resolvers returned values, otherwise don't emit any "next" and just complete
// the chain which will cancel navigation
if (Object.keys(data).length === keys.length) {
return of(data);
}
return EMPTY;
}),
);
如您所见,如果resolve()
fn没有发出任何值,则将发出EMPTY
可观察值,这将导致导航被取消。
我还要修改您的resolve
函数:
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): any {
const id = +route.paramMap.get('id');
const language = route.queryParamMap.get('language');
const request$ = this.service.get(id, language);
return request$.pipe(
tap(
null,
err => this.router.navigateByUrl(AppRoutingConfig.search),
),
);
}
我认为share()
是不必要的,因为您只有一个订户。
因此,我认为一种快速解决方案(我完全不建议这样做),在这种情况下也将发送缓存的值:
// Inside `service.get()`
else {
// The value is cached
of(getCachedValue(id)).pipe(
subscribeOn(asyncScheduler) // delay(0) would also work
).subscribe(v => {
requestSubject.next(v)
requestSubject.complete()
})
}
return requestSubject.
另一种解决方案是考虑在interceptors
级别添加缓存层,因为我认为这将简化您的代码。如果您决定改用这种方法,您的resolve()
fn可能会返回类似this.service.get(...)
的内容,因为拦截器会拦截所有的东西,因此您可以从那里返回缓存的值
答案 1 :(得分:2)
在您的情况下,您无需使用Subject
来代替ReplaySubject
,它会记住最后一个值,并在有人订阅时将其发出。
尽管如此,我还是将get()
更改为对流使用缓存状态。
//resolve function from resolver
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): any {
const id = +route.paramMap.get('id');
const language = route.queryParamMap.get('language');
return this.service.get(id, language).pipe(
catchError(() => {
this.router.navigateByUrl(AppRoutingConfig.search);
return EMPTY; // to suppress the stream
}),
);
}
//the service get function
private readonly cache = new Map(); // our cache for urls
get(id: number, language: string): Observable<any> {
const url = `some url`;
// if we had the id - we return its observable.
if (this.cache.has(id)) {
return cache.get(id);
}
// otherwise we create a replay subject.
const status$ = new ReplaySubject(1);
this.cache.set(id, status$);
this.http.get(url).subscribe(
() => status$.next(),
e => status$.error(e),
() => requestSubject.complete(),
);
return status$.asObservable();
}