The Flux pattern, with Angular 2 and Observables
I’m going to run through how to implement the flux pattern in Angular 2 using observables.
In particular I hope the post conveys the following key concepts:
- Views only generate actions, and are not two way data bound to data models.
- Actions use a dispatcher to centralise the distribution of events.
- Data stores subscribe to the dispatcher and update themselves when relevant events are raised.
- Views subscribe to changes in the data store and update when the stores update.
- The pub / sub model for the dispatcher and data stores are implemented using RxJS observables.
Update: This was built with an alpha version of Angular 2, and some of the angular semantics have changed since the beta release. However the key principles still hold.
App Overview
We’re starting with the tour of heroes app from the Angular tutorial. We want to implement the same functionility but this time we’re going to use the flux pattern to achieve unidirectional data flow, which will make our app easier to reason about and maintain.
In a traditional Angular application we tend to store data right in $scope, it gets updated straight from the view, and the view changes when changes are made to it. It seems pretty great when you’re building simple apps how fast you can throw together views and data, but when you start to share data across multiple scopes things can get a little more complex. Understanding how user interactions translate into data changes can become convoluted and similarly understanding how data changes are reflected in views can end up the same.
Let’s start by looking at a Flux diagram to understand the pattern at a high level.
We can see from the diagram a brief description of each of the key elements of the flow, but let’s dive a little deeper using this repo as an example.
We start by implementing a updateHero
method on our main component’s view controller. This is the method the form will call once a user has selected a hero, filled in a new name and clicked submit.
In a normal Angular app this would update a list of heroes that exists on the controller, but here we’re going to create a new action, more specifically the updateHero
action on the heroesAction
object, this is injected into our component class.
Without Flux (update this.heroes, our hero data model straight on the view-model)
With Flux (create an updateHero action)
In our flux example we don’t manipulate our data model directly, instead we decouple the user interaction from our data model and simply provide the relevant data for the action.
Our action object, HeroActions
, performs any of our data access (http, web socket, local storage .etc.) and notifies a dispatcher (another injected object) when certain things need to happen.
In our example we don’t have any network connectivity so we simply immediately notify the dispatcher to publish an HeroActionType.Update
event.
We could have similarly here published a HeroActionType.Loading
event and a HeroActionType.Update
once loading was complete.
The dispatcher, implements a publish / subscribe pattern, so data stores can register themselves as interested in certain types of events.
In this app we’re using observerables to implement that pattern. Observerables are objects with a subscribe
method, which takes 3 arguments for next
, error
and complete
cases, similar to promises success
and error
callbacks.
Our dispatcher has a dispatch
method, which will notify all the subscribers of the dispatcher’s observable. Let’s take a look at how it works.
In our instance we’re going to use a Subject, which is both an observer (you can call next
on it for example) and an observable (you can subscribe
to it) so our dispatch
method can easily trigger an event.
So actions objects call the dispatcher, which in turn will notify all of the dispatchers subscribers, that is any object which has implemented the subscribe method on the dispatchers observable dispatcher.observable.subscribe(function next() { .. })
.
In our example we have the HeroesStore
which holds our data model for our heroes and subscribers to the dispatcher. It only listens to update
events by using the filter
operator avaible on Rx’s observables. When the dispatcher raises the events the store is interested in the store is responsible for mutating it’s data.
We see below how the HeroesStore
subscribes to dispatcher, when an event of the right type is raised we call this.updateHeroes(payload.notification)
, the method responsible for changing the data in our data model, this.heroes
.
We close the loop now by having our view controller subscribe to changes on the data model, first by making the data model HeroesStore
an observable, and then calling subscribe
in the view controller and assigning our view-model to the data model in HeroesStore
.
We’ll also add a call to next
so that when the state changes, all subsribers will be notified.
So when any data changes occur within HeroesStore
, this.notify()
is called which in turn will update our view-model.
Conclusion
We’ve shown how flux pattern can help us seperate our concerns and centralise data models away from the view controllers, making our app easier to reason about and more flexible to change. Additionally using an implementation of observables we’re able to react to events from a dispatcher and to changes in our data model.