1

I've got this reducer

on(CmsActions.loadCmsTopNewsSelected, (state, { slug }) => {
    let selected;
    if (state.data) {
      selected = state.data.items.find(item => item.data.slug.iv === slug);
    }
    return {
      ...state,
      selected
    };
  })

and this guard

canActivate(route: ActivatedRouteSnapshot): Observable<boolean | UrlTree> {
    return this.checkStore().pipe(
      switchMap(() => {
        const slug = route.params.slug;
        this.facade.selected(slug);
        return this.facade.selected$.pipe(
          map(selected => {
            console.log('selected', selected);
            if (selected) {
              return true;
            }
            return this.router.parseUrl('/not-found');
          })
        );
      }),
      catchError(() => of(false))
    );
  }

  checkStore(): Observable<boolean> {
    return this.facade.loaded$.pipe(
      tap(loaded => {
        if (!loaded) {
          this.facade.load(this.ITEMS_TO_LOAD);
        }
      }),
      filter(loaded => {
        console.log('loaded', loaded);
        return loaded;
      }),
      take(1)
    );
  }

it works nicely if I go to the route by router link,

but if I go directly the selected is undefined even if

the data has been loaded.

What's wrong?

UPDATE

I post more code for a better understanding By the way, I'm waiting to have the data loaded when the children route is running by the browser.

Routes Parent

{
   path: 'news',
   canLoad: [CmsNewsGuard],
   loadChildren: () =>
          import('./news/news.module').then(m => m.PublicNewsModule)
},

Children

{
    path: ':slug',
    canActivate: [CmsNewsGuardSelected],
    component: PublicNewsPageDetailsComponent
  },
  {
    path: '',
    component: PublicNewsListComponent,
    pathMatch: 'full'
  }

Facade

export class CmsNewsFacade {
  get data$(): Observable<CmsArray<CmsNews> | null> {
    return this.store.pipe(select(selectCmsNewsData));
  }

  get error$(): Observable<Required<ErrorDto> | null> {
    return this.store.pipe(select(selectCmsNewsError));
  }

  get loaded$(): Observable<boolean> {
    return this.store.pipe(select(selectCmsNewsLoaded));
  }

  get selected$(): Observable<CmsNews | undefined> {
    return this.store.pipe(select(selectCmsNewsSelected));
  }

  constructor(private store: Store<CmsState>) {}

  load(top: number): void {
    this.store.dispatch(CmsActions.loadCmsTopNews({ top }));
  }

  selected(slug: string): void {
    this.store.dispatch(CmsActions.loadCmsTopNewsSelected({ slug }));
  }
}

Selector

export const selectCmsNewsSelected = createSelector(
  selectMarketAccountFeature,
  (state: CmsState) => {
    return state.news.selected;
  }
);

UPDATE2

It workish with

return this.facade.selected$.pipe(
          filter(selected => {
            return !!selected;
          }),
          map(selected => {
            if (selected) {
              return true;
            }
            return this.router.parseUrl('/not-found');
          })
        );

but doing so I lost the goal show a not found page if the slug didn't exist :(

WORKED IT OUT!

I worked it out adding a check in the reducer

const current = state.data.items.find(item => item.data.slug.iv === slug);
if (!current) {
   selected = null;
}

in the guard simply

filter(selected => {
   return selected !== undefined;
}),
Whisher
  • 31,320
  • 32
  • 120
  • 201

1 Answers1

1

I think your selector tries to select an undefined piece of state, because when your page loaded then your Guard checks the condition faster than your data appearances in the store after loading. This is because the lazy loading. Your Guard is provided in eager loaded module. Your store piece is declared in a lazy loaded module, this is the problem why your Guard doesn't accept your navigation with first load.

How you solve this?

Check when your state is undefined, and in this case return true, and when your state is makes updates, your guard will redirects your user if they don't have access.

Anarno
  • 1,470
  • 9
  • 18