@ngrx/store builds on the concepts made popular by Redux and supercharges it with the backing of RxJS. The result is a tool and philosophy that will transform the way you approach state management in your Angular 2 applications. This lesson takes an existing Angular 2 app and refactors it to utilize @ngrx/store, touching on all of the major concepts along the way!
Let's refactor a basic Angular2 app to use ngrx/store for State management. Our sample application allows the user to enter a person at Guest, remove Guest, mark Attending or Not Attending, filter the list, and delete a person.
If you're coming from an Angular background, you're probably used to maintaining a fair amount of State locally within your controller or an Angular to your component, so in this case, our People and Filter, when we need to make updates, we're pushing a new person, we're updating a reference to Guest.
The main difference in a store app is this State is going to move up into the store. Instead of performing these updates locally, we'll be dispatching actions for reducer functions to handle and create new representations of State.
Let's start by creating a people reducer and actions. A reducer is a function that takes the previous representation of State for that reducer key, and the currently dispatched action, ultimately returning a new representation of State based on the action that was dispatched.
Let's go ahead and set up our reducer to handle all appropriate actions for People. This is generally taken care of with a switch statement, with each key being an action type. We'll also set up a default so if the action doesn't apply to this reducer, we'll go ahead and return the previous State.
For now, let's just stub out the actions that will apply to this reducer, to give us a blueprint of the updates that we need to implement.
We'll start by looking at the Add Person action. We first need to give this reducer an initial state of an empty array, and we can do this by supplying an optional parameter to State. Then, every time that an Add Person action is dispatched, we'll simply concat the payload of the action to the already existing State, which in this case will just be an array of people, and our payload will be the new person being added.
Now that this action is set, let's go and head back to our main component and do a little cleanup. We can remove all State here that's being handled locally. We don't need to push people. That's going to be handled by our reducer, Add Guest, Remove Guest, Remove People.
This is all going to remove up to our reducer function, so we don't need it here. We can also get rid of the People array and the filter, which will be implemented later.
Before we go any further, let's go ahead and import Store and Provide Store from the ngrx/store library. Store is actually what we'll be injecting into our components to gain access to our application State and to dispatch actions. Provide Store sets all this up for us on application bootstrap.
Let's call Provide Store now. It's coming out of the Bootstrap function, and Provide Store just takes a object map of your reducer functions. Behind the scenes, it's going to combine these into a parent reducer which calls each one of these reducers every time an action is dispatched in your application.
You can almost think of this as tables in a database. Right now, we have people, but in a minute, we'll have filters. We could have dozens of reducers in an application, and using rxgs, we can query these, join these, combine these, to give us the appropriate State needed for each component.
The last bit of maintenance is to add a constructor, so store can be injected into this app component.
We're now ready to set up our Add Person action. Our person input component is simply sending a name of a person any time Add Person is clicked. When this happens, we want to go ahead and dispatch an action of Add Person that is sent to our reducer to concat this new person to the already existing People array.
Our type will be Add Person, and our payload will be the new person we're adding. We're going to give it an ID, which we'll add in a minute, a name that's coming from the input. We'll go ahead and set the Guest as zero to start off, and Attending to False.
Next, let's add an array to temporarily store people from our store. We'll also add an ID to tag each person. We can go ahead and delete the filter going into the person list, as we're going to be reimplementing that shortly in our reducer.
To gain access to People in our store, we can call store.select, passing a string which is just the name of our reducer, in this case, People, and Subscribe, and take the people that we get back and assign it to our local People variable.
Let's check this out to make sure it works. We'll add one person, and we'll add a second. It's important to understand what's actually going on here. Since Store is an observable, or more specifically, a behavior subject, Subscribe will say, "Give me the last value admitted and all future values as long as I'm subscribed." Our People will be automatically updated as people in our store are updated.
Behind the scenes, Select is equivalent to applying Map, getting People from State, and calling Distinct until changed, which just says, "Unless People are updated, don't admit a new value."
Let's ensure our application still works before reverting these changes. Let's take a second to follow the flow of actions one more time. The user clicks Add Person, which emits an event with the person's name. Add Person is called, which dispatches an action with the type of Add Person and the data about that person.
That hits our reducer, which concats the current people to the person being added, causing our new version of People to be emitted, and our component to be automatically updated.
Now that we understand how this works, let's go ahead and fill in the rest of the actions for our People reducer.
It's important to note that reducers are required to be pure functions, so instead of modifying the existing State reference, we'll always return new objects. That's why we use methods like Map, Filter, and Object Out Assigned.
We can now update our main component to dispatch the appropriate actions when each method is invoked. We can start by replacing the person that's being passed back with just their ID, as that's all our reducer needs.
Then for each method, we can just dispatch the appropriate action. For instance, for Add Guest, we're going to call store.dispatch with the type of Add Guest and a payload of the ID. This is similar for Remove Guest, Remove Person, and Toggle Attending.
Our Party Planner app should now have the majority of its functionality back. All that's left is to implement our filter, so let's do that now.
For the filter reducer, we're going to store the appropriate function to be applied to the People array in order to produce the correct projection. The default will return everyone, and we'll also have an option to return people with Guest, and people that are Attending or Not Attending.
Let's return to the main component, and go ahead and import our filter reducer. We can then add it to Provide Store, and when Update Filter is called, we want to dispatch the appropriate action type based on the filter that's passed.
All that's left to do is to apply the appropriate filter when State is updated, and rxgs has an operator to do just that, so what we want to do is call observable.combinelatest. What this does is take any number of observables, and any time any of them emit, it will emit the latest value from each.
In our case, we want to watch People, and the current filter. Any time People or Filter are updated in our store, it'll call our projection function here, passing the latest people and the latest filter. All we need to do is call people.filter and pass our filter function.
We really don't need to subscribe in our component with Angular2, so we're going to assign this .people to the observable being returned, and introduce the async pipe.
What this will do is subscribe, in our template, and also handle unsubscribing for us once our component is disposed. Let's come up and add the async pipe there, and make sure our application still functions correctly.
We've now successfully converted our app to an ngrx/store application.