Want more updates, tutorials, and awesomeness in general? Sign up!

Build a Better Angular 2 Application with Redux and ngrx

NGRX Header

The Evolution of Angular State Management

State management within Angular started out as a single celled organism if you will in the form of a single controller managing all the state for the application. If this is a single page application, one controller makes sense right? We emerged out of the ice age by starting to group our views and controllers into smaller, self-contained units either within a directive or a route. This was a vast improvement, but there was still the problem of managing complex state within our applications. It was not uncommon for us to have bits and pieces of state strewn across our application tucked inside of controllers, services, routes, directives, and occasionally, in our templates. Mutable state in itself is not inherently evil but shared mutable state is a recipe for disaster.

State Everywhere

Just as modern web frameworks like Angular permanently altered our jQuery-centric approach to app development, React has fundamentally changed the way that we approach state-management while using modern web frameworks. Redux is front and center of this shift as it introduced an elegant, yet profoundly simple way to manage application state. It is worth mentioning, Redux (big R) is a library but more importantly it is a design pattern (little r) that is completely framework agnostic and coincidentally works really well with Angular.

Required Viewing

This entire post was inspired by the amazing Egghead.io – Getting Started with Redux series by Dan Abramov. There is no better way to learn Redux than by having its creator explain it to you. It is 100% free and has changed the way that I approach programming in general.

The beauty of redux is that it can be articulated in just a few sentences. In fact, my “ah ha!” moment could be summarized in three main points.

Single State Tree

Single State Tree

The fundamental premise of redux is that the entire state of the application is represented in a single JavaScript object called a store, or application store, that can be acted upon using special functions called reducers. Equally important is that state is immutable and reducers are the only part of the application that can change them. As you can see in the graphic above, the store is the center of the appliation universe.

The consolidation and immutability of state makes understanding and predicting how an application will behave exponentially easier.

Events Flow Up

Events Up

In redux, user events are captured and emitted up to a reducer for processing. In Angular 1.x, it was a very common anti-pattern to see bloated controllers with large chunks of logic dedicated to manage local state. By moving logic that can directly manipulate state to reducers, the burden placed upon our components become negligible. In Angular 2, you will often see dumb controllers who do nothing more than capture an event and emit it via output to its parent controller.

In the graphic above, you will see two events flows. One is an event being emitted from a child component to its parent component and then onto the reducer. The second flow is an event being emitted to a service to perform an asynchronous operation and then the result of that being emitted into the reducer. All roads lead to the reducer.

State Flows Down

State Down

While events flow up, state flows down from the parent component to its children components. Angular 2 makes this really easy by declaring input on a child component for the parent component to pass state to it. This has some serious implications in terms of change detection which we will get into in a bit.

@ngrx/store

NGRX Hooray

The icing on the cake is that state moving through an Angular 2 application is exponentially easier with the introduction of observables and the async pipe. My buddy Rob Wormald created an awesome Redux implementation using RxJS called @ngrx/store. This gives us all the power of Redux combined with the power of observables which makes for a very, very powerful stack.

The Sample Application

NGRX App Demo

We are going to be building out a simple master-detail REST application that lists a collection of items and then we can select an item and edit it or create a new item. To illustrate how @ngrx/store works with asynchronous operations, we are going to use json-server to provide use a REST API for use to consume with the Angular 2 http service. If you want to see a simplified version of the application, you can check out the simple-data-flow branch to skip the HTTP calls.

Grab the code and let’s get started!

Code

Laying the Foundation

We are going to cover a lot of ground over the course of this lesson, and so we will do our best to take baby steps along the way. There is always the initial phase of a new concept where you have to lay some groundwork before you can start to expound on specific components. In this section, we are going to build out just enough Angular to give us the space to start talking about redux and ngrx in the context of a working application. Don’t get too hung up on the particulars just yet as we will revisit everything more than once to fortify the ideas we are covering.

Reducers Take One

To facilitate our master-detail interface, we need to manage an array of items as well as the currently selected item. We will use @ngrx/store to provide use with a store for us to well… store our state.

To manage our application state, we need to kick things off by creating our items and selectedItem reducers. A traditional reducer is nothing more than a function that takes a state object and an action to perform. Our ngrx reducer is slightly different in that the second parameter is an object with the type of action to perform and the payload for that action. We can also set the default value for the state to ensure that everything initializes smoothly.

