Ngrx http$ Effects should listen for CANCEL action
In a typical Ngrx implementation, a Store Effect loads entities from REST APIs when it receives a FETCH_ENTITIES
action and then dispatches a FETCHED_ENTITIES
action with payload for reducers to merge them in Store.
What usually we don’t handle is the case of CANCEL_FETCH_ENTITIES
which is sometimes implicit (when user navigates away before results could load) or explicit (user clicks on a button to cancel or change the previous action).
In an example below, our Store Effect listens to both ofType(‘FETCH_ENTITIES’, ‘CANCEL_FETCH_ENTITIES’)
actions and can switch an ongoing this.getEntities()
http$ Observable for something else with an of()
.
import {Injectable} from '@angular/core';
import {Actions, Effect, ofType} from '@ngrx/effects';
import {catchError, switchMap, map} from 'rxjs/operators';
import {of} from 'rxjs';
import {EntityService} from './';
@Injectable()
export class Effects {
constructor(
private actions$: Actions,
private enService: EntityService,
private store: Store<{entities: any[]}>
) {}
@Effect()
getUsers$ = this.actions$.pipe(
ofType('FETCH_ENTITIES', 'CANCEL_FETCH_ENTITIES'),
.switchMap(action => action.type === 'CANCEL_FETCH_ENTITIES' ?
of() :
this.enService.getEntities().pipe(
map(users => ({type: 'FETCHED_ENTITIES', entity})),
catchError(error => of( {type: 'ERROR', error}))
)
)
);
}
Upon CANCEL_FETCH_ENTITIES
we could return of({type:'FETCHED_ENTITIES_ACTION, []})
depending on what reducing layer expects.
However the main point is since we are using switchMap(…)
to switch to a different Observable upon CANCEL_FETCH_ENTITIES
it will unsubscribe previously emitted this.getEntities()
http$ observable if it were not complete. The unsubscribe() to an http$ Observable will also abort any ongoing http requests inside it.
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';@Injectable()
export class EntityService {
constructor(private http: HttpClient) { }
getEntities() {
return this.http.get<any[]>('/api/entities');
}}
To learn more about cancel / abort works on an http requests running inside an http$
on http$.unsubscribe()
checkout Create Custom Observables from Event Sources.
Now that our effect processes both ofType(‘FETCH_ENTITIES’, ‘CANCEL_FETCH_ENTITIES’)
actions, when should we emit CANCEL_FETCH_ENTITIES
?
It depends, sometimes with an explicit user-case where a user clicks on a <button (click)=”cancel()”>Cancel</button>
, to cancel an operation. Trickier one’s are implicit, like when we navigate away and the router is destroying the mounted component but which dispatched FETCH_ENTITIES
action which may be going on.
import {select, Store} from '@ngrx/store';
import {Component, OnInit, OnDestroy} from '@angular/core';@Component({
selector: 'some-comp',
template: `
<ng-container *ngIf="en$ | async en; else #loading ">
{{en | json}}
</ng-container> <ng-template #loading>
<button (click)="cancel()">Cancel</button>
</ng-template>`
})
export class SomeComponent implements OnInit, OnDestroy { en$: Observable<any[]>; constructor(store: Store) {
this.en$ = this.store.select('entities');
} cancel() {
this.store.dispatch({type: 'CANCEL_FETCH_ENTITIES'});
}
ngOnInit() {
this.store.dispatch({type: 'FETCH_ENTITIES'});
} ngOnDestroy() {
this.cancel();
}
}