angular

The state we’re in:

Angular2 has gone a long way in making UI apps more modular. The app has become a tree of potentially independent components that can be tested in isolation. Http operations are encapsulated in their own class and use Observables for asynchronous operation. The Forms have been overhauled with built in controls and validators. All in all things are much more modular and reactive. The problem of shared state remains, however.

As client-side apps become more complex, the need to handle more and more state on within the browser increases. As components need to share data between them we end up with bits and pieces of the state stored and managed all over the app, often with dependencies on the state managed by other components and our nice, componentized, highly testable, modular app is suddenly tightly coupled again. It’s also difficult to get a picture of the total state of the app.

Enter Redux

An increasingly popular pattern for dealing with this problem is Redux. The basic principles of Redux are as follows:

  • All the state in the app in encapsulated in a single JavaScript object called state, which is held in a store.
  • The store is immutable and never directly changed.
  • User interaction, events etc. fire actions which describe what has happened and encapsulate the data.
  • A function called a reducer combines the old state and the action to create the new version of the state, which is stored in the store.

Using this style makes the complicated job of state management more predictable, it separates the functional code from the presentational (separation of concerns) and the app code easier to maintain.

It’s possible to use Redux directly in Angular2, however that is more work than using a library implementation. There are a couple of implementations available and this post will use one of them, ngrx-store

Example app

The Angular2 seed project can be used to generate starter projects based on Angular2. Out of the box it presents a list of scientists along with an input box to allow you to add your own:

The initial list of names comes from doing an http call to get a list from a local file, data.json, which is in the assets folder. The service code that initiates this is the NameListService which is found in the shared/name-list folder:

In this tutorial we will update the app to use redux via ngrx-store and hook it into the service.

Pre-requisites, etc.

The examples below assume you are following the standard set-up and conventions of using Node & NPM, typescript for development and Angular 2 as per the seed project.  Details of how to get up and running are in the seed project ReadMe file.

Step 1: Add ngrx-store as a dependency

We need to add ngrx-store to the project package.json file. These are highlighted below:

 

After this we need to run NPM install to bring in the requisite node modules.

Step 2: Introducing the key concepts of Redux

Any ngrx-store (and Redux) app relies on the following key components

  • Store
  • Reducers
  • Actions

Store

The Store is the Javascript object that holds the application state. The common analogy is that it is like a database. The state maintained by the application is an array of strings which hold the names of the famous scientists. This is therefore what we will maintain in our store.

With ngrx-store, the convention is to create the store at bootstrap time. Until the recent (at time of writing) addition of modules to Angular2 this was achieved by importing the provideStore function and using it on bootstrap of the app. Now, we need to import the StoreModule from ngrx-store to the root module of the app and call provideStore on that. In the case of the seed application, the root module is defined in app.module.ts.

First we add an import to import StoreModule from @ngrx/store. Then in the imports serction of the NgModule declaration, we add the code which instantiates the Store:

Wait! What is that parameter, “names”? That is a reducer, and they are covered in the next section. A more complex example will contain many reducers but as this in an introduction we’ll keep it simple.

Now we have set up the store, we need to make it available to all the sub-components that make up the app. The next change we do is to the AppComponent, defined in app.component.ts

This time we add the import statement for the Store:

You can see where the store will be injected into the new private attribute, _store, and then be available to the rest of the components in the app.

Reducers

The usual analogy is that if the Store is the database, the reducers are like the tables in the database. They represent the “slices” of state that we want to keep track of. What makes this harder to grasp is that they may not represent a single recognisable business object. They could just be the state of a UI component, but for now we’ll keep things simple.

When dealing with Reducers, some theory needs to come in. Reducers need to be pure functions. A pure JavaScript function:

  1.       Always evaluates to the same result value given the same argument value(s). The function result value cannot depend on any hidden information or state that may change while program execution proceeds or between different executions of the program, nor can it depend on any external input from I/O devices (or HTTP calls).
  2.       Evaluation of the result does not cause any semantically observable side effect or output, such as mutation of mutable objects or output to I/O devices or HTTP calls.

In our example app, we’re going to add a reducer function, names. In a new file, src/client/app/shared/store/name-reducer.ts, we add the following:

So what’s going on here? Well we’re declaring a function that takes two parameters: the state and and action. In this example state is initialised to be an empty array. The Store will use this function to determine how the state will be changed. If the type of the action is ‘ADD_NAME_LIST’ then the function will return the payload associated with the action (in this case it will be the list of names returned by the NameListService). If the action type is ‘ADD_NAME’, then the payload (in this case a new name string) will be appended to the existing state. Lets take a look at actions.

Actions

While the Store stores the state, and Reducers output how the state changes, Actions are what communicate to the Reducers when the state needs updated. In an application that uses ngrx-store, all user interaction that would cause a state update has to be expressed in the form of actions. Actions are simple JavaScript objects that implement this interface:

The type is a string, denoting what change in state has occurred. The payload is a JavaScript object containing any data that is associated with that state change.

When a triggering event occurs, the relevant action is dispatched to the store which then triggers the relevant reducer function, resulting in a new version of the state. Which we will see in a moment.

Changing the Service

The next changes are to the NameListService. Like the app component, we add a _store attribute to the constructor so that the Store can be injected into the service. Thus this:

Becomes this:

The original code had a “get” method that loads the JSON data file, returning an Observable array of strings. I have added a new function that instead will load the JSON data file, then dispatch the ‘ADD_NAME_LIST’ action to the store so the loaded list of names (which goes into the payload of the action) go into the store instead. This is the new function:

As you can see, the payload in the response from the http call is mapped to the payload of an action object of type’ADD_NAME_LIST’.

The action is then dispatched to the Store, which will call the Reducer function outlined above, and move the payload (the list of names) into the store.)

Changing the home Component

Finally, we change the HomeComponent, which is defined in home.compent.ts. Yet again we add a Store attribute to the constructor so it can be injected. This:

becomes this:

In the original version, the HomeComponent calls the get() method on the NameListService on the NgInit method. We now replace that with a call in the constructor to the new getnameNgrx()method on the NameListService instead.

Next, we call select on the store and bind the local variable, names to the list of names in the store. Because the store is an Observable, this means that every time the list of names in the store is updated, our local variable which is subscribed to it (which is bound to the display on the UI page) will change too.

The last change is to the code that adds the name to the list. The original version pushed the new name onto the array of names. We change that to – you guessed it – dispatch an action of type ‘ADD_NAME’  to the store instead (Note: The TODO is from the original code!):

The Store fires the reducer function outlined above and the new name is added to the list in the store. Because the local variable, names, in the HomeComponent is subscribed to the names in the Store, it will be updated as well.

And that’s it. We’ve now converted the seed application to use Redux with ngrx-store instead.

Summary

Yeah but that’s really simple, I hear you say. Indeed it is and a real world application will be a lot more complex. There are a lot of other topics to get into, like Reducer Composition, Rehydration of the Store and others, however hopefully this has served and a useful introduction to Redux and Ngrx-store.

Further reading/watching:

  • Gerard Sans

    Thanks Muhammad! Would you be so kind to share your code in GitHub and add it to your post?

  • John Hopkins

    Many thanks. I also would like to see the resultant code on GitHub.
    Failed to load resource: the server responded with a status of 404 (Not Found)
    http://localhost:5555/node_modules/@ngrx/package.json