// The "items" reducer performs actions on our list of items
export const items = (state: any = [], {type, payload}) => {
  switch (type) {
    default:
      return state;
  }
};

We will build our reducers out to handle specific actions but for now, we are going to set the default state of the switch statement to just return state. The code snippet above and below are almost identical except that one is the items reducer and the other is the selectedItem reducer. Seeing them side by side makes it easier to identify the underlying pattern when creating reducers.

// The "selectedItem" reducer handles the currently selected item
export const selectedItem = (state: any = null, {type, payload}) => {
  switch (type) {
    default:
      return state;
  }
};

Making an interface for the application store really helped me to understand how reducers fit into the application. In our AppStore interface, you can see that we are dealing with a single object that has an items collection and a selectedItem property which holds a single Item object.

export interface AppStore {
  items: Item[];
  selectedItem: Item;
}

If we needed to add additional functionality, the store would just expand with new key value pairs to accommodate the updated model.

Inject the Store

Now that our reducers have been defined, we need to make them available by adding them to our application store and then injecting that into our application. The first step is to import items, selectedItem and provideStore into our application. The provideStore is appropriately named in that it provides us with an application store to use for the life-cycle of the application.

We initialize our store by calling provideStore and passing in an object that contains our items and selectedItem reducers. Notice that we are passing in an object that matches our AppStore interface.
We then make the store available to our entire application by defining it as an application dependency when we call bootstrap to initialize our application.

import {bootstrap} from 'angular2/platform/browser';
import {App} from './src/app';
import {provideStore} from '@ngrx/store';
import {ItemsService, items, selectedItem} from './src/items';

bootstrap(App, [
  ItemsService, // The actions that consume our store
  provideStore({items, selectedItem}) // The store that defines our app state
])
.catch(err => console.error(err));

You may have noticed that we are also importing and injecting ItemsService; we will define it next as it will be the primary consumer of our newly minted store.

Create the Items Service

The first and simplest iteration of our ItemsService will expose the items collection by pulling it from the store. Notice that we have typed our items collection as an Observable that contains an Array of Item objects. The benefits of having our array wrapped in an observable will become clearer once we start to consume the collection in our components. We are also injecting our store into our constructor and typing it to the AppStore interface that we declared earlier.

@Injectable()
export class ItemsService {
  items: Observable<Array<Item>>;
  constructor(private store: Store<AppStore>) {
    this.items = store.select('items'); // Bind an observable of our items to "ItemsService"
  }
}

Because we are basically dealing with a key-value store, we can set this.items by calling store.select(items’). The select method returns an observable with our collection in it.

Important! The reason that I have created a service to pull the items collection from the store is because we are going to introduce asynchronous operations when we start to wire everything up to talk to our remote API. This abstraction allows us to accommodate some potentially complex async operations before handing everything off to the reducer for processing.

Consume the Items

Now that we have created the ItemsService that has an items collection available, we will consume it in our App component. Just like in the ItemsService, we will declare our items collection as items: Observable<Array<Item>>. While we are at it, we will also define our selectedItem as an observable that contains a single Item object.

export class App {
  items: Observable<Array<Item>>;
  selectedItem: Observable<Item>;
  constructor(private itemsService: ItemsService, private store: Store<AppStore>) {
    this.items = itemsService.items; // Bind to the "items" observable on the "ItemsService"
    this.selectedItem = store.select('selectedItem'); // Bind the "selectedItem" observable from the store
  }
}

To set this.items, we will assign it the items collection from the ItemsService and to set this.selectedItem, we will fetch it directly from our store by calling store.select(‘selectedItem’). If you recall, I created the ItemsService to abstract our asynchronous operations when dealing with the items collection. Managing selectedItem is completely synchronous in nature and so I could not justify creating a SelectedItemService for it. This is why I am using ItemsService when dealing with items but deal with the store directly when getting selectedItem state. You could create a service to handle this for the sake of symmetry and that would be completely justified.

Display the Items

Angular 2 is designed for the creation and composition of small specific components. Our application has two sub-components called ItemsList and ItemDetail which are responsible for listing all items and displaying the details of the selected item, respectively.

@Component({
  selector: 'my-app',
  providers: [],
  template: HTML_TEMPLATE,
  directives: [ItemList, ItemDetail],
  changeDetection: ChangeDetectionStrategy.OnPush
})

