Retry Resolver if ngrx data unavailable

Viewed 270

I currently have a resolver for a details page that checks to see if part of the store is available before moving forward to that page

Every time there is a router action (at the moment I'm trying them all)

ROUTER_REQUEST, ROUTER_NAVIGATED, ROUTER_NAVIGATION

There's a check to see if the store is loaded, if not, it consumes the appropriate end points and populates the store with the data

However, when I enter the app using a link to a details page (and not from the home page) the store loads after the resolver and the page just hangs.

So I would like to try to either; retry the the resolver function, a couple of times, or see if I can get the data to load before the resolver.

The load data if no store effect:

  loadData$ = createEffect(() => this.actions$.pipe(
    ofType<RouterNavigationAction>(ROUTER_REQUEST, ROUTER_NAVIGATED, ROUTER_NAVIGATION),
    withLatestFrom(this.store.pipe(select(isLoaded))),
    filter(([action, loaded]) => {
      return !loaded;
    }),
    switchMap(() => {
      return forkJoin([this.gamesService.getGames(), this.moviesService.getMovies(),
                      this.musicService.getMusic(40), this.comicsService.getComics(40)])
                      .pipe(
                        map((data) => {
                        const [games, movies, music, comics] = data;
                        return AppActions.loadDataComplete({ games, movies, music, comics });
                      })
                    );
    })
  ));

This is a function, that is also router related thats executed long before the the one above.

  setPageLoading$ = createEffect(() => this.actions$.pipe(
    ofType<RouterNavigationAction>(ROUTER_REQUEST, ROUTER_NAVIGATED),
    switchMap((action: Action) => {
      // Returns true if request is ROUTER_REQUEST false if ROUTER_NAVIGATED
      const loading = action.type.indexOf('request') !== -1;
      return of(loading);
    }),
    map((loading) => {
      return AppActions.setPageLoading({ pageLoading: loading });
    }),
  ), { dispatch: true });

The resolver:

export class ComicDetailResolver implements Resolve<ComicDetail> {
  constructor(private store: Store<State>) {
  }

  resolve(): Observable<ComicDetail> {
    return this.store.pipe(select(getRouteID))
    .pipe(
      map((routeId: string) => this.store.dispatch(AppActions.getComicDetail({ routeId }))),
      switchMap(() => this.store.pipe(select(getItem))),
      filter((comic: ComicDetail) => {
        console.log(comic, !!comic);
        return !!comic;
      }), first());
  }

I tried using retry, but this doesn't work at all:

  resolve(): any {
    this.store.pipe(select(getRouteID))
      .pipe(
        map((routeId: string) => this.store.dispatch(AppActions.getComicDetail({ routeId }))),
        switchMap(() => this.store.pipe(select(getItem))),
        map((comic: ComicDetail) => {
          let val = null;
          if (!comic) {
            val = throwError(of(null));
          } else {
            val = !!comic;
          }
          console.log(val);
          return val;
        }), retry(3)).subscribe(data => {

          const loaded = !!data ? false : true;
          console.log(loaded);
          return loaded;
        });
  }

At the request of @Mikkel Christensen...

I have the app module, at the moment all of my effects are in the router effects:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { EffectsModule } from '@ngrx/effects';
import { StoreModule } from '@ngrx/store';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { reducers, metaReducers, CustomSerializer } from '@web/store/reducers';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { AppEffects } from '@web/store/effects/main.effects';
import { HttpClientModule } from '@angular/common/http';
import { StoreRouterConnectingModule } from '@ngrx/router-store';
import { RouterEffects } from '@web/store/effects/router.effects';
import { LoadingComponent } from './shared/components/ui/loading/loading/loading.component';
import { HeaderComponent } from './shared/components/header/header.component';
import { LazyLoadImageModule } from 'ng-lazyload-image';

@NgModule({
  declarations: [
    AppComponent,
    LoadingComponent,
    HeaderComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
    LazyLoadImageModule,
    StoreModule.forRoot(reducers, { metaReducers }),
    StoreDevtoolsModule.instrument({ maxAge: 25 }),
    EffectsModule.forRoot([AppEffects, RouterEffects]),
    StoreRouterConnectingModule.forRoot({serializer: CustomSerializer}),
  ],
  providers: [{ provide: Window, useValue: window }],
  bootstrap: [AppComponent]
})
export class AppModule { }

My component module is lazy loaded:

{
    path: 'comics',
    loadChildren: () => import('./features/comics/comics.module').then(m => m.ComicsModule)
  },

Hope this is what you meant, if this isn't clear enough, or you need more info, please let me know

1 Answers

The issue, (apart from my understanding of resolvers) was that;

I was checking for the data for the details page, but not checking that the main data had loaded, the data that the detail is extracted from.

I was trying to find a way to retry or make the resolver wait, but once I made it check for the main data, it waited anyway (hence my understanding of resolvers has improved)

As well, the reason the main data takes a little while to load, is because consumes 4 different endpoints, this by itself causes race conditions that needed to be looked into.

The updated code:

  resolve(): Observable<ComicDetail> {
  // Added this
    return this.store.pipe(select(isLoaded))
       .pipe(
         filter((loaded: boolean) => {
           return !!loaded;
         }),
       // Added above
         switchMap(() => this.store.pipe(select(getRouteID)).pipe(
          map((routeId: string) => this.store.dispatch(AppActions.getComicDetail({ routeId }))),
          switchMap(() => this.store.pipe(select(getItem))),
         )), filter((comic: ComicDetail) => {
                console.log(comic, !!comic);
                return !!comic;
              })
         , first());
   }
Related