reactive store

Create a simple reactive store for Angular 2

Angular 2 uses observables for many features and has a peer dependency on RxJs for its robust API around observables. The community has adopted a single store approach for dealing with state in modern applications. Let’s build a store for our Angular applications that is both reactive and easy to use with RxJs.

Single store is awesome

We’ll create this store with intentions on it being the only store in our app. By doing this, we can provide a better experience and lower the difficulty of reasoning about state in our app, because all the state is in one place! First we’ll create the provider for the store itself.

export class AppStore {}

Add a subject

Right now our store literally does nothing. We want this store to have a reactive api that we can use in our application. We’re going to create a Subject using RxJs. A subject is perfect for our store because we can multicast to more than one observer. This just means we can setup many listeners. This is going to allow use to subscribe to our store in other components and providers. We need the Subject to always know what the current state is at all times, and when a new observer subscribes, the Subject should provide the observer the current state. Luckily, there is a special Subject for this, a Behavior Subject. Let’s make our store reactive!

import { BehaviorSubject } from 'rxjs/BehaviorSubject';

const store = new BehaviorSubject(); 

export class AppStore {
  store = store;
}

Above we created the store outside the class to ensure there will only ever be one instance of the actual store no matter how Angular injects and instantiates the AppStore provider. Now lets set a default value for our store.

import { BehaviorSubject } from 'rxjs/BehaviorSubject';

const state = {
  user: {},
  isLoading: false,
  items: []
};

const store = new BehaviorSubject<any>(state); 

export class AppStore {
  store = store;
}

Subscriptions

At this point, our AppStore is ready to be used in our app! Yea, that’s basically it, surprised? Although we could stop here, we should make it super easy to work with the store in our application. The first thing we can do is setup a way to subscribe to store changes anywhere in our app.

export class AppStore {
  store = store;
  changes = store.asObservable();
}

We converted the store to an observable so we can subscribe to it. The changes property is going to be that observable. Now all we have to do is subscribe to changes anywhere in our app and we’ll be able to see those changes to the store. We should also have a way to get the current state at any given time without having to wait for changes.

export class AppStore {
  store = store;
  changes = store.asObservable();

  getState() {
    return this.store.value;
  }
}

A BehaviorSubject allows us to access the current state synchronously by using the value property. We can now subscribe to changes and access the current state, lets create an easy way to make those state changes to the store.

Updates

We’ll continue to make it easy to use our store in our app by creating a simple way to issue store updates. Because our state lives in the store, we can’t just mutate the state and expect the store to update. You lose the benefit of a single store at that point and your app can get messy and confusing as it grows. First lets create a interface for our state.

interface State {
  user: Object;
  isLoading: boolean;
  items: any[];
}

Now that we have that interface, we can ensure that new store updates will always be the shape of our state. Next we’ll create a method that when given a state, will update the store with that state.

export class AppStore {
  // ...

  setState(state: State) { // use type here
  // will trigger all subscriptions to this.changes
    this.store.next(state); 
  }
}

Our store is looking pretty sweet now! So far we can subscribe to state changes on the store and create state changes. Lets add this store to a Component and use it!

Using the store

Be sure to inject the AppStore before using it. Now, inside a component…

import { Component } from '@angular/core';
import { Store } from './store';
import 'rxjs/Rx';

@Component({
  selector: 'app',
  template: `
    <div *ngIf="isLoading">...loading</div>
  `
})
class App {
  isLoading: boolean = false;
  constructor(private store: AppStore) {
    this.store
    .changes
    .pluck('isLoading')
    .subscribe((isLoading: boolean) => this.isLoader = isLoading)
  }
}

Once we have the store, all we have to do is subscribe to the changes. We then use the pluck operator as shortcut to grab the isLoading prop from the store and bind it to the local state for templates. Now every time there is a call to setState, that subscribe callback will run.