My syntax highlighter does not handle inline templates very well which is why I have separated them out into two separate code blocks. In practice, I recommend keeping your components fine-grained enough that it is easy to inline your templates. A really large HTML snippet probably means that you are doing too much.

In the my-app template, we are initializing the items-list component with a property binding to items that is bound to our local items collection. This is similar to isolated scope in Angular 1.x in that we are creating a defined input on the child component with [items] and then binding it to the value of whatever the items collection happens to be on the parent component. Because we are dealing with observables, we can bypass a lot of boilerplate by using the async pipe to pass the updated values directly to our inputs without having to extract them. By “boilerplate”, I am referring to a common Angular 1.x scenario where we could call a service in our controller and when the promise resolved, we would take the results and assign it to a property we were binding to. In Angular 2, we skip this step entirely by allowing the pipe to assign the asynchronous to our template for us.


<div>
  <items-list [items]="items | async"></items-list>
</div>
<div>
  <item-detail [item]="selectedItem | async"></item-detail>
</div>

We will follow the same pattern for the selectedItem by passing it to the item-detail component as item. We have now laid the foundation for our application, and now the stage is set for us to dig into the three main features of data flow in a redux application.

Centralized State

NGRX Centralized State

To reiterate, the single most important concept within redux is that the entire state of your application is centralized into a single JavaScript object tree. This is, in my opinion, the largest shift from how we used to write Angular applications to where we are now. We manage our application state through a reducer function which takes the original state and an action, performs a unit of logic based on the particular action and returns a new state object. We will build out our children components to display items and selectedItem and keep an eye out for the fact that they are being populated by this master, single state tree.

Our reducers are the only thing that can modify application state and so we will start out with the selectedItem reducer since it is by far the simplest of the two we have in our application. When an event is dispatched from the store with an action type of SELECT_ITEM, it will hit the first condition in the switch statement and return the payload as the new state. In plain English, we are telling our reducer to “take this new item and assign it as the currently selected item.” Also, actions are customarily strings that are all in caps and often times defined as application constants.

export const selectedItem = (state: any = null, {type, payload}) => {
  switch (type) {
    case 'SELECT_ITEM':
      return payload;
    default:
      return state;
  }
};

Because our object state tree is read-only, our response for every action must return a new state object without mutating the previous state object. Enforcing immutability in our reducers is critical when implementing redux and so will step through each action below and discuss how we can accomplish this.

export const items = (state: any = [], {type, payload}) => {
  switch (type) {
    case 'ADD_ITEMS':
      return payload;
    case 'CREATE_ITEM':
      return [...state, payload];
    case 'UPDATE_ITEM':
      return state.map(item => {
        return item.id === payload.id ? Object.assign({}, item, payload) : item;
      });
    case 'DELETE_ITEM':
      return state.filter(item => {
        return item.id !== payload.id;
      });
    default:
      return state;
  }
};

ADD_ITEMS returns whatever collection we send in as the new array.

CREATE_ITEM returns a new array by concatenating the existing items array with our new item.

UPDATE_ITEM returns a new array by mapping through the current array, finding the item we want to update and cloning a new object using Object.assign.

DELETE_ITEM returns a new array by filtering out the item that we want to delete.

By centralizing our state into a single state tree and then grouping the code that operates on our tree into reducers makes our application so much easier to reason about. Another benefit is that by having our logic segmented into pure units within our reducer, this makes testing our application very easy.

State Down Interlude

Just to give a preview of how this data flow is connected, let us take a look at our ItemsService and see how we can initiate an action on our items reducer. We will eventually replace the loadItems method with an HTTP call but for now, we will just assume that we are hard coding some sample items and assigning it to the initialItems array. To execute an action, we will call this.store.dispatch and pass in our actions object with a type of ADD_ITEMS and a payload of initialItems.

@Injectable()
export class ItemsService {
  items:Observable <Array<Item>>;
  constructor(private store:Store<AppStore>) {
    this.items = store.select('items');
  }
  loadItems() {
    let initialItems:Item[] = [
      // ITEM OBJECTS HERE
    ];
    this.store.dispatch({type: 'ADD_ITEMS', payload: initialItems});
  }
}

