@ngrx/store in 10 minutes

Brian Troncone
InstructorBrian Troncone
Share this video with your friends

Social Share Links

Send Tweet
Published 8 years ago
Updated 5 years ago

@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!

[00:02] 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.

[00:23] 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.

[00:43] 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.

[00:56] 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.

[01:15] We've talked a little bit about actions in the context of reducers, but what are they? An action is just a plain JavaScript object with a type or a string describing the action, and a payload or data associated with that action.

[01:28] 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.

[01:46] 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.

[02:02] 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.

[02:27] 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.

[02:43] 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.

[02:53] 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.

[03:12] 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.

[03:28] 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.

[03:46] The last bit of maintenance is to add a constructor, so store can be injected into this app component.

[03:53] 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.

[04:19] 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.

[04:45] 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.

[05:03] 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.

[05:25] 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.

[05:56] 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."

[06:16] 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.

[06:44] 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.

[06:59] Now that we understand how this works, let's go ahead and fill in the rest of the actions for our People reducer.

[07:04] 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.

[07:31] 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.

[07:48] 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.

[08:07] 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.

[08:26] 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.

[09:00] 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.

[09:31] 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.

[09:50] 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.

[10:10] 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.

[10:22] 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.

[10:44] We've now successfully converted our app to an ngrx/store application.

Michael Peake
Michael Peake
~ 8 years ago

It would be nice if the original code (i.e. the starting point for the video) was available so that it was possible to follow along with the changes. The code on the Git repo appears just to be the finished version, even exploring the commits.

Brian Troncone
Brian Tronconeinstructor
~ 8 years ago

Good suggestion, I'll push a branch up today with original code before refactor.

Update: You can now access code before refactor in 'original' branch. :)

Samir Medjdoub
Samir Medjdoub
~ 8 years ago

Great practical example on many things I studied recently on RxJS, reducers, pure functions etc plus ngrx. The only issue when trying to install the app from your repo is this error:

Cannot resolve module 'rxjs/BehaviorSubject'
C:\sandbox\angular2\ngrx-store-in-ten\node_modules@ngrx\store\ng2.d.ts (1,39): error TS2307: Cannot find module '@angular/core'

I guess it's due to ngrx depending on angular 2 RC, not on Angular 2 beta anymore ?

Brian Troncone
Brian Tronconeinstructor
~ 8 years ago

I apologize, it looks like I left a ^ in the ngrx/store dependency. The current versions of store depend on Angular RC, causing the issue. I have updated the package.json file in the repo to fix this problem. Thanks for watching!

Samir Medjdoub
Samir Medjdoub
~ 8 years ago

Great thanks, that was the issue. I really have to remember these "^" and other "~" on npm packages. Looking forward to seeing your next videos :)

Brian Troncone
Brian Tronconeinstructor
~ 8 years ago

Thanks, more coming soon! :)

Jeff
Jeff
~ 8 years ago

I've have tried to get this to work over and over and always get the following error at npm start:

ERROR in ./src/app/main.ts (48,19): error TS2339: Property 'filter' does not exist on type '{}'.

ERROR in Path must be a string. Received undefined webpack: bundle is now VALID.

Can't debug/play with this at all due to this issue

Brian Troncone
Brian Tronconeinstructor
~ 8 years ago

Looking into this now, I apologize for the inconvenience.

Edit: This should now be resolved. I also went ahead and upgraded to @ngrx/store 2.0 w/ Angular RC.1. Thanks for the heads up!

asvifi85
asvifi85
~ 8 years ago

You are storing in memory on client side , How do ajax or rest call to save the newly added items in server database

Brian Troncone
Brian Tronconeinstructor
~ 8 years ago

For a more robust example, including making REST calls, caching data, etc. I suggest checking out the official ngrx example app. This is a great guideline to build off of for your ngrx based applications. Good luck! https://github.com/ngrx/example-app

DANIEL Pride
DANIEL Pride
~ 7 years ago

I am struggling to understand why when you have tailored sql database calls why you would use this ? What is the advantage, reduced server calls ?

Brian Troncone
Brian Tronconeinstructor
~ 7 years ago

Hello, thanks for watching! Check out the redux docs for an excellent overview on why this pattern is so useful in complex JavaScript apps. http://redux.js.org/docs/introduction/Motivation.html

Erkan Buelbuel
Erkan Buelbuel
~ 7 years ago

great, thank you!

Ian Smith
Ian Smith
~ 7 years ago