class App {
  // ...
  showLoader(isLoading: boolean) {
    const currentState = this.store.getState();
    currentState.isLoading = isLoading
    this.store.setState(currentState);
  }
}

The updateName method takes a name and updates isLoading in the current state with value, then calls setState on the store. This looks ok, and, it’ll work, but its not what we want. We’re directly mutating the state, and completely bypassing the store. Kinda defeats the purpose of a store in the first place. All state changes, even nested state, must go through the store. Lets make sure we can’t mutate the state to ensure a nice unidirectional data flow where we have one store that pushes state changes to our app, and our app issuing those changes. Just one big circle.

changes = store.asObservable().distinctUntilChanged()

By using distinctUntilChanged, the store won’t push updates triggered by setState unless the state is an entirely different object. Now we have to change how our component updates the state in the store, because its current implementation won’t trigger a change now.

class App {
  // ...
  showLoader(isLoading: boolean) {
    const currentState = this.store.getState();
    this.store.setState(Object.assign({}, currentState, { isLoading }));
  }
}

Using Object.assign or another merging strategy, we create a new object with the new value of isLoading. This state change will be pushed through. Because we’re subscribing in the constructor, the component get’s notified of this change and updates its local state. it all comes full circle! Another benefit of our immutable store, is that now we can take advantage of some performance enhancements like changing the change detection strategy for our components. I did promise a better dev flow with this single store, so without getting complicated with some sophisticated dev tools, lets create some middleware for logging!

import 'rxjs/Rx';

export class AppStore {
  store = store;
  changes = store
  .asObservable()
  .distinctUntilChanged()
  // log new state
  .do(changes => console.log('new state', changes))

  getState() {
    return this.store.value;
  }

  setState(state: State) {
    console.log('setState ', state); // log update
    this.store.next(state); 
  }
}

That was easy. Using the do operator on changes, we can log the new state. Then inside of setState, we can log to see what new state is trying to set. These two values won’t always be the same. Changes will only log immutable state changes, where setState will log any attempt to update the state. Very useful for debugging. All together now.

import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import 'rxjs/Rx';

interface State {
  user: Object;
  isLoading: boolean;
  items: any[];
}

const state: State = {
  user: {},
  isLoading: false,
  items: []
};


const store = new BehaviorSubject<State>(state); 

export class AppStore {
  store = store;
  changes = store
  .asObservable()
  .distinctUntilChanged()
  // log new state
  .do(changes => console.log('new state', changes))

  getState() {
    return this.store.value;
  }

  setState(state: State) {
    console.log('setState ', state); // log update
    this.store.next(state); 
  }
}
import { Component } from '@angular/core';
import { Store } from './store';
import 'rxjs/Rx';

@Component({
  selector: 'app',
  template: `
     <div *ngIf="isLoading">...loading</div>
  `
})
class App {
  isLoading: boolean = false;
  constructor(private store: AppStore) {
    this.store
    .changes
    .pluck('isLoading')
    .subscribe((isLoading: boolean) => this.isLoader = isLoading)
  }

  showLoader(isLoading: boolean) {
    const currentState = this.store.getState();
    this.store.setState(Object.assign({}, currentState, { isLoading }));
  }
}

Conclusion

This is a great starting point for creating a solution for state management in your Angular 2 apps. There’s so many more things we can do to make this easier, like getting rid of the boilerplate needed to perform the immutable state changes. You can take a look at our free Angular 2 fundamentals course, where we do just that.

Related Posts

Angular 2 for React Developers

Angular is here, the time has come for us to drop everything and learn something new, again. The good news is, like React and Angular 1.x, Angular 2 is here to stay for a while so it’s a good investment to become productive with this new framework…

Angular 2 for AngularJs Developers

Now that Angular 2 is in Release Candidate someone moved our cheese! Let’s put on our running shoes. First off, let me start off by saying I have been writing Angular 2 for a year and a half now (actually, since this tweet) and writing AngularJS…
twitter tweet