The interesting thing is that every time we dispatch the ADD_ITEMS event, our local items collection is automatically updated because it is being via an observable. Because we are consuming items in our App component, it is automatically updated there as well. And if we were (and we are) passing that collection to the ItemsList component, it would automatically update at the subcomponent level as well.

Redux is a great pattern for centralizing state management with immutable data structures. Add in observables and you now have an ultra-convenient way to move that state down through the application by binding directly to the stream of values provided by the observable.

State Down

NGRX State Down

Another cornerstone of redux is that state always flows down. To illustrate this point, we will start with the App component and follow our items and selectedItem data down to the child components. We are populating items from the ItemsService (because it will eventually be an asynchronous operation) and pulling in selectedItem directly from the store.

export class App {
  items: Observable<Array<Item>>;
  selectedItem: Observable<Item>;
  constructor(private itemsService: ItemsService, private store: Store<AppStore>) {
    this.items = itemsService.items;
    this.selectedItem = store.select('selectedItem');
    this.selectedItem.subscribe(v => console.log(v));
    itemsService.loadItems(); // "itemsService.loadItems" dispatches the "ADD_ITEMS" event to our store,
  }                           // which in turn updates the "items" collection
}

This is the only place in the entire application where those two properties are set. We will learn in a moment how to do some sleight of hand when we get to the ItemDetails component to localize state mutation but we never directly manipulate these values again. Conceptually, this is a huge shift from how we have approached Angular applications, and the implications are huge. We no longer need change detection if we are not directly mutating data within our component.

The App component takes items and selectedItem and hands them off via property bindings to its child components.


<div>
  <items-list [items]="items | async"></items-list>
</div>
<div>
  <item-detail [item]="selectedItem | async"></item-detail>
</div>

In our ItemsList component, we pick up the items collection by annotating our local items property with @Input().

@Component({
  selector: 'items-list',
  template: HTML_TEMPLATE
})
class ItemList {
  @Input() items: Item[];
}

And in the HTML template, we display it in the view using ngFor to loop over items and create a template for each one.


<div *ngFor="#item of items">
<div>
<h2>{{item.name}}</h2>

</div>
<div>
    {{item.description}}
  </div>
</div>

The pattern is slightly more complicated in our ItemDetail component because we need to allow a user to create a new item or edit an existing item. You are going to ask the same question I did as I was learning redux. How can you edit an existing item without mutating it? Behold the sleight of hand! We will create a local copy of our item to work with so that we are not directly mutating our selected item. This has the added benefit of allowing us to cancel the operation without any side-effects.

To accomplish this, we will modify our annotation slightly to assign our item input to a locally scoped _item property using @Input(‘item’) _item: Item;. We can then leverage the power of ES6 and create a setter for _item and perform addtional logic everytime the object is updated. In our case, we are going to create a copy of _item using Object.assign and assign it to this.selectedItem which we will use to bind our form to. We are also going to create a property and store the name of the original item so that user is aware of what item they are currently working with. This is strictly motivated by the user experience but these little things make a big difference.

@Component({
  selector: 'item-detail',
  template: HTML_TEMPLATE
})
class ItemDetail {
  @Input('item') _item: Item;
  originalName: string;
  selectedItem: Item;
  // Every time the "item" input is changed, we copy it locally (and keep the original name to display)
  set _item(value: Item){
    if (value) this.originalName = value.name;
    this.selectedItem = Object.assign({}, value);
  }
}

In our template, we are toggling the title that we show the user based on whether or not we are dealing with an existing component or new component by using ngIf to detect if selectedItem.id exists. We then have two inputs bound to selectedItem.name and selectedItem.description using ngModel and the two-way binding syntax.


<div>
<div>
<h2 *ngIf="selectedItem.id">Editing {{originalName}}</h2>
<h2 *ngIf="!selectedItem.id">Create New Item</h2>

</div>
<div>
<form novalidate>
<div>
        <label>Item Name</label>
        <input [(ngModel)]="selectedItem.name"
               placeholder="Enter a name" type="text">
      </div>
<div>
        <label>Item Description</label>
        <input [(ngModel)]="selectedItem.description"
               placeholder="Enter a description" type="text">
      </div>

</form>

</div>
</div>

And that is it! This has essentially been an exercise in taking data and passing it to the children components to display.

Events Up

NGRX Events Up

