Managing side effects with @ngrx/effects

In this exercise you will create an effect for loading flights.

  1. Open your flight-booking.actions.ts file and add a flightsLoad action creator:

    Show code

    [...]
    
    export const flightsLoad = createAction(
        '[FlightBooking] Flights load',
        props<{from: string, to: string, urgent: boolean}>()
    );
    

  2. Open the file flight-booking.effects.ts and add an effect that takes a flightsLoad action, loads the requested flights and returns a flightsLoaded action.

    Show code

    import * as FlightBookingActions from './flight-booking.actions';
    
    @Injectable()
    export class FlightBookingEffects {
    
      loadFlights$ = createEffect(() => 
          this.actions$.pipe(
          ofType(FlightBookingActions.flightsLoad), 
          switchMap(a => this.flightService.find(a.from, a.to, a.urgent)),
          map(flights => FlightBookingActions.flightsLoaded({flights}))));
    
      constructor(
          private actions$: Actions,
          private flightService: FlightService) {}
    }
    

    Tip: Import the Actions type from the module @ngrx/effects:

    import {Actions} from '@ngrx/effects';

  3. Open the file flight-search.component.ts. Change the search method so that it just dispatches a flightsLoad action.

    Show code

    search(): void {
      if (!this.from || !this.to) return;
    
      // New:
      this.store.dispatch(flightsLoad({
          from: this.from, 
          to: this.to, 
          urgent: this.urgent
        }));
      
      // Old:
      /*
      this.flightService
          .find(this.from, this.to, this.urgent)
          .subscribe({
            next: flights => { 
              this.store.dispatch(new flightsLoaded({flights}));
            },
            error: error => {
              console.error('error', error);
            } 
          });
      */
    }
    
    

  4. Test the application.

  5. Use the Redux DevTools Chrome plugin to find out which actions are dispatched.

Bonus: Error Handling

  1. Open your flight-booking.actions.ts file and add an flightsLoadedError action:
export const flightsLoadedError = createAction(
  '[FlightBooking] Flights loaded error',
  props<{ error: any }>()
);
  1. In your flight-booking.effects.ts, add an error handler to the switchMap. This error handler should return the flightsLoadedError action.

    Show code

      loadFlights$ = createEffect(() => this.actions$.pipe(
        ofType(FlightBookingActions.flightsLoad),
        switchMap(a => this.flightService.find(a.from, a.to, a.urgent).pipe(
          map(flights => FlightBookingActions.flightsLoaded({flights})),
          catchError(err => of(FlightBookingActions.flightsLoadedError({ error: err })))
        )),
      ));
    

  2. Test your solution. You can simulate an error with the Browser's dev tools by activating offline module in the Network tab.

  3. Use the Redux Dev Tools to make sure, that the flightsLoadedError action is send to the store.