Skip to content

Latest commit

 

History

History
117 lines (96 loc) · 3.29 KB

epics.md

File metadata and controls

117 lines (96 loc) · 3.29 KB

Side-Effect Management Using Epics

@angular-redux/store also works well with the Epic feature of redux-observable. For example, a common use case for a side-effect is making an API call; while we can use asynchronous actions for this, epics provide a much cleaner approach.

Consider the following example of a user login implementation. First, we create some trivial actions:

session.actions.ts:

import { Injectable } from '@angular/core';
import { NgRedux } from '@angular-redux/store';
import { IAppState } from '../reducers';

@Injectable()
export class SessionActions {
  static LOGIN_USER = 'LOGIN_USER';
  static LOGIN_USER_SUCCESS = 'LOGIN_USER_SUCCESS';
  static LOGIN_USER_ERROR = 'LOGIN_USER_ERROR';
  static LOGOUT_USER = 'LOGOUT_USER';

  constructor(private ngRedux: NgRedux<IAppState>) {}

  loginUser(credentials) {
    this.ngRedux.dispatch({
      type: SessionActions.LOGIN_USER,
      payload: credentials,
    });
  };

  logoutUser() {
    this.ngRedux.dispatch({ type: SessionActions.LOGOUT_USER });
  };
}

Next, we create an @Injectable SessionEpic service:

session.epics.ts:

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { ActionsObservable } from 'redux-observable';
import { SessionActions } from '../actions/session.actions';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';

const BASE_URL = '/api';

@Injectable()
export class SessionEpics {
  constructor(private http: Http) {}

  login = (action$: ActionsObservable) => {
    return action$.ofType(SessionActions.LOGIN_USER)
      .mergeMap(({payload}) => {
        return this.http.post(`${BASE_URL}/auth/login`, payload)
          .map(result => ({
            type: SessionActions.LOGIN_USER_SUCCESS,
            payload: result.json().meta
          }))
          .catch(error => Observable.of({
            type: SessionActions.LOGIN_USER_ERROR
          }));
        });
  }
}

This needs to be a service so that we can inject Angular's HTTP service. However in this case we're using the same "arrow function bind trick" as we did for the dependency-injected middleware cookbook above.

This allows us to configure our Redux store with the new epic as follows:

app.component.ts:

import { NgModule } from '@angular/core';
import { NgReduxModule, NgRedux } from '@angular-redux/store';
import { createEpicMiddleware } from 'redux-observable';
import rootReducer from './reducers';
import { SessionEpics } from './epics';

@NgModule({
  /* ... */
  imports: [ /* ... */, NgReduxModule ],
  providers: [
    SessionEpics,
    /* ... */
  ]
})
export class AppModule {
  constructor(
    private ngRedux: NgRedux<IAppState>,
    private epics: SessionEpics) {

      const middleware = [
        createEpicMiddleware(this.epics.login)
      ];
      ngRedux.configureStore(rootReducer, {}, middleware);
  }
}

Now, whenever you dispatch a "USER_LOGIN" action, the epic will trigger the HTTP request, and fire a corresponding success or failure action. This allows you to keep your action creators very simple, and to cleanly describe your side effects as a set of simple RxJS epics.