The flip side to the “state down” maxim is that events always flow up. User interactions will trigger events that ultimately make its way to a reducer to be handled. The interesting thing about this approach is that your components suddenly become very lightweight and in most cases “dumb” in the sense that they perform zero logic. We could technically dispatch a reducer event within our children components but instead we are delegating that to the parent component to minimize dependencies in our components.

Let’s check out the ItemsList component without the template to see what I mean by this. We have a single input for our items array and then we have two event outputs we are emitting when an item is selected or deleted. That is the entire sum of the ItemsList class.

@Component({
  selector: 'items-list',
  template: HTML_TEMPLATE
})
class ItemList {
  @Input() items: Item[];
  @Output() selected = new EventEmitter();
  @Output() deleted = new EventEmitter();
}

In our template, we are calling selected.emit(item) when an item is clicked and calling deleted.emit(item) when the delete button is clicked. We are also calling $event.stopPropagation() when the delete button is clicked so that it doesn’t trigger the selected event handler.


<div *ngFor="#item of items" (click)="selected.emit(item)">
<div>
<h2>{{item.name}}</h2>

</div>
<div>
    {{item.description}}
  </div>
<div>
    <button (click)="deleted.emit(item); $event.stopPropagation();">
      <i class="material-icons">close</i>
    </button>
  </div>
</div>

By defining selected and deleted as component outputs, we can capture them in our parent component in the same way that we capture native DOM events like click. We see this in the code below as (selected)=”selectItem($event)” and (deleted)=”deleteItem($event)”. The $event parameter does not contain mouse information but rather the data that was sent with the event.


<div>
  <items-list [items]="items | async"
    (selected)="selectItem($event)" 
    (deleted)="deleteItem($event)">
  </items-list>
</div>

When those events are emitted, we then capture them and handle them in our parent component. In the case of selecting an item, we are going to dispatch a new event with the action type of SELECT_ITEM and set the payload to the item selected. When an item is deleted, we will just delegate that to the ItemsService to be handled.

export class App {
  //...
  selectItem(item: Item) {
    this.store.dispatch({type: 'SELECT_ITEM', payload: item});
  }
  deleteItem(item: Item) {
    this.itemsService.deleteItem(item);
  }
}

For now, we are just going to dispatch a new event on store within our service to pass the DELETE_ITEM action to the reducer with our deleted item in tow. We will replace this with HTTP calls in a moment.

@Injectable()
export class ItemsService {
  items: Observable<Array<Item>>;
  constructor(private store: Store<AppStore>) {
    this.items = store.select('items');
  }
  //...
  deleteItem(item: Item) {
    this.store.dispatch({ type: 'DELETE_ITEM', payload: item });
  }
}

To reinforce what we have just learned, we will go through the ItemDetails component and follow the event flow up. We want to allow the user to save an item or cancel the operation and so we will define two outputs called saved and cancelled.

class ItemDetail {
  //...
  @Output() saved = new EventEmitter();
  @Output() cancelled = new EventEmitter();
}

In the bottom of our form, we have a cancel button that calls cancelled.emit(selectedItem) on click and a save button that calls (click)=”saved.emit(selectedItem).


<div>
  <!-- ... --->

<div>
      <button type="button" (click)="cancelled.emit(selectedItem)">Cancel</button>
      <button type="submit" (click)="saved.emit(selectedItem)">Save</button>
  </div>
</div>

In our main component, we then bind to the saved and cancelled outputs to call the event handlers in our class.


<div>
  <items-list [items]="items | async"
    (selected)="selectItem($event)" 
    (deleted)="deleteItem($event)">
  </items-list>
</div>
<div>
  <item-detail [item]="selectedItem | async"
    (saved)="saveItem($event)" 
    (cancelled)="resetItem($event)">
    </item-detail>
</div>

When a user clicks the cancel button, we create an empty item and emit a SELECT_ITEM action. When an item is saved, we call saveItem on ItemsService and then reset the form..

export class App {
  //...
  resetItem() {
    let emptyItem: Item = {id: null, name: '', description: ''};
    this.store.dispatch({type: 'SELECT_ITEM', payload: emptyItem});
  }
  saveItem(item: Item) {
    this.itemsService.saveItem(item);
    this.resetItem();
  }
}

Originally, I wrestled with having a form for creating an item and then a separate form for editing an item. This seemed a bit heavy and so I opted to share the same form since both exist to save an item. I then approximate upsert functionality in the itemSave method by detecting the presence of item.id and calling either createItem or updateItem. Both methods take the item we send and dispatch it with the proper event. By now, I hope that the pattern of how we deliver objects to a reducer for processing is starting to emerge.