I just downloaded the code to run and followed the instructions. npm install ran fine. However, when I issue the npm start command I get the following errors: "webpack: Compiled successfully. [default] Checking started in a separate process... [default] C:/Dev/Egghead/ngrx-store-in-ten/src/app/main.ts:2:9 Module '"C:/Dev/Egghead/ngrx-store-in-ten/node_modules/@angular/platform-browser-dynamic/index"' has no exported member 'bootstrap'. [default] C:/Dev/Egghead/ngrx-store-in-ten/src/app/main.ts:33:2 Argument of type '{ selector: string; template: string; directives: (typeof PersonList | typeof PersonInput | typeo...' is not assignable to parameter of type 'Component'. Object literal may only specify known properties, and 'directives' does not exist in type 'Component'.

Any clues as to why I'm seeing these errors?

Bhaumik
Bhaumik
~ 7 years ago

I can see two dispatch one for people and another one for the filter as below:

  1. this._store.dispatch({type: "ADD_GUESTS"})

  2. this._store.dispatch({type: filter})

we have two different reducers in our code with different switch case so how above code will decide which reducer to choose ?

Bhaumik
Bhaumik
~ 7 years ago

As per above code, there are 2 reducers. one for handling list and other one is for sorting.

when you use this.store.dispatch({type: 'ADD_GUEST'}) and this.store.dispatch({type: filter}). How component function identifies which dispatch is for which reducer ???

Jason Kendall
Jason Kendall
~ 7 years ago

Having problems with this code build. I've installed all the exact dependencies as specified in the package.

<pre> [default] Checking started in a separate process... [default] /Applications/MAMP/htdocs/sandbox2017/egghead/ngrx-store-in-ten/src/app/main.ts:2:10 Module '"/Applications/MAMP/htdocs/sandbox2017/egghead/ngrx-store-in-ten/node_modules/@angular/platform-browser-dynamic/index"' has no exported member 'bootstrap'. [default] /Applications/MAMP/htdocs/sandbox2017/egghead/ngrx-store-in-ten/src/app/main.ts:33:2 Argument of type '{ selector: string; template: string; directives: (typeof PersonList | typeof PersonInput | typeo...' is not assignable to parameter of type 'Component'. Object literal may only specify known properties, and 'directives' does not exist in type 'Component'. [default] Checking started in a separate process... [default] /Applications/MAMP/htdocs/sandbox2017/egghead/ngrx-store-in-ten/src/app/main.ts:2:10 Module '"/Applications/MAMP/htdocs/sandbox2017/egghead/ngrx-store-in-ten/node_modules/@angular/platform-browser-dynamic/index"' has no exported member 'bootstrap'. [default] /Applications/MAMP/htdocs/sandbox2017/egghead/ngrx-store-in-ten/src/app/main.ts:33:2 Argument of type '{ selector: string; template: string; directives: (typeof PersonList | typeof PersonInput | typeo...' is not assignable to parameter of type 'Component'. Object literal may only specify known properties, and 'directives' does not exist in type 'Component'. </pre>
danilo
danilo
~ 7 years ago

The original branch doesn`t seem to work I get errors when running NPM install :

ngrx-store-in-ten@0.0.2 typings-install D:\Projects\ngrx-store-in-ten typings install

'typings' is not recognized as an internal or external command, operable program or batch file.

npm ERR! Windows_NT 10.0.15063 npm ERR! argv "D:\Program\nodejs\node.exe" "D:\Program\nodejs\node_modules\npm\bin\npm-cli.js" "run" "typings-install"

Recep Can
Recep Can
~ 6 years ago

Concepts may be same, but implementation is diffrent now. Please update this video.

Oktay Bilgin
Oktay Bilgin
~ 6 years ago

Very good explanation... you got talent...

~ 3 years ago

Same problem for me [default] Checking started in a separate process... [default] /Users/~/ngrx-store-in-ten/src/app/main.ts:2:10 Module '"/Users/~/ngrx-store-in-ten/node_modules/@angular/platform-browser-dynamic/index"' has no exported member 'bootstrap'. [default] /Users/~/ngrx-store-in-ten/src/app/main.ts:27:2 Argument of type '{ selector: string; template: string; directives: (typeof PersonList | typeof PersonInput | typeo...' is not assignable to parameter of type 'Component'. Object literal may only specify known properties, and 'directives' does not exist in type 'Component'.

Markdown supported.
Become a member to join the discussionEnroll Today