angulartypescriptrxjsngrx-entityangular-ngrx-data

Angular resolver with Ngrx data service error


I have an NGRX Data entity service that is working, I want to preload data before accessing to a route, therefore I made a resolver.

import { Injectable } from "@angular/core";
import {
  ActivatedRouteSnapshot,
  Resolve,
  RouterStateSnapshot,
} from "@angular/router";
import { Observable, race } from "rxjs";
import { filter, first, tap, mapTo } from "rxjs/operators";
import { ExerciseEntityService } from "./exercise-entity.service";

@Injectable()
export class ExercisesResolver implements Resolve<boolean> {
  constructor(private exercisesService: ExerciseEntityService) {}

  resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean> {
    return this.exercisesService.loaded$.pipe(
      tap((loaded) => {
        if (!loaded) {
          this.exercisesService.getAll();
        }
      }),
      filter((loaded) => !!loaded),
      first()
    );
  }
}

If the service is responding successfully, the route gets resolved but if there is an error resolver will not get resolved. How can I manage if service for example returns an error? I tried with a race this way, but It doesn't work:

import { Injectable } from '@angular/core';
import {
  ActivatedRouteSnapshot,
  Resolve,
  RouterStateSnapshot
} from '@angular/router';
import { Observable, race } from 'rxjs';
import { filter, first, tap, mapTo } from 'rxjs/operators';
import { ExerciseEntityService } from './exercise-entity.service';

@Injectable()
export class ExercisesResolver implements Resolve<boolean> {
  constructor(private exercisesService: ExerciseEntityService) {}

  resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean> {
    return race(
      this.exercisesService.loaded$.pipe(
        tap((loaded) => {
          if (!loaded) {
            this.exercisesService.getAll();
          }
        }),
        filter((loaded) => !!loaded),
        first()
      ),
      this.exercisesService.errors$.pipe(mapTo(true))
    );
  }
}


Solution

  • After struggling with different kind of approaches with combineLatest, mergeMap, race and other operators, solved this way:

    import { Injectable } from '@angular/core';
    import {
      ActivatedRouteSnapshot,
      Resolve,
      RouterStateSnapshot
    } from '@angular/router';
    import { Observable, Subscriber } from 'rxjs';
    import { filter, first, tap } from 'rxjs/operators';
    import { ExerciseEntityService } from './exercise-entity.service';
    
    @Injectable()
    export class ExercisesResolver implements Resolve<boolean> {
      constructor(private exercisesService: ExerciseEntityService) {}
    
      resolve(
        route: ActivatedRouteSnapshot,
        state: RouterStateSnapshot
      ): Observable<boolean> {
        const loadExercises = (subscriber: Subscriber<boolean>) => {
          this.exercisesService.loading$
            .pipe(
              filter((loading) => !loading),
              first()
            )
            .subscribe({
              next: (loading) => {
                subscriber.next(true);
                subscriber.complete();
              }
            });
          this.exercisesService.getAll();
        };
    
        const resolve$ = new Observable<boolean>((subscriber) => {
          this.exercisesService.loaded$
            .pipe(
              tap((entitiesLoaded) => {
                if (!entitiesLoaded) {
                  loadExercises(subscriber);
                } else {
                  subscriber.next(true);
                  subscriber.complete();
                }
              })
            )
            .subscribe();
        });
    
        return resolve$;
      }
    }