@Injectable()
export class ItemsService {
  items: Observable<Array<Item>>;
  constructor(private store: Store<AppStore>) {
    this.items = store.select('items');
  }
  //...
  saveItem(item: Item) {
    (item.id) ? this.updateItem(item) : this.createItem(item);
  }
  createItem(item: Item) {
    this.store.dispatch({ type: 'CREATE_ITEM', payload: this.addUUID(item) });
  }
  updateItem(item: Item) {
    this.store.dispatch({ type: 'UPDATE_ITEM', payload: item });
  }
  //...
  // NOTE: Utility functions to simulate server generated IDs
  private addUUID(item: Item): Item {
    return Object.assign({}, item, {id: this.generateUUID()}); // Avoiding state mutation FTW!
  }
  private generateUUID(): string {
    return ('' + 1e7 + -1e3 + -4e3 + -8e3 + -1e11)
      .replace(/1|0/g, function() {
        return (0 | Math.random() * 16).toString(16);
      });
  };
}

We have just completed the “state down, events up” circuit but our example still lives in a vacuum. How hard would it be to modify our application to communicate with a real server? The answer is “not hard at all!”.

Calling All Servers

First, a bit of setup to prepare our application to make HTTP calls. We will import Http and Headers from angular2/http.

import {Http, Headers} from 'angular2/http';

We will then define a BASE_URL constant so we only have to type it once and we will also create a HEADER constant to tell our server how we are communicating with it. This may not be necessary depending on what backend you are using but to get json-server working, I had to add it.

const BASE_URL = 'http://localhost:3000/items/';
const HEADER = { headers: new Headers({ 'Content-Type': 'application/json' }) };

We will modify our ItemsService constructor to include Http and assign it to a private local instance called http.

constructor(private http: Http, private store: Store<AppStore>) {
  this.items = store.select('items');
}

From here, let us modify our CRUD methods to handle remote server calls, starting with loadItems. We are calling this.http.get(BASE_URL) to get our remote items and because Http returns an observable, we can pipe our results through additional operators. We will call map to parse our results and then call map again to create the object we want to dispatch to our reducer. The combinations of map method calls is an observable sequence in that every result gets passed through those sequence of operations. To “tie off” a sequence, we will subscribe to it and then hand off control to our reducer by dispatching our transformed results.

loadItems() {
  // Retrieves the items collection, parses the JSON, creates an event with the JSON as a payload,
  // and dispatches that event
  this.http.get(BASE_URL)
    .map(res => res.json())
    .map(payload => ({ type: 'ADD_ITEMS', payload }))
    .subscribe(action => this.store.dispatch(action));
}

We will follow a similar pattern when we update createItem. The only difference is that we are calling http.post with a formatted item as the payload and our HEADER constant. Once we have our results, we map everything to an object that we can dispatch in our subscribe method.

createItem(item: Item) {
  this.http.post(BASE_URL, JSON.stringify(item), HEADER)
    .map(res => res.json())
    .map(payload => ({ type: 'CREATE_ITEM', payload }))
    .subscribe(action => this.store.dispatch(action));
}

Updating and deleting are a bit simpler in that we are not dependent on an object being returned from the server. We only care that the operation was successful. Because of that, we will use the http.put and http.delete and skip mapping the response entirely. We can dispatch an action to our reducer from the subscribe block as you can see below.

updateItem(item: Item) {
  this.http.put(`${BASE_URL}${item.id}`, JSON.stringify(item), HEADER)
    .subscribe(action => this.store.dispatch({ type: 'UPDATE_ITEM', payload: item }));
}

deleteItem(item: Item) {
  this.http.delete(`${BASE_URL}${item.id}`)
    .subscribe(action => this.store.dispatch({ type: 'DELETE_ITEM', payload: item }));
}

BONUS: Testing

One of the most important aspects of redux is that it is very easy to test reducers because they are pure functions with a clear contract. In regards to our application, the surface area that contains testable logic has been vastly reduced. I wasn’t trying to be funny when I wrote that but in hindsight, it kind of is!

Setting Up

