Go Back

Ngrx http$ Effects should listen for CANCEL action

Posted by Simar Paul Singh on 2018-10-05

In a Ngrx implementation, a Store Effect loads an entity from from REST APIs when it receives a FETCH_ENTITIES action and then dispatch a FETCHED_ENTITIES action with payload for reducers to merge them in Store.

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

If you notice, the same effect now listens to both ofType(‘FETCH_ENTITIES’, ‘CANCEL_FETCH_ENTITIES’) actions. It can now switch an ongoing this.getEntities() Observable for of() . Upon CANCEL_FETCH_ENTITIES We could also 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 the the previously emitted this.getEntities() http$ observable and when the http$ observable is unsubscribed will cancel any ongoing requests.

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<div class="section-inner sectionLayout--insetColumn" onhttp$.unsubscribe()` checkout 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 a component which dispatched FETCH_ENTITIES action is getting destroyed.

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

}