Ngrx http$ Effects should listen for CANCEL action

Simar Paul Singh
Simar's blog
Published in
2 min readOct 26, 2018

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();
}
}

--

--