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!
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());
}
);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With