可观察到的角度解析器过早完成

时间:2020-05-31 09:17:12

标签: angular rxjs angular-resolver angular-guards

我正在将可观察的主题从服务传递给解析器。当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();
}

2 个答案:

答案 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;
      }),
  );

Source

如您所见,如果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();
}
相关问题