Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

@ngrx/store pre-load guard catch error

I'm working on the Angular guard with @ngrx/store to check if state has been already loaded and I faced an issue that canActivate method never returns if I use filter.

Here is an example effect for GetCompanies action:

return this.companiesService.getCompanies()
  .pipe(
    map(companies => new companiesActions.GetCompaniesSuccess(companies)),
    catchError(error => Observable.of(new companiesActions.GetCompaniesFail(error)))
  )

If there is an error I'm dispatching a GetCompaniesFail action and redirect a user to /login page. That's fine, the interesting part is with the guard.

@Injectable()
export class CompaniesGuard implements CanActivate {
  constructor(
    private store: Store<AppState>,
    private router: Router
  ) {}

  canActivate(): Observable<boolean> {
    return this.checkStore()
      .pipe(
        switchMap(() => Observable.of(true)),
        catchError(() => Observable.of(false))
      );
  }

  checkStore(): Observable<boolean> {
    return this.store.select(getCompaniesLoaded)
      .pipe(
        tap(loaded => {
          if (!loaded) {
            this.store.dispatch(new companiesActions.GetCompanies());
          }
        }),
        filter(loaded => loaded),
        take(1)
      );
  }
}

If there is an error and loaded is not false, canActivate wouldn't return anything because checkStore does nothing.

If I change filter to map it works but it takes only the first value which is false even if the data will be loaded successfully.

What am I missing? How can I handle HTTP errors there? Is there a way to either wait for certain state or throw an error inside checkStore?

Thank in advance!

like image 261
Frelseren Avatar asked Nov 07 '22 10:11

Frelseren


1 Answers

This seems easier to handle if you separate the responsibilities a little.

You should run checkStore regardless, and not depend on it's result directly. Instead, let the store inform you of success or failure and return that result.

Note that ngrx and redux encourages one-way-data-flow to simplify app logic.

The store property getCompaniesLoadedStatus in initialized to initial, and set to success or failed within companiesActions.GetCompaniesSuccess or companiesActions.GetCompaniesFail.

Retries are handled in the service (closer to the fetch).

canActivate(): Observable<boolean> {
  this.checkStore();
  return this.store.select(getCompaniesLoadedStatus).pipe(
    filter(status => status !== 'initial'),
    map(status => status === 'success' ? true : false )
  );
}

checkStore(): void {
  this.store.select(getCompaniesLoadedStatus).pipe(
    take(1)  // so that unsubscribe is not needed
  ).subscribe(status =>
    if (status === 'initial') { 
      this.store.dispatch(new companiesActions.GetCompanies());
    }
  );
}
like image 94
Richard Matsen Avatar answered Nov 15 '22 08:11

Richard Matsen