I am not going to get into the entire testing harness but let’s a take a quick tour of our test spec. The first thing we need to do is to import items and selectedItems as well as it, describe and expect from angular2/testing. Wait a second! Aren’t those Jasmine methods!? Yes they are and Angular 2 now uses Jasmine by default.

import {items, selectedItem} from './items';

import {
  it,
  describe,
  expect
} from 'angular2/testing';

For reference, the skeleton of our spec looks like this.

describe('Items', () => {
  describe('selectedItem store', () => {
    it('returns null by default', () => {});
    it('SELECT_ITEM returns the provided payload', () => {});
  });
  describe('items store', () => {
    let initialState = [
      { id: 0, name: 'First Item' },
      { id: 1, name: 'Second Item' }
    ];
    it('returns an empty array by default', () => {});
    it('ADD_ITEMS', () => {});
    it('CREATE_ITEM', () => {});
    it('UPDATE_ITEM', () => {});
    it('DELETE_ITEM', () => {});
  });
});

The tests were really easy to write because we start with an initial state and when we send an action to our reducer, we know exactly what we should get back. We know that if we dispatch an action of ADD_ITEMS, we will get back whatever we put in the payload, which we see in the assertion below.

it('ADD_ITEMS', () => {
  let payload = initialState,
      stateItems = items([], {type: 'ADD_ITEMS', payload: payload}); // Don't forget to include an initial state

expect(stateItems).toEqual(payload);
});

If we call the items reducer with an action type of CREATE_ITEM, we would expect that the result would be the initial array plus the new item.

it('CREATE_ITEM', () => {
  let payload = {id: 2, name: 'added item'},
      result = [...initialState, payload],
      stateItems = items(initialState, {type: 'CREATE_ITEM', payload: payload});

expect(stateItems).toEqual(result);
});

We can easily articulate the expected result for the remaining two reducer’s methods and then write assertions for them as I have below.

it('UPDATE_ITEM', () => {
  let payload = { id: 1, name: 'Updated Item' },
      result = [ initialState[0], { id: 1, name: 'Updated Item' } ],
      stateItems = items(initialState, {type: 'UPDATE_ITEM', payload: payload});

expect(stateItems).toEqual(result);
});

it('DELETE_ITEM', () => {
  let payload = { id: 0 },
      result = [ initialState[1] ],
      stateItems = items(initialState, {type: 'DELETE_ITEM', payload: payload});

expect(stateItems).toEqual(result);
});

Review

We have covered a lot of ground in this lesson and #higFive if you have made it this far! Let’s do a quick recap of what we did while it is fresh in our minds.

  • The primary characteristics of redux is that state is centralized, events flow up and state flows down.
  • The @ngrx/store implementation uses observables allows us to populate our templates using the async pipe.
  • We created our reducers which are simple functions that take an action and state object and returns a new state object.
  • Our reducer functions must be pure and so we saw how we could create them without mutating our collections
  • A store is basically a key value value map with some mechanisms to handle events and emit state.
  • We broadcast our events using store.emit.
  • We subscribe to data using store.select.
  • Create a local copy when working with a form to avoid higher level mutations.
  • With asynchronous calls, we pass our results through an observable sequence and then emit the event to the reducer on completion.
  • Reducers are really easy to test because the methods are pure and the contract is crystal clear.

Learning redux via @ngrx/store has been the closest thing to that “new programmer” feeling that I have felt in awhile. It is been so much fun! Take the example and play around with it and think about how you can use this approach in your day to day projects. If you create something awesome then by all means, share it in the comments for everyone to check out!

Huge Shout Outs

First and foremost, a big #highFive to Dan Abramov for creating such an awesome library. I owe Rob Wormald a huge debt of gratitude for creating @ngrx/store and being a super awesome and patient friend while I wrapped my mind around this brave new world. I never ever take it for granted the amazing friends that I have who take time to help me write the best possible blog posts that I can. Thank you, Joe Eames for your very thorough and thoughtful input as well as Victor Savkin and Tero Parviainen for their great feedback and encouragement. Also, a very warm Angular [(hug)] to Gleb Bahmutov for his special Russian brand of tough love.

Resources

Egghead.io – Getting Started with Redux

Github – RxJS

Github – ngrx Store

Angular2 – Http

Angular2 – Headers

Angular2 – @Input

Angular2 – EventEmitter

Angular2 – Async Pipe

Angular2 – Testing

Code

Leave a Comment