In this article I will attempt to use the Redux pattern in an Angular component.

I am new to Redux, but my understanding is that it's primarily a state management pattern without any specific framework dependencies. I've seen a few available libraries, but for the purposes of this article, I've decided to implement Redux without bringing in a third party implementation.

Key to the pattern is a single “store” that governs the state of the entire application, and don't forget, no state management is allowed outside the store! The store works in tandem with one or more “reducer” functions. I view the reducer as a processing step in the store that allows state to transition form one state to the next. The reducer operates on the current state and returns the new state, but it's a requirement that state objects are immutable. Meaning, the next state is not a result of mutating the current state, but rather a brand new object.

For the purposes of this article I have implemented a log viewer where exiting log entries can be viewed and new entries may be added. A simple extension of this would be to allow for grid sorting as well.

We will start with the main component and go from there.

import {Component, OnInit} from '@angular/core'; import {Observable} from 'rxjs/Observable'; import {Http,Response} from '@angular/http'; import {Store} from './store'; import {LogAction} from './log-action'; import {LogEntry} from './log-entry'; @Component({ providers:[Store], template:`<div> <h1>Error log state managed using Redux</h1> <button style="margin-bottom: 10px;" (click)="generateLogEntry()">Add new log entry</button> <input placeholder="message" type="text" [(ngModel)]="msg" /> <input placeholder="severity" type="number" [(ngModel)]="severity" /> <table> <tr *ngFor="let log of store.logEntries | async"> <td>{{log.text}}</td> <td>{{log.severity}}</td> </tr> </table> </div>` }) export class LogDemo implements OnInit{ store:Store; http:Http; msg:string; severity:number; constructor(store:Store, http:Http){ this.store = store; this.http = http; } generateLogEntry(){ let entry = new LogEntry(this.msg, this.severity); this.store.dispatchAction(new LogAction('ADD_ENTRY',entry)); this.msg = ''; this.severity = undefined; } ngOnInit(){ this.http.get('./components/log-tail/log.json') .map((res: Response) => res.json()) .subscribe((res) => this.store.dispatchAction(new LogAction('LOAD_ENTRIES', res.entries))); } }

Redux manages state in one centralized location (the store) and components can only trigger state changes by dispatching events to the store. The store will process the events and make the appropriate state transitions by invoking the reducer. In the example above we are making an http call to get existing log entries before passing the result to the store in the form of an event. The event includes a type and the payload for processing.

Next we will look at the store.

import {Subject} from 'rxjs/Subject'; import {Observable} from 'rxjs/Observable'; import {LogAction} from './log-action'; import {LogEntry} from './log-entry'; import {logReducer} from './log-reducer'; export class Store{ private dispatcher = new Subject(); private log = new Subject>(); public logEntries; private logItems = []; constructor(){ this.dispatcher.subscribe((action) => this.handleAction(action)); this.logEntries = this.log.asObservable(); } private handleAction(action) { this.logItems = logReducer(this.logItems, action); this.log.next(this.logItems); } dispatchAction(action){ this.dispatcher.next(action); } }

As you can see the store will process the events but delegate to the reducer for state transitions. My particular store implementation is based on RxJs subjects, but there are probably many ways to implement a store and still comply with the pattern.

Next up is the reducer.

export const logReducer = (state: any = [], action) => { switch (action.type) { case 'ADD_ENTRY': return [...state, action.data]; case 'LOAD_ENTRIES': return action.data; default: return state; } };

I am currently only supporting two events; add and load. ADD_ENTRY will add a new log entry to the list of existing entries. Notice that I'm not pushing the new entry onto the existing array. This is to comply with the requirement that the reducer is not allowed to mutate existing state. Instead we have to return a new array instance. LOAD_ENTRIES is trivial since it just passes back the payload as the state.

If you go back to the component you will see that I have bound the view to the log property (subject) exposed on the store. This allows the component to subscribe to changes and update the view as the store changes the state of the application.

I am still just learning about the Redux pattern, but I think there is merit to this approach. One of the goals of this is more predictable state management. However, I am not convinced that it's always necessary to go “all in” and set up a store for your application. Especially in cases where state is already immutable like in the case of a simple http call where the result is bound directly to the view. In some ways my LOAD_ENTRIES example falls under this category since it's a simple “passthrough” state transition.

I am looking forward to seeing more experiences with Redux in real life applications. One concern I have is that it might, in some cases, make the code more complex to comply with the no mutation requirements in the store. This is probably rare and limited to complicated object scenarios, but I am still interested in hearing what other people think about this though.

As always my code is available on Github and as a demo.

Sources:

The following article by Lukas Ruebbelke was a great source for me when writing this article.