Angular NgRx: Getting Started
-
Introduction
Introduction
As we build large or complex applications, it becomes important to clearly structure our component interactions and actively manage our application state. That's the goal of NgRx, a powerful library for organizing and managing state and interactions with that state in our Angular applications. Welcome to Angular NgRx: Getting Started from Pluralsight. My name is Deborah Kurata, and my name is Duncan Hunter, and this course covers what you need to understand and get started with NgRx. Every Angular application needs to manage state. As an application gets larger, it must manage more and more state. Keeping track of that state can be challenging, especially as more components must react to state changes. We often end up with fragile interactions and hard to find bugs, making it difficult to add more features and more state. Using a consistent pattern, we give our applications a firm foundation and stabilize our often complex state management. What is state in this context? Information about the view, such as whether to display specific fields or UI elements. It can be user information, such as the user's name and roles, that is used to tailor the application for the user. State can be entity data, such as product information, that is displayed and manipulated by the application, but originally retrieved from and stored on a backend server somewhere. It can be user selection and input, such as the currently selected product or entered filter string, or any other information or data that the application tracks. The purpose of NgRx is to provide a formal pattern for organizing our application's state into one single, local state container, managing that state by requiring a one-way data flow, and communicating state changes to our components so they can react accordingly. In this first module, we introduce NgRx and examine the types of problems it can solve. Then we consider several suggestions for getting the most from this course. We explore the sample application that we'll work with throughout this course, and we'll look at the topics we'll cover in the remainder of this course. Let's get started.
-
What Is NgRx?
NgRx is a set of reactive libraries for Angular that you can install from GitHub and use in any new or existing application. We'll discuss these libraries further as we progress through this course, but this definition is not very specific. So what is NgRx? NgRx is the popular state management pattern called Redux tailored to Angular using standard Angular concepts and techniques. As an Angular developer, you know Angular. Let's focus then on walking through the Redux pattern to better understand what NgRx is. The Redux pattern helps us manage our application state by providing a one-way data flow throughout the application. Let's start with the view. In this example we have a checkbox at the bottom of our view that the user can select to include the product code and the display. We could define a local property and the component to track this flag, but then if the user navigates away, and later navigates back, we lose their selection. Let's see how the Redux pattern can help us. When the user clicks on the checkbox, the view users event binding to notify the component of the user event. The component then creates an action representing that event. The action includes a payload, which in this case is a Boolean value identifying whether the user checked or unchecked the box. The component dispatches this action to a dispatcher function called a reducer. The reducer uses the action and the current application state from the NgRx store to define new state and updates the store with that state. The store is a single, in-memory, client-side state container. It only stores our application state during execution of the application. Using this pattern, our state is immutable, which basically means it is never modified. Instead, the reducer creates new state from the existing state and the defined action, making state changes much more explicit. Any component subscribes to this store using a selector to get notified of specific state changes. Think of a selector like a stored procedure for our application store. It knows how to locate and return data from the store. When new state is replaced in the store, the component is notified of that new state and bound values in the view are updated. In this case, the view then displays the product codes. If the user navigates away from the product list view and later navigates back, the component re-subscribes, immediately obtaining the state from the store and the view is updated with the retained user's selection. With NgRx, for any bit of state that we retain beyond the life of a single component, or provide to other components, we dispatch an action to add that data to our store so we can better manage it. Our components subscribe through a selector to watch for any changes to that stored data. This then defines our one-way data flow. Our example here is a simple one. As we'll see throughout this course, we can use this pattern for more complex interactions, such as multiple component communication, loading data from a backend server, and create update and delete operations. We'll cover more about this pattern in the next module. For now, let's look at the types of problems we can solve with NgRx.
-
Why Use NgRx?
Why use NgRx? Well without NgRx, as we build our application, we may use a service to define some state we retain for our product pages. As we add more features to our application, we'll create more services with a similar purpose. Over time we may find ourselves with dozens of these little services. With NgRx we don't need a service for each bit of state, rather we have a single store for our application state, making it easy to find, track, and retrieve state values. We may have a data access service that looks like this. Here is a basic get method that loads our products from a backend server using http. Without NgRx, our component, such as our ProductListComponent, calls that service. Notice that this code is in the ngOnInit method, that means when the user navigates to this page, the component calls the service to issue an http.get request. This retrieves the products from the server. If the user navigates away, even if only for a moment, and navigates back, the component calls the service to get the data again. If that data changes frequently, that may be desired, but some data just doesn't change that much, so there is no need to get it again and again and again as the user navigates through the application. We could manually implement a data cache in our service, but with NgRx the store provides the client-side cache, so we don't need to re-get the data from the server every time the component is initialized. Without NgRx, we have to explicitly handle complex component interaction. In this example, each time the user selects a new product from the list, the application notifies the detail page so it can display the appropriate detail. It notifies the shell component so it can display the correct number of months. And it notifies the list itself so it can ensure that the selected product is highlighted in the list. It can be a bit of work to ensure each component is notified of the change without closely coupling the components. With NgRx, each time the user selects a product, the component dispatches an action with the current product as the payload. The reducer uses that action and the current application state from the store to define new state and updates the store with this new state. The store then retains the current product. Any component can subscribe to the current product selector to receive change notifications, and their associated views display the information appropriate for the current product. It is all cleanly decoupled. The components don't put data directly into the store, rather they dispatch an action for the reducer to update the store. The components don't read data directly from the store, rather they subscribe to the store through a selector to receive state change notifications. And since all of the components are getting the state from a single source of truth, the state is consistently rendered. If something isn't working correctly, NgRx has tools that allow us to view our list of actions and our state, making it much easier to see what's going on in our application. We can even rollback and do time travel debugging. More on that later. Putting it all together, use NgRx when there is lots of state and little services. The store provides a convenient place to put UI state to retain it between router views. Use NgRx when there are excessive HTTP requests. The store provides a client-side cache our application can use as needed. Use NgRx when there are complex component interactions. The reducer updates the store and the store notifies all subscribers, keeping the components decoupled, yet communicating. Using NRx in this scenario can prevent race conditions and issues caused when multiple components are updating data. And when we use NgRx and something isn't working, it has great tooling to help us see our actions and state. Using NgRx gives us a standard pattern that can help large or complex projects and larger teams with these issues. But NgRx is not for every project. Don't use NgRx if you and your team are new to Angular, wrap up with Angular and RxJS Observables first. You'll be much more successful with NgRx once you've mastered Angular. You may not need NgRx if the application is simple, the extra code required for NgRx may not be worth the effort for a simple application. And you may not need NgRx if you and your team already have a good state management pattern in place for your applications. Now that you have the criteria to determine if NgRx is a good fit for your team, let's look at some tips for getting the most from this course.
-
Getting the Most from This Course
This is listed as a beginning course because it is for beginners of the NgRx library. However, it is aimed at intermediate level Angular developers. To get the most from this course, it is important that you minimally know the basics of Angular. This means understanding Angular modules, components, services, and especially RxJS Observables. If you don't have the requisite knowledge, consider taking one of the introductory Angular courses, such as Angular: Getting Started or Angular: First Look, and to learn more about state management in general before learning NgRx, consider watching the Angular Component Communication course. You do not need any prior NgRx experience, we'll cover what you need in this course. Another way to get the most from this course is to join the discussion. Thoughts, comments, or questions about this course, feel free to use the Discussion tab. You can find the link to the discussion on the Pluralsight page for this course, or follow us on Twitter, it would be great to hear about your experiences with NgRx. There is also a blog post specifically for this course at the URL shown here. This post identifies common issues with the materials, along with their solutions. If you have problems with the code for this course, check here first, there may already be a solution provided. When using a pattern, there are often lots of steps and places where things can go wrong. We'll present checklists at the end of a module and use them as a brief review of what was covered in that module. Feel free to jump ahead to the checklists if you have any problems while coding along with the demos in the module, and consider referencing these checklists as you start using NgRx in your own Angular applications. We've set up a public GitHub repository specifically for the sample application used in this course. The repo is called Angular-NgRx-GettingStarted and you can find it at this URL. Coding along through the demos is another great way to get the most from this course, though not required, it is often helpful to try out the presented techniques. And we've defined optional homework at the end of several modules for additional practice if desired. We'll walk through the steps for setting up this sample application in a later module. For now, let's take a quick look at this sample application.
-
Sample Application
To demonstration NgRx, our sample application must be complex enough to have state management requirements, but simple enough to implement those requirements within a reasonable amount of time. We start with an existing application that does not use NgRx. As we work through this course, we replace the state management and component interactions with NgRx techniques, allowing us to stay focused on NgRx, not building components and templates. If you are working with an existing application, using this approach illustrates how to retrofit NgRx. If you are building a new application, this approach helps you make the mental switch between how you would have implemented the state and interaction to implementing them the NgRx way. Now let's see the starter files for the sample application in action. Welcome to Acme Product Management. As its name implies, this application manages our company's current list of products. You may remember this application from such courses as Angular: Getting Started and Angular Routing. This is the demo 0 version of the sample application that does not contain any NgRx. Here we have an option for the user to log in. Note that the login does not check your credentials, so you can enter any values here to log in. After logging in, we see our list of products. Here is a checkbox to turn on and off the display of the product code in the list. Clicking on a product name displays the product edit page. Here we can edit or delete a product. The Add button clears the product edit page and provides defaults in preparation for entry of a new product. That's the basic operation of the sample application that we'll use throughout this course. Our sample application has a root application module called App Module. This module declares the app, shell, menu, and welcome components. Following best practices, the features of the application are defined in feature modules, product module for the product features, user module for the user features, including the login page, and a shared module for functionality that is shared among the feature modules. Now let's finish up this introductory module with a look at the outline for the remainder of this course.
-
Course Outline
In this course we begin with a closer look at the Redux pattern and describe its pieces. Then we walk through how to add NgRx to an application and use it to manage a simple bit of state. We examine the development and debugging tools provided with NgRx. With the basics of NgRx covered, we'll power up our code with strong typing. We'll cover how to strongly type our state and access it with selectors. Then we strongly type our actions with action creators. Sometimes our actions require async or other operations with side effects, such as retrieving or saving data to a backend server. We may then use the app named NgRx/Effects. We'll cover what effects are and when and how to use them. Most real applications require creating, updating, and deleting data. We'll walk through how to handle these operations with NgRx. We explore additional architectural considerations when building larger applications with NgRx and look at the OnPush change detection strategy for improved performance. Lastly, we briefly look at other NgRx packages, such as schematics, Entity, router store, and NgRx data. We're covering a lot of information, and by the end of this course, we'll have an application that leverages NgRx for state management and component communication. You can use this application as a reference for your own development. Now let's dive a bit deeper into the Redux pattern and better understand its pieces.
-
The Redux Pattern
Introduction
Before we jump into implementing NgRx, let's take a high-level look at the pattern it follows. Welcome back to Angular NgRx: Getting Started, from Pluralsight. My name is Duncan Hunter, and in this module we cover the core concepts and principles of the Redux pattern used by NgRx. Patterns bring order to chaos, and the Redux pattern is no different. Redux is not just an Angular concept and is implemented in almost all major front-end frameworks. React has Redux the library, View has Vuex, and Angular has NgRx. So what is the Redux pattern? Redux is a way to implement a predictable state container for JavaScript apps. Redux the library, which was the first to implement the Redux pattern, was based on Facebook's Flux library, and in the last few years has become the dominant state management pattern for single-page applications. The Redux pattern, which NgRx is based on, has three main principles. First, there is only one single source of truth for application state called the store. Second is that state is ready-only and the only way to change state is to dispatch an action. And the third, is changes to the store are made using pure functions called reducers. In this module, we examine the core principles of the Redux pattern used by NgRx, including the store, actions, and reducers. And finally, we'll highlight the main benefits you can expect to get from using Redux. Let's get started.
-
Store
The store is literally a JavaScript object that holds all of your application state. You can think of it as a client-side database. With Angular, you may be building services to hold your application state. Following the Redux pattern, all of this state instead is retained in the store. This becomes very powerful when it comes to reasoning about user interaction, debugging, performances, and avoiding race conditions in general. Do you have to put every piece of state in the store? Well no, you don't. So what shouldn't go in the store? Unshared state that is solely owned by a single component that does not need to be shared or made available across routes. Angular forms also don't belong in the store as they are usually self contained and do not need to be shared across multiple components. Also, Angular forms are not serializable or immutable and can change themselves, meaning you will not be able to track those changes with actions, which is the second principle of Redux, that to change state you need to dispatch actions. Finally, state that has cycles in it or has complex data structures that cannot be serialized should not be put into the store. For example, the whole router state should not be put into the store because it is not serializable.
-
Actions
All relevant user events are dispatched as actions, effecting reducers who update the store. Some examples of actions you might dispatch to change the store are a login action after a login form submission, toggle side menu action after clicking a button, retrieve data action when initializing a component, and start a global spinner action when saving data. Actions are basically simple JavaScript objects with a type as a string and an optional payload that can be of any type. When we say the store is read-only and that to change state we dispatch actions, we mean you shouldn't mutate the state and you should follow the principle of immutability, meaning that if you need to change the state of the store, then replace the whole state object and not just mutate part of it. Immutability can bring increased performance to your apps and lead to simpler programming and debugging as data that never changes is much easier to reason about then data that is free to be changed arbitrarily throughout your app. We'll talk more about immutability and non-mutating methods for updating the store later in this course.
-
Reducers
Reducers are functions that specify how state changes in response to an action. Here are some examples of state changes we might make in a reducer. Set a userDetails state properly on login, toggle a sideMenuVisible state property to true on a button click, set successfully retrieved data on a component initialization, or set a globalSpinnerVisible property to true while saving data. Not all dispatched actions can directly update the store via a reducer as some actions have side effects. To manage side effects, we use the NgRx Effects library, which we'll cover later in this course. A reducer is a pure function, accepting two arguments. The previous state and an action dispatched to update state. Reducers use switch statements to listen and act on specific action types, taking the actions payload and state and returning new state. But what is a pure function? A pure function is a function, given the same arguments, will always return the same value with no observable side effects. For example, this function called sum that takes in two arguments, a and b, and simply adds them together, will always return the exact same value when called with the same two arguments. So pure functions will always return consistent results, but also pure functions will not mutate or access properties outside of their function scope. This is an impure function because it depends on outside variables.
-
Advantages of the Redux Pattern
So why take the time to learn NgRx and the Redux pattern it implements? Having a centralized immutable state tree makes state changes more explicit and predictable with a repeatable trail of state changes, making it easier to track down problems. Using pure functions to change state allows features in Redux, like time travel debugging, record replay, and hot reloading. It also makes it easy to hydrate your application's state from local storage or when doing server-side rendering. Redux makes it easier to implement an Angular change detection strategy in your components called OnPush, which can improve your view performance. We cover this strategy later in the course. Using Redux makes writing unit tests easier. All of your state changes go through pure functions, which are much simpler to test. Tooling is another huge benefit of using the Redux pattern, as Redux makes it possible to have a history of state changes. This makes it easy to visualize your state tree, debug by undoing or redoing state changes, while also getting advanced logging. This is a huge change to how we normally debug an Angular application and most developers are surprised at how much less traditional debugging they do, like console logging and setting breakpoints. Another benefit of Redux is just simpler component communication. NgRx makes it easier to access to shared state via injecting the store into a component versus passing data between components. I've really liked this quote for expressing the value of the Redux pattern. "Redux is not great for making simple things quickly. It's great for making really hard things simple."
-
Checklists and Summary
Let's finish up this module with a checklist around the principles of the Redux pattern. There is only one single source of truth for application state called the store. State is read-only, and the only way to change state is to dispatch an action. And changes to the store are made using pure functions called reducers. Now we've looked at het pattern that NgRx is based on and its underlying principles, let's move on to have a look at some of our first NgRx code.
-
First Look at NgRx
Introduction
Now that we know its basic pattern, let's take our first look at NgRx in action. Welcome back to NgRx: Getting Started from Pluralsight. My name is Deborah Kurata and in this module we install NgRx and use it to implement the Redux pattern for a simple scenario. Using NgRx often involves quite a bit of orchestration and ceremony, which can make it seem complex, especially at first. To focus on the basics, we'll skip the formalities for now and implement our first scenario more informally, taking smaller steps and using only the essentials. We'll add the ceremony in later modules of this course. The first bit of state we'll implement is the show product code flag associated with the Display Product Code checkbox. When the user clicks the box, the view uses event binding to notify the product list component. The component then creates an action and dispatches it to the reducer, telling it to toggle the show product code property. The reducer uses that action and the existing store state to create new state with the show product code property set to true. It then returns that new state to the store. The store broadcasts the state change to all subscribers, which in this case is only the product list component. The component updates its bound property and Angular's change detection automatically updates the display showing the product codes. In our first simple scenario, we won't use the selector function, instead we'll subscribe to all product state changes from the store. Now let's implement our first data flow with NgRx. In this module, we set up the sample application we'll work with throughout this course. Then we install NgRx store, initialize the store for our application, define the state to track and the actions that change that state and build a reducer to process those actions and set the store state. Then in a component, we dispatch an action to request a state change and subscribe to the store to get state change notifications. Let's get started.
-
Demo: Setting up the Sample Application
We've prebuilt a sample application that includes all of the components and templates, but does not yet use NgRx. We can then focus on implementing NgRx techniques into this existing application. To code along with this course, let's download and install this sample application. Here is the GitHub repository for this course. The starter files for the sample application are here, marked as APM-Demo0. Use these files as a starting point to code along with the demos. Each of the remaining folders contain completed demo files at specific points in the course. The README file identifies the modules covered in these folders. If you have issues while coding along, or to walk through completed code, use one of these folders. If you're new to GitHub, simply click this button to download all of the code as a zip file. I've already downloaded this code and renamed the APM-Demo0 folder to just APM. I then navigated to this folder. From here I type code dot to open the folder in VS Code, but you can use your preferred code editor. If you're coding along, these are the starter files we just downloaded. To use these starter files, you must first install the application's packages. Open the VS Code integrated terminal and ensure you are in the folder containing the package.json file, then type npm install. This installs all of the packages defined in that package.json file. You may see warnings during this process, you can ignore them. If the process is successful, you should see a summary message with the number of added packages. I've already completed this step, so I won't do it again. I'll clear this terminal window. Note that there is no real backend server for this application, rather it uses the Angular in-memory-web-api library, which emulates a backend server and supports basic CRUD, or Create, Read, Update, and Delete operations. This is the same library used by the Angular documentation. For more information on this library, see this GitHub repo. We are using this library so you don't have to set up and install a backend server. Now let's try out our sample application. In the terminal type npm start. This serves up our application, and since I have -o defined here, it automatically opens it up in your default browser. It starts with a display of a welcome page. Select Product List to view the list of products. Check the show product code checkbox and the product codes appear in the list. Uncheck it and the product codes disappear. Impressive, I know. Check it again, then navigate to the home page. Coming back to the product list, our prior selection is gone. When we navigated away from the product list page, its component and its local state was destroyed. We can manage this state better with NgRx. As we progress through this course, we'll also manage our list of products and track the current product with NgRx. But we'll take it one step at a time, starting with this checkbox. To begin, let's install the NgRx store. One note before we move on, since this application is using the in-memory-web-api, any changes you make to this data are only in-memory. They are lost if you refresh the browser or restart the application.
-
Installing the Store
NgRx is comprised of a set of packages. The only required package is the store. That's the one we'll install first. We'll install others as we need them. The NgRx store package provides an in-memory container for our application's state. This store provides a single source of truth for the application. It is the one place where we store our application state and the one place to read that state. This ensures that every component that accesses the state has consistent results. The store is runtime only, so the state is not retained if the user refreshes the page or after the user exits the application. Think of the store as a container with a simple JavaScript object holding the state for the application. Each bit of state is defined with a property. Here our properties include a showProductCode flag, the currently selected product, and our list of products, all used by the product feature pages. As we add more features to our application, the amount of state retained in our store grows. It may become unruly to manage and difficult to tell which state goes with which feature. Which page is this allowEdit flag for? The state is much easier to manage if it is arranged into a logical structure. Because Angular applications are often organized into feature modules, it makes sense to layout the state by feature as well. This creates a tree-like hierarchy of properties. Here we define our root application state, such as whether to hide the welcome page. Next we have the state associated with our products feature, then our users feature state, and continuing, one for each feature module. Each feature state is added to the application state once the feature is loaded, resulting in a single state tree. These pieces of state are sometimes called slices. So we have the products slice, users slice, and so on. Think of the store somewhat like an in-memory database. If you've ever designed a database, you don't put all of the data into one flat structure, rather you organize it so you can more readily access the values you need. Now that we have a sense of what the store is, let's install it in our application. We are back in VS Code with the APM sample application open. Since the application is already running in this terminal, I'll click plus to open another terminal. In the terminal, we install the store using npm or yarn. I'll use npm. Start by typing npm -v to check your version of npm. Ensure you have version 5.5.1 or higher. If not, install a newer version of npm before continuing. Then type npm install @ngrx/store. This installs the store package into our application. You may, again, see some warnings that you can ignore. When the installation is finished, you'll see the package version logged to the console. The store is now installed and ready to be initialized in the application.
-
Initializing the Store
When we set up the NgRx store in our application, we initialize the store with its reducer. Recall that the reducer takes in an action and the existing state, creates new state, and updates the store with that new state. So it makes sense to associate the store with its reducer that creates the state for that store. The code to initialize the store looks like this. In our root App Module, we use the import statement to import the StoreModule from the NgRx store library we just installed. Then we add StoreModule to the imports array. Since this is the root application module, we call the forRoot method and pass in a reference to the reducer. We haven't looked at the code for a reducer yet, but we will shortly. This store initialization associates the reducer with a store and registers the state container with the application. With NgRx, we have one store for all of the state, so we could build a single reducer to work with that state, but we don't want one big, flat, massive state, nor do we want one huge reducer to manage all of this state. Rather, we'll organize our state by feature to match with our feature modules. To create this state, we define multiple reducers, one for each feature's slice of state. Each reducer is then smaller and more focused and therefore easier to build, maintain, and test. Plus state is never created for a module that is not loaded. It is the combination of these reducers that represent the application's state at any given time. To achieve this kind of organization, we'll take advantage of a technique called feature module state composition. This allows us to compose our application state from our feature module reducers. That sounds much harder than it is. To use feature module state composition, we begin by initializing our root application state in the root AppModule, just as we saw previously. We pass to the forRoot method, the reducer that creates our root application state. This could include values such as a hide welcome page flag. We then initialize each features state using the StoreModule forFeature method. The forFeature method takes in the name of the feature slice, which is often the plural form of our feature. The second argument is a reference to the reducer that manages that feature's slice of state. We repeat this code in each feature module that uses the store. Notice how similar these methods are to those used by the router. Here we have RouterModule forRoot to define the root routes. And here we have RouterModule forChild to define the feature module's routes, though forFeature is a better name choice. We could break our state down even further and build multiple reducers for one feature slice. In this example, we divide the product's slice of state into two sub-slices, one for the productList page settings, the other for the productData. We then define a reducer for each sub-slice of state. These reduces are aggregated for their associated feature. This is especially useful when a single feature has lots of state, allowing us to break our reducers into smaller pieces. When initializing the store for this scenario, we again set the name of the feature slice as a string in the first argument of the forFeature method. In the second argument, we pass in the set of reducers as key and value pairs. The key is the name for each sub-slice of state and the value is a reference to its associated reducer. You'll see this technique in some of the more intermediate level NgRx examples. For our example, let's stick with one reducer for our products feature slice. Now let's give this a try.
-
Demo: Initializing the Store
In this demo, we'll leverage feature module state composition and initialize the store in our root app module and in our product feature module. We'll start in the root app module. Even though we don't have any root application state at this point, we still initialize the store in our root application module to register the store container with our application. First we add the import statement, then we add the StoreModule to the imports array and call its forRoot method. We don't have a reducer for our root application state, so we'll pass in an empty object. Our application now has a store container. Next, we initialize the StoreModule in every feature module that defines a slice of state. We are currently defining product state, so we'll add it to the product module. Again, we start with the import statement, then add the StoreModule to the imports array. We'll call its forFeature method, since we are adding this code to a feature module. The first argument is the name of the feature slice. Since this is our product module, we'll name it products. The second argument is the reducer, or set of reducers, that create our products state. We don't have a reducer for that yet either, so we'll again pass in an empty object for now. That's all it takes to initialize our store, now let's define our state and actions.
-
Defining the State and Actions
When using NgRx, it's important to take a moment to think about the state we need to track and the set of actions that change that state. For our first scenario, our state is simple. We track the value of this checkbox, defining whether to include the product code in the display of our product list. We'll call that bit of state showProductCode. With our first bit of state defined, let's analyze the actions needed to modify that state. Recall that an action is an object with an action type and optional payload or data associated with it. For our showProductCode state, we have two state changes. We set the value to true when the user checks the box and false when the user unchecks the box. We could define two action types, one to show the product code and one to hide the product code. Neither action type needs a payload because the action type itself defines whether to set the showProductCode state to true or false. Alternatively we could define one action to toggle the product code and pass a payload defining whether to set the showProductCode to true or false. Whether you use one action type or two in this scenario, really doesn't matter. In either case, our next step is to build a reducer.
-
Building a Reducer to Process Actions
We've defined our state and our action, now we are ready to build a reducer to process that action and create new state. Though the store is the star of the NgRx show, it is the reducer that does all of the work. As actions are dispatched, it's the reducer that performs any required processing and returns a new representation of the state to the store. This is a key concept of NgRx. The store's state tree should be immutable, meaning its structure and values should never be changed once it's been created. Wait, what? If we aren't supposed to change the store's state, how do we process our actions? Let's walk through an example. We registered our product's reducer in the product feature module when we initialized the store, so our product's reducer represents the product's slice of state. Let's say our product's slice of state includes the data for the currently selected product and the list of products for display in the product list. The user clicks on the Display Product Code checkbox and the TOGGLE_PRODUCT_CODE action is dispatched with a payload of true. The reducer takes in that action and the reducer's associated slice of state, which in this example is our entire product slice. The reducer then creates new state by copying the existing state and applying changes to that copy based on the defined action, in this example, by adding showProductCode with a value of true. The reducer then replaces the product's slice portion of the state tree with this new state, thereby creating new state. So technically we aren't modifying or mutating the state. Why go through all of this work for immutability? It makes state changes more explicit and predictable, which is important as the application gets larger or more complex. It keeps us from asking questions like, where the heck is this being changed? And all changes are defined by specific actions that can be tracked and traced. It's important to note, however, that NgRx does not currently enforce this immutability. It is up to you and your team to follow the rules and ensure no other code in the application updates the state directly. There are third party tools to help with this, such as the ngrx-store-freeze library. Now let's see what a reducer looks like. A reducer is simply an exported function that takes in the reducer's slice of state from the store and an action. It is called whenever an action is dispatched. The reducer function evaluates the passed in action type and processes that action accordingly. The body of the reducer function is a big switch statement with a case for each defined action type, specified here as a string. For each action, the reducer creates a new representation of the state and returns that new state to the store, replacing any prior state. One important thing to note here, a reducer should be a pure function, that is to say that given the same input, the function always returns the same output with no side effects. Let's look a little more closely at the return statement. We return a new object defining our new state. To create the new state, we start with a copy of the state that was passed in, which is what this code does. These three dots are the JavaScript spread syntax, available for use with arrays or with object literals. When used with object literals, as in this example, the spread syntax expands the object, basically copying the properties from the provided object to this new object. This is similar to object.assign, but shorter. Then we specify the state to modify based on the dispatched action. If the specific bit of state already exists, it changes the property value in the copy. If the state does not exist, it adds the property to the copy, like this. Returning this new state then replaces the original state in the store. In this example, we have only one action type. If we chose to use two actions for our scenario, our reducer would have two case statements, one setting the new state to true, the other setting it to false. As we add more actions for our product's features, we add more cases here. We can also stack case statements. Here we have two different actions that set the same state. This minimizes the amount of repeated code in our reducers. Let's jump back into a demo.
-
Demo: Building a Reducer to Process Actions
In this demo, we'll build a reducer to process our action and hook that reducer up to the store. We are back in the APM sample application and ready to create our reducer. The first question we ask ourselves is where, where should we put this reducer? There are several ways to layout the folders for an application. Currently our sample application folders are divided by feature. We have a home folder, products folder and user folder. We could have many more folders as we add more features. We'll organize our state actions and reducers by feature as well. So let's define a folder within our product feature folder for the features state information. We'll call that folder state and it will contain the actions and reducers for this feature. Under the state folder, we create a new file for our reducer. We'll name it product.reducer.ts. I'll paste the basic structure of our reducer and we'll talk through it. This function takes two parameters, the first parameter is state from our store, the second parameter is the action to be processed. The body of this function is a switch statement with a case for each possible action. If none of the action types match the dispatched action, our default case returns the original state to the store. Next we add a case statement for each action to process. For the Display Product Code checkbox, we defined a TOGGLE_PRODUCT_CODE action, so we add a case for that action here. We then create an object literal, defining the new state and returning it to the store. When the TOGGLE_PRODUCT_CODE action is dispatched, we update the showProductCode property in the store with the passed in payload. But there is a problem with this code. Do you spot it? If we have any other state in our store, we'll replace all of it with this one showProductCode property. That's not what we want. Instead we first spread the existing state, effectively making a copy and then apply our changes to that copy. And just temporarily, let's add a little logging so we can see when the state is changed. We'll display the existing state before it is changed by the reducer and the actions payload. It is this reducer that basically defines the state for our store. That's why we specify the reducer when initializing the store. Now that we've built the product reducer, let's go back to our product module and update the store initialization. Here instead of an empty object, we reference our reducer and use the VS Code quick fix feature to add the appropriate import statement. With our reducer defined, we are now ready to dispatch our action.
-
Dispatching an Action to Change State
We have our store initialized, our state in action defined, and our reducer implemented, but nothing happens until we dispatch the action. We often dispatch actions from our components based on user events. Before we can dispatch an action from a component, we must inject the store into that component. Just like any other service, we inject the store's service using the constructor. Notice the any data type here, we aren't yet strongly typing our state, so the generic type argument must be any. When the user performs an operation, such as checking the Display Product Code checkbox, we dispatch an action. We dispatch the action by calling the store's dispatch method. We then create an object literal defining our action and pass it into that method. Recall that an action is an object with a type and an optional payload. Our type is the string we defined for our action. We'll see later how to strongly type our actions so we won't need hard coded strings, but this works for now. We optionally define an action payload. This is the value that we dispatch with the action and is the value the reducer uses to create new state. In this example, it's the checked value of the checkbox. Let's dispatch an action. In this demo, we'll dispatch our TOGGLE_PRODUCT_CODE action each time the user clicks the Display Product Code checkbox. We have the product-list.component and its template open. Our goal is to dispatch an action when the user toggles the checkbox. The Display Product Code checkbox is defined in the product-list.component's template. We already have the code in place to use event binding and call the checkChanged method each time the checkbox is checked. The binding passes in the checked value of the event, which is true or false. Looking at the product-list.component, the checkChanged method currently sets a local displayCode property to the checked value passed from the event binding. In the template, the local property is used with an ngIf directive to hide or show the product code in the list. This works well until the user navigates away from the product-list page. Since the displayCode property is local to this component, its value is lost when the component is destroyed. So if the user navigates back to this page, it doesn't remember its previous setting. Let's dispatch an action to retain the value in the store instead. In the component, we first inject the store into the constructor and define the generic argument type as any. Then we use the quick fix to add the import statement for the store. There are several options here, so be sure to pick the one from NgRx store. Next, we replace the code in the checkChanged method with the code to dispatch the action. We call this.store.dispatch and pass in our action object, specifying the type using our type string, and payload, which is the checked value passed from the event binding. With our action dispatched, we can try it out. Let's check it out in the browser. Open the developer tools with the Console tab selected. Then click the Product List option. The first set of logging is from our data access component when it retrieves the data for the page. Let's clear that, then click the Display Product Code checkbox. We see that the existing state is undefined. This is expected because when the application starts we have no state defined in this store. The payload of the dispatched action is true, which is correct to turn on the display of the product codes, but it doesn't display the product codes. If we check the checkbox again, we see that the existing state now has our showProductCode property with a value of true. So the store state tree was replaced with our new state. Our reducer seems to be working. We also see that our new payload is false. Check it again and we see that the existing state correctly reflects our new value. Why isn't the product code showing? That's because we are not yet retrieving the state from the store. Let's do that next.
-
Subscribing to the Store to Get State Changes
Each time the user clicks the checkbox, the action is dispatched, the reducer is executed, and the existing store state is replaced with the new state, including the checked value. But we are missing the final piece. We are not yet getting the value from the store. That's why our UI is not reflecting the change in state. To access a value in the store, we select the appropriate slice of state. We select from the store using the store's select method, specifying the name of the desired state slice, which in this example is our product slice. Or since the store is an observable, we can use its pipe method and the NgRx select operator. Both the method and the operator work the same, however, the benefit of the pipe syntax is that we can readily add other pipeable operators as needed and shape the results as required by the component. We'll see that later in this course. The NgRx select method and select operator both return a slice of state as an observable. If we want our component to be notified of changes to the state, we subscribe to this observable. Note that we are subscribing to this entire slice of state, so any changes to any bit of state in the products slice generates a notification and provides the entire slice of state. We'll improve our subscription a bit later in this course. The first argument to the subscribe method is a next function. That function is executed each time it receives the next change notification from the store. In that function, we set our local displayCode property to the desired bit of state from our state slice. So each time an action causes the reducer to replace the state, if the product slice was effected, the component is notified and this code is executed. Let's complete our first NgRx data flow and subscribe to state changes. Looking at the product-list.component, we want notifications whenever the product's state changes. We'll add the code to subscribe to changes in the ngOnInit method, so it immediately provides any existing values from the store. I'll paste the code and we'll talk through it. Here we use the NgRx select operator to select the products slice of state, use the quick fix to add the import statement for the select operator. The string is the name of the slice of state. We then subscribe to receive change notifications. When we first subscribe, and every time the state changes, we receive the entire products slice. When the application is first executed, its slice of state is undefined as we saw in our last demo. So here we wait to read the flag from our slice of state until that state exists. We then set our local property to the value from our slice of state. Our template binds to this local property and uses it in an ngIf to toggle the display of the product codes. So this should turn the display on or off based on the state change. If you've subscribed to observables before, you know that we also need to unsubscribe from them. One way to do that is to store the subscription in a variable, as we do up here, and then use that variable to unsubscribe, as we do here. But as we add more subscriptions to this component, storing each in a separate variable just won't cut it. We'll talk about unsubscribe strategies in the effects module later in this course. For now, we'll leave it as a TODO comment. So that's it, we've subscribed to changes from the store. Will it work? Bringing up the browser, we check the box and the product codes appear, uncheck and the product codes disappear. Check the box again, navigate to the Home page, then back to the Product List, our store retained our state and the checkbox is reset to the previous selected value. We've now completed our first NgRx implementation. Yay! Now that we have it working, let's go back to the code and remove our logging. In the next module, we'll see a much better way to watch our actions in state, but first let's finish up this module with some checklists for using each of these parts of NgRx.
-
Checklists and Summary
The NgRx store provides a single container for our application state. Our goal is to interact with that state in an immutable way, replacing the state instead of modifying the states structure or values. To use the store, start by installing the NgRx/store package. Consider organizing your application state by feature, then name each feature slice with the feature name. Initialize the store, both in the root application module and in each feature module that uses the store. An action represents an event that changes the state of the application, such as the user checking a box or selecting an item or the component loading data. Define an action for each event worth tracking in the application. Don't track events local to a component, unless those events change data that must be tracked after the component is destroyed. An action is represented as an object with a type and optional payload. A reducer responds to dispatched actions and replaces the reducer's slice of state with new state based on the action. Build a reducer function to process the actions. You'll often have one or more reducer functions per feature. Implement the reducer function as a switch statement with a case per action, and don't forget to spread the state as needed. This copies the existing state to a new object and allows updating that copy before overriding the existing store state with that copy. Once we have the state actions and reducers in place, we are ready to dispatch an action. Dispatching an action is often done in response to a user event, such as checking a box or selecting an item or for an operation, such as retrieving data. First inject the store in the component's constructor. To dispatch an action, call the dispatch method of the store and pass in the action to dispatch. The action is passed to all reducers and processed by any matching switch case statements. For any of our components to react to changes in the store, they subscribe. Subscribing to the store is often done in the ngOnInit lifecycle hook. This provides any current store values when the component is initialized and immediately starts watching for change notifications. First, inject the store into the component's constructor, then use the store's select operator or select method, passing in the desired slice of state. Lastly, subscribe to get the current store values and watch for changes. The component receives notifications whenever the selected slice of state changes. As we work through this module, you may have contemplated how nice it would be to actually watch these actions get dispatched and see the state in the store. We'll see how in the next module. But first, an optional homework assignment. To try out what you learned in this module or practice a bit more with NgRx, give these steps a try. Here is the login page displayed in the APM application. Notice that it also has a checkbox at the bottom. If checked, this masks the user name. Run the application and examine the login page, specifically how this checkbox works, and notice that when you leave this page and come back, by clicking on your user name in the menu bar, the value is not retained. Note that there is no verification of the user name and password, so you can put in any value. Your task, should you decide to accept it, is to retain the value for this checkbox in the store, similar to how we handled the show product detail state. Initialize the store in the user module, define the state in action, create the reducer function, dispatch the action, and subscribe to the user feature slice of state. The completed steps are in the APM-Demo1 folder in the same GitHub repo as the starter files. Enjoy!
-
Developer Tools and Debugging
Introduction
To really see what is going on with our application and our store, we need some tools. Welcome back to Angular NgRx: Getting Started from Pluralsight. My name is Duncan Hunter, and in this module we install the Redux DevTools available with NgRx. Having the right tools is critical for debugging your Angular applications, and with NgRx you get to take your tooling to the next level with the Redux DevTools. Previously we needed to console log our actions in the reducer to get a visual on what actions were being dispatched, but let's start using the awesome tools already available to us. In this module, we look at how to get the Redux DevTools browser extension, install the ngrx/store-devtools package, navigate and use the tools, and look at how to do time travel debugging. Let's get started.
-
Installing the Tools
You can install the Redux DevTools in three simple steps. First, install the Chrome browser Redux DevTools extension, then npm install the ngrx/store-devtools package into your application, and then initialize ngrx/store-devtools module in your main app module. The Redux DevTools extension is the tool for visual analysis and debugging, while the @ngrx/store-devtools package is for exposing your state and reduces to the Redux DevTools extension. This is the link to the Chrome dev store and specific Chrome extension we'll be using. The Redux DevTools are also available for Firefox plus Electron and other browsers, or non-browser environments via the remote Redux DevTools. This is the same tool used by other frameworks and libraries that follow the Redux pattern, like React and MobX. Once the Chrome extension is installed, you'll see a little grayed out icon in your Chrome extensions menu like this, it will stay gray, indicating that you're on a site that is not exposing any Redux metadata. But when you do navigate to a site that is exposing its state and reducers, normally only done in dev mode, you will see the icon light up like a Christmas tree, and you can click on the icon to open up a pop out window of the DevTools. The ngrx/store-devtools comes from the same NgRx platform repository on GitHub, which you can access via this link. With the ngrx/store-devtools installed, it's trivial to add them to your Angular application's main app module. First you import the store-devtools module and the environment so that you can turn the dev tools to log only mode if the environment settings are set to production. Second, you add the StoreDevtoolsModule to the app module's import array and use its instrument method, passing in an object with a name property for the application, which in the browser, if you have two applications running, you can use to filter down to debug only the one application at a time. The maxAge property limits the amount of actions to be stored in history with the oldest actions removed once maxAge is reached. The maxAge setting is critical for performance with a default of 50. The logOnly property disables all extension features, but logging if set to true and will disable features like time travel debugging. Let's jump into a demo where we will install and initialize the Redux DevTools. Back in VS Code with our terminal open, we can install the DevTools with npm by typing npm install @ngrx/store-devtools. With the dev tools installed we can Command+J to close our terminal and Command+P and navigate to our app module, and down in our app module's import array, we can use the StoreDevTools module, but we are not getting any auto import help here, so I'll go up and add the import by typing import StoreDevtoolsModule from @ngrx/store-devtools. With this done, we can now use the StoreDevtoolsModule's instrument method and pass in an object with the settings we want to use. First we name our instance of dev tools APM Demo App Devtools, and second, add a maxAge set to 25, and lastly add a logOnly property using our environment variable's production setting. Now we're exposing our store to the outside world, let's go install the Redux DevTools Chrome extension to listen to our application's exposed by the StoreDevtoolsModule. Over in the browser, at the Chrome web store at this URL, we can search for the Redux DevTools and we want the most popular one here, so clicking the Add to Chrome button and agreeing to add the extension, we can now see our extension grayed out at the top of the browser. If we open a new tab and navigate to the running app on localhost 4200, you can see how the DevTool icon has changed color, and this indicates that an open website using Redux is being tracked by the DevTools. Clicking on this, we get a pop out window and a choice to pin the tools to the left, right, or bottom in a floating panel. But I prefer to open the native Chrome DevTools and use the Redux tab here, thus as a floating panel. The downside of this approach is you need to have the Redux tab selected when you navigate to the app or it will stay blank until you refresh the page. I know this kind of is annoying, but still it's better than floating panels if you ask me. So we now have the DevTools working, and in the next clip, we'll look at what they give us and what are the most common features you'll want to use.
-
Using the Tools
Let's jump into a demo where we look at using the main features of the Redux DevTools. Over in the browser, on the Home page, with the DevTools open, the first thing you can see on the left is our actions log. This lists all the actions dispatched in our application, which at the moment is only two system dispatched actions to initialize the store. If we click on this action @ngrx/store/init, then on the right click on the Action button to view this action, we can see it has a type of @ngrx/store/init and no payload. Inside this Action view, we can choose to look at the Tree view, which we are currently on, or the Chart view, which is cool but I tend not to use this much, and the Raw view, which can be very handy to cut and paste from. We also have a second dispatched action to update store reducers. If we click on this action, we can see that it has a type of ngrx/store/update-reducers and a feature property set to users. If you've been following along during the homework exercises, you will have added a users reducer, if not, you will not see this second dispatched action. At the moment we have no state loaded when we initialize our application. If we click on the Product List menu item, we can see another NgRx system dispatched action and clicking on that action, we can see that it has a feature name of products. Clicking on the State button over here, we can see what our state tree looks like when this action was dispatched. We can see our product state is still empty. Later in the course, we'll initialize a default state for each feature module, but for now the default still is an empty object. If we toggle our Display Product Code checkbox here to turn it on, we see an action logged to the DevTools. Clicking on this TOGGLE_PRODUCT_CODE action, we see the state tree on the right changed to have a showProductCode flag set and that it is nested in the state tree under our products feature state. We can click back and forth on each action, the one before it, and see what the state tree looks like at each point in time. We can also click on the Action view and see more details about the action's type and payload. Just this feature of the tools alone, being able to see your application state and step back and forth in state as actions are dispatched, is a massive deal and a real game changer to how you debug your Angular applications. If we go and toggle our Display Product Code on and off a couple of times and dispatch a bunch of actions, we can see it will dispatch multiple actions into our Action log. Now clicking on the slider button at the bottom of the tools, we can use time travel debugging to replay our action state. So if I just click Play, it will start at the beginning of the Actions log and we'll see our UI automatically toggle on and off our Display Product Code checkbox as it replays the actions. We can also use the arrows to go back and forth in time. These tools have a lot of other advanced features you'll use less often, but are worth quickly mentioning. You can pause and unpause logging actions by clicking on this button, you can persist the current action log, so if you refresh the browser, this log is kept, you can dispatch actions manually from the browser. If we close the slider and click on Dispatcher, we can copy an action here from the roll view and just give ourselves lots of room here. We can then paste that in here and we can manually dispatch this action. You can also import and export your action and state history into and out of the tools. This can be handy to import a bug state history from a production app into a dev environment to replay, also it can be handy to export your state tree for use in tests to ensure the test case covers certain scenarios. There's lots to explore in these tools, but we've now covered the main types of things you need to get started and will use day to day.
-
Checklists and Summary
Let's finish up this module with some checklists around the DevTools. To install the Redux DevTools, you'll first need to install the Redux DevTools Chrome extension, then you'll need to npm install the @ngrx/store-devtools package, and finally initialize the StoreDevtoolsModule in your main App module. The main features of the Redux DevTools you will want to use are inspecting action logs and specific actions, visualizing the state tree as actions are dispatched, and time travel debugging. Up next, we'll power up our code with strong typing our state.
-
Strongly Typing the State
Introduction
One of the benefits of using TypeScript with Angular is its strong typing, but so far in our implementation we have hardcoded strings and way too many any. Welcome back to Angular NgRx: Getting Started from Pluralsight. My name is Deborah Kurata and in this module we strongly type our state. Without strong typing, it is often much too easy to find ourselves trying to put a round peg in a square hole. Strong typing helps us use the right type at the right time. Let's take a quick look at the state of our state and potential issues with our current typeless approach. Here we are in VS Code with the APM application open. Let's start with our product reducer. We pass in the state and an action. Hovering over the state, we see that it is of type any, so no strong typing here. Hover over the action, it's any as well. Because we have no strong typing, we could literally add anything to our state here. Yeah, I know, that's a bit far-fetched, but it would be easy enough to misspell something. How about this? That's what we really like about strong typing, it helps prevent us from making that occasional spelling mistake or typo. But we get no help here. Now let's look at the product-list.component. Notice that we set the type of the generic store argument to any. Here where we watch for changes to the state, hovering over products gives us, yep you guessed it, any. So when we type products dot, we get no help. This is another place to easily introduce typographical errors, misspellings or a bad guess, such as specifying the wrong property here. Checking out the terminal, no syntax errors, so nothing telling us about the mistakes we've just created. Bringing up the browser, unsurprisingly our Display Product Code checkbox no longer works. We can use the NgRx DevTools to help resolve these types of issues. Open the tools, as shown in the last module, and select to watch the State. Here we may notice our misspelling, and of course, the out of place bit of state. But imagine trying to find these issues when you have lots and lots of state. We may save ourselves hours of debugging and frustration if we strongly type our state. In this module, we define interfaces for our slices of state, then use those interfaces to strongly type our store's state. We examine how to initialize our state so we have predictable initial values, and build selectors to simplify accessing our state. We'll strongly type the actions in the next module. Let's get started.
-
Defining Interfaces for Slices of State
Earlier in this course, we discussed how to organize and structure the state for our store. We don't want one big, flat glob of state. Rather we organize our state into feature slices. This keeps the state associated with its feature module and makes it easier to find and manage the state. How do we strongly type this state? With TypeScript, we use interfaces. For each slice of state we define an interface that describes the structure of that state. For our root app state, we may have a hideWelcomePage property of type Boolean that defines whether to hide the welcome page on future logins. For the product feature, we have a showProductCode property, a currentProduct property, and a list of products. The interface now matches our product slice of state and can be used to strongly type that state. We create something similar for each additional slice of state, then we pull it together to compose the global application state from each feature slice of state. This interface then defines our entire state tree, referencing each of the feature interfaces for detailed properties. Let's try it out. In this demo, we'll define the interface for a slice of state, namely the product slice, and use it to compose the interface for the entire global application state. We are back in VS Code. Our first question is where, where should we define the interface for our state? Earlier in this course, we said that our reducers provide a representation of our application state. So it makes sense to define our feature state interfaces in their associated reducers. Here in the product.reducer file, we'll add an interface for the product slice of state. We export the interface so we can use it anywhere in our application, and we'll name it with the feature name. We can't just call it product, however, because we already have a product interface that defines the structure of our product entities. So we'll call it ProductState. The first cut of our ProductState includes our showProductCode flag, the currently selected product and the array of products displayed on the product list page. So we define each of these properties as part of our interface. Next we define the interface for the entire application state tree. Where should we put that? One option is to create a folder directly under our app folder and name it state. Then in that folder, create a file named app.state.ts. In this file, we define the interface for our entire store's state. We don't currently have any root application state. We do have state for our product and user features, so we define two properties, products and user. We've just defined the interface for our product slice of state, so we specify that as the type here. For now the user slice of state is of type any. You can change that later if you do the homework and create the user state interface. Now we've just defined the complete structure of the state in our store. It currently has two feature slices, product and user. As we build out other features, we'll add them here. But there is an issue with this code, I'll give you a hint, our products feature is currently lazy loaded. Let's look at what that means and why this code breaks our lazy loading boundaries.
-
Extending the State Interface for Lazy Loaded Features
When we set up a feature module for lazy loading, that module is independently bundled, and when the user accesses the application, it is downloaded from the server separate from our main application bundle, after the first page of the application is displayed. This improves our application's startup performance. For more information on lazy loading, see the Angular Routing Course here in the Pluralsight library. So what does lazy loading have to do with our state interface? We want to establish logical boundaries around our lazy loaded features. To maintain that boundary, we want to keep our lazy loaded feature areas completely separate from our initial bundled code. By directly importing the product state interface here, we break through that boundary. Now what? We'll instead define the interface only for the users slice of state, since it isn't lazy loaded. Then we don't need that problematic product state import statement. Then in the products feature code, we'll extend that definition of our global application state to include the product state. Since this code is part of the products feature, we keep it within our lazy loading boundary. Here we define a state interface that extends our global application state interface using the extends key word. But what is this odd syntax? Instead of importing each individual interface from our app.state file, we import *, which imports all of the exported members. To make the interfaces easier to access, we define a namespace for those exported members using the as keyword. We then reference each interface using fromRoot. Yeah, I know, we only have one interface in this file now, but we could add other interfaces over time. Extending the global application state results in a state interface that looks just like our original, but is defined to keep our lazy loading boundary intact. As we add features, if they aren't lazy loaded we'll add them to this state interface. Otherwise we'll use this technique and the associated feature to extend the state interface. Let's go back to the code and fix our interface to maintain our lazy loading boundaries. In this demo, we refactor our code, extending the state interface for lazy loaded features. In the app.state.ts file, we delete the product slice from our state interface, along with the problematic import statement. In the product reducer, we'll add an import statement to access our global application state. Then extend the state interface to include the product slice of state. Now we have a single state interface that represents our entire state tree. Next, we'll use these interfaces to strongly type our state.
-
Strongly Typing the State
Once the interfaces are defined, we use them to strongly type the state. Let's start with the product reducer. We'll add the type here when the state is passed in and for the return type. Bang. Notice now that our code has a syntax error. The strong typing immediately found the spelling mistake we made and the tooling provides the list of valid options. With that fixed, it tells us that we can't just add some arbitrary bit of state to the store, we must consider whether we want that property in the store or not. If so, we add it to the interface, otherwise we remove it. Having the strong typing makes quick work of identifying any typographical or spelling errors and helps ensure we don't add state that was not intended. Looking at the product-list.component, we can now strongly type our store here when we inject it. First, we add an import statement to import * from our product reducer and define a namespace to access our interfaces. Now we can strongly type our state, starting with the constructor. When injecting the store into our components, we want the entire global application state. That allows us to access any application state from our component. For example, the product list may need to access user information, such as user preferences. Our first thought may be to specify the state from our app.state.ts file, but recall that it does not include our product slice of state. Instead, we want the extended state as defined in our product.reducer file. So we'll specify fromProduct.State. And bang, the error we added here is immediately revealed. Now if we delete the incorrect property, the auto complete feature of VS Code provides a nice list of the valid state properties to choose from. No chance of a typo here. Let's see if we fixed it. Bring up the browser, checking the box shows the product codes, unchecking it hides the product codes. It works. Looking back at the code, all it took to strongly type our state was to define a set of interfaces that laid out the structure of that state. We then use these interfaces as a type whenever we reference the global application state or any particular slice of state. It would be a bit cleaner, however, if we clearly defined the initial values of our state as well. Let's do that next.
-
Setting Initial State Values
When a component first subscribes to the store, it gets the current value of its requested slice of state. You may recall from an earlier demo that when the application loads, the initial value of the store is undefined. Since one of the goals of using NgRx is to make our application more predictable, we should explicitly define initial values for each bit of state so that it is not undefined. One way to initialize our state is to define an object and set an initial value for each bit of state. To ensure the initial value is never changed, we declare it as a constant. Here we define a constant named initialState and strongly type it using our ProductState interface. We then specify an initial value for each property. To assign this initial state, we take advantage of JavaScript's default functional parameter syntax. This syntax allows us to initialize a function parameter with a default value. Now when the store is initialized and the reducer is first called, these initial values are assigned and our store is never undefined. Let's give it a try. In this demo we'll set initial values for our slice of state. Since the interfaces and the reducer are both in the reducer file, it makes sense to initialize the state here too. Immediately after the interface, we'll define our initial state. We declare the initial state as a constant, give it a name, and set its type as our interface type. Then we define an object with default values for each property. We initialize the showProductCode flag to true so it initially appears checked. We define the current product as null since no product is initially selected, and set the products property to an empty array. Then we modify the reducer function state parameter to use this initial value if there is no current state. We don't really need the type and the initial value. By defining the initial value, the type is correctly inferred, so let's remove it. Now that we are setting the initial state of our showProductCode to true, when we launch the application, the checkbox should be checked by default and the product code should immediately appear. Bring up the browser and there it is. Refresh and our default value initializes our component to the defined initial state. If we open the DevTools, select the init action, and look at the state, we'll see that the initial state is exactly as we defined it. Going back to the code, setting initial values ensures that the application begins in a known state, making it more predictable. Looking at the product-list.component, we no longer need to check our state before accessing it, because now it is predictably defined, so we can delete this if condition and simplify our arrow function. That looks nicer. Even though we've strongly typed and initialized the state, we still have a hardcoded string here, selecting the slice of state. We can do better.
-
Building Selectors
So far our components subscribe to the store, selecting the entire products slice of state. There are a few issues with this approach. First, we have a hardcoded string here that leaves us open to typographical and misspelling errors. Second, we explicitly retrieve a property from the store, making assumptions about the store structure. That means if we ever change the structure of our store, reorganizing it into sub-slices, for example, we have to find every select and update its code. Lastly, this code watches for changes to any property in the products slice of state, so this code is notified even if the showProductCode property was not changed. How do we make this better? Selectors. A selector is a reusable query of our store. It is basically like a stored procedure for accessing our in-memory state information. Selectors allow us to keep one copy of the state in the store, but project it into different shapes, making it easier to access by our components and services. Our components use the selector to select state from our store, adding a level of abstraction between our stores structure and our component. There are several benefits to using selectors. They provide a strongly typed API for the components to use. We don't have to refer to slices of state with hard coded strings. They decouple the store from the components so the components don't need to know about the structure of the store. This allows us to reorganize or split up the state differently over time without updating every component that accesses it. Selectors can encapsulate complex data transformations, making it easier for the components to obtain complex data. They are reusable, so any component can access the same bit of state the same way. And selectors are memoized, that's a fancy word meaning that the selectors returned value is cached and won't be reevaluated unless the state changes. This can improve performance. So what is a selector exactly? It is a function that drills into the store and returns a specific bit of state. There are two basic types of selector functions provided by the NgRx library. The first type is a createFeatureSelector. This function allows us to get the feature slice of state simply by specifying its feature name. We strongly type the return value using the generic argument. Here we specify our products slice of state and assign this function to a constant. When executed, it selects the specific feature slice of state. We don't export this constant so it can only be used where it is defined. The second type of selector function is a createSelector. This function allows us to get any bit of state by composing selectors to navigate down the state tree. Here we pass the feature selector function in as the first argument to this selector. The last argument is a projector function that takes in the state returned from the prior arguments, which in this case is the products slice. We can then filter, map, or otherwise process the state to return the desired value. In this case we map to the showProductCode property to return its value. We assign this function to an exported constant so we can use the selector from our components. When executed, it selects a specific bit of state and returns its value. In this case, it returns true. By using selectors in our components, if our stores structure ever changes, we can modify these selectors to access that new structure without changing any of the components that use them. One important thing to note here, a selector should be a pure function. That is to say that given the same input, the function should always return the same output with no side effects. We use the selector in our components with the select operator or select method. Here is a select operator with a hardcoded string. We are currently using this technique in our product-list.component to select the showProductCode flag. The select argument is a string representing the name of the slice of state. The select returns the data in the store associated with this name. In this case, it returns the entire products slice of state. We then manually navigate down from the products slice to the desired property, so our code must know how to navigate the state tree. To use a selector instead, we replace the hardcoded string with a reference to the desired selector function. This then returns the Boolean flag value, so we simply assign that value to our local property. This code knows nothing about our stores structure. We import * from our reducer file. To make the set of selectors easier to access, we define a namespace for those exported members using the as keyword. We then reference each selector using fromProduct. Note that you don't have to use this namespacing technique, you can instead import each selector individually from your reducer file. Let's give this a try.
-
Demo: Building Selectors
In this demo, we'll build the selectors for our products slice of state. Once again, we'll start with the question of where. Where do we define our selectors? In our example, it is the reducer that defines the interfaces for our state, so it makes sense to put the selectors in the reducer as well. But you are welcome to put them wherever it makes sense for your application. We'll look at a common alternative location for these selectors later in this course. Here is the reducer file, just as we left it. We first create the feature selector. We define it with a constant we'll call getProductFeatureState. We then build the feature selector using the NgRx createFeatureSelector function, specifying our interface as the generic argument. We'll use the quick fix feature to import this function. We pass in the name of the feature slice of state. Recall that we defined this name in the product module. We don't export this constant, so that way it can only be used inside of this code file. Now we are ready to add a selector for any state property that our application may require. Let's add a selector for our showProductCode property. We want our general selectors to be available anywhere in the application, so we export them as constants. We'll name this selector getShowProductCode. We then build the selector using the createSelector function and again use the quick fix. The first argument to this function is the selector required to retrieve the desired bit of state. For this example, it is the feature selector function. The last argument is the projector function. The projector function gets the result of the selector functions, which in this example is the product slice of state. We then manipulate that slice as needed to return the desired property value. In this case, we return the value of the showProductCode property. Note that the order of these constants matter. Since this is a simple code file, not a class, each constant must be after any constant it references. Since this constant references the getProductFeatureState constant, it must be after it in the file. Now let's add selectors for the currentProduct and array of products properties. To try these on your own, stop the video now and implement them. Are you ready to see my result? Here is the selector for the currentProduct, and here is the selector for the array of products. They both select a property value from our store without any additional processing. Now let's see how to use one of these new selectors.
-
Demo: Using Selectors
In this demo, we use a selector to watch for changes to specific state properties in our store. Here in the product-list.component, we currently select the products slice of state using a hardcoded string. Let's modify this code to instead use one of our new selectors. In the select operator, we change the hardcoded products to our selector. We can use the namespace we defined on the import statement to see the list of all available selectors. Cool. We'll use getShowProductCode. Instead of returning the entire products slice of state, it now returns only the showProductCode flag, so we change the argument here and the assignment here. That simplifies things and ensures that if the store structure is ever changed, we won't have to change our component. Will it still work? Let's bring up the browser and refresh. Because we set our showProductCode initial value to true, it comes up with the box checked and displays the product codes. If we uncheck the box, the product codes are gone, it still works. Yay! Before moving on, let's look a little deeper at composing selectors.
-
Composing Selectors
We've already seen how to compose selectors from other selectors. Each selector we created with createSelector is composed from the createFeatureSelector. But we aren't limited to one. We can compose multiple selectors to build a single selector. Let's walk through an example. Say that instead of storing the currentProduct in this store, we retain the ID of the currentProduct. We'll talk more about why we may store an ID instead of the object later in this course. For now, let's go with it as an example. The selector for this property would look like this. But our component still wants the current product, not just the ID. To do that, we further compose our current product selector. Here we pass in both the getProductFeatureState selector and the getCurrentProductId selector. The result of each selector is provided to the projector function in the order they are defined. So the first argument here is the state returned from the getProductFeature selector. The second argument is the currentProductId returned from the getProductId selector. Our projector function can then use both arguments to return the appropriate product. This code uses the array find method to find the desired product in the array using the currentProductId provided from the store. Why use composition here and not simply reference the ID from the state like this? It comes back to encapsulation. By separately defining a selector for each bit of state and then composing the selectors, the structure of the store can be changed over time with minimal impact on the code. Bottom line, when building selectors, define one for each bit of state that is accessed from the store and compose them as needed by your components and services. Now let's finish up this module with some checklists for strongly typing our state.
-
Checklists and Summary
To strongly type our application state, start by defining an interface for each slice of state. Compose those interfaces to define the global application state, then use the interfaces to strongly type the state throughout the application. To aid with predictability, always provide initial values for each slice of state. One way to set initial values is to define a constant with an object literal that defines a default value for each state property. We then initialize the state by setting the reducer functions state parameter to this object literal. Build selectors to define reusable queries against the stores state. These selectors decouple the component from the store and can encapsulate complex data manipulation. Selectors are conceptually similar to stored procedures. Define a feature selector to return a feature slice of state. Use a general selector to return any bit of state from the store by composing selector functions to navigate down the state tree. We now have strongly typed state and selectors to access that state. Up next, we'll strongly type our actions. But first, an optional homework assignment. In an earlier module, the homework assignment involved adding state for the masked user name checkbox. In this assignment, we'll add to that earlier work using techniques from this module. Your task is to add strong typing and selectors for the user feature. First, strongly type the user state to include the mask user name and current user. Next, build the associated selectors. Then modify the reducer to use the strongly typed state. And modify the login component to use the strongly type state and selector. The completed steps are in the APM-Demo2 folder in the same GitHub repo as the starter files. Note that this repo also has the code for the next module. Enjoy.
-
Strongly Typing Actions with Action Creators
Introduction
We've strongly typed our state, but what about our actions? Welcome back to Angular NgRx: Getting Started from Pluralsight. My name is Deborah Kurata and in this module we build action creators to strongly type our actions. We've all wanted to be an action hero, and with action creators we can do just that. We save the day by strongly typing our NgRx actions. With NgRx, it is the action that makes the application go. The user clicks something, we kick off an action. Need to load data, we kick off an action. The user updates a value and clicks Save, we kick off another action. The result of the action is often a change of state. Recall that an action is comprised of a type, which is defined with a string, and an optional payload, which is the data associated with the action. Neither the type or the payload are strongly typed here, so we get no help from the tooling and can easily make a hard to find spelling or typographical error. When the action is dispatched, its processed by a reducer. The reducer switches on the action type and uses the existing state and actions payload to create new stage. It then updates the store with that new state, setting the showProductCode property to true. Neither the action, the action type, nor the payload are strongly typed, again, making it too easy to make a mistake. Let's take a closer look at the state of our actions and potential issues with our current typeless approach. Here we are in VS Code with the APM sample application open. Let's start with our product reducer. We pass in the state and an action. Hovering over the state, we see that it is now strongly typed to our product feature interface. Hover over the action and it's still any, as is our action type. And here we have a hardcoded string, so easy to mistype, like this. No strong typing means no help with our typos. Looking at the product-list.component, here we dispatch the action, again, with a hardcoded string. Hovering over the dispatch, the action type property is defined as string and the payload as Boolean. This is using type inference, whereby TypeScript is inferring these types based on their assigned values. We could easily make a mistake here and pass in the wrong payload type. Now TypeScript things that the payload is a string. Let's see if it runs. No syntax errors, so nothing telling us about the mistakes we've just created. Viewing it in the browser, our Display Product Code checkbox defaults to true, as per the initialization code we added in the prior module. But the checkbox no longer toggles the product codes, they're stuck on. We can use the NgRx DevTools to help us resolve these issues. Open the tools and select one of the TOGGLE_PRODUCT_CODE actions. Looking at the Action details, we may notice our payload error here, but there is nothing that helps us with the typo in our reducer. Let's become an action here and save the day, or at least hours of debugging and frustration, by strongly typing our actions. In this module, we build action creators to strongly type our actions, and use those action creators to create and dispatch those actions. Then we walk through how to use actions and selectors to communicate between our components. Lastly, we examine how to define actions for more complex operations, such as loading data. Let's get started.
-
Building Action Creators
There are many benefits to strongly typing our actions. Strong typing helps prevent hard to find errors from misspellings or typos. Strong typing improves the tooling experience, making it easy to see the list of actions and select the appropriate action for a given purpose, and it results in a well-defined set of valid actions that the application can perform. This provides a level of documentation on what the application can do. So strong typing help us define a clear and clean set of actions that the tooling uses to help us better use those actions. There are several ways to strongly type our actions, including action factory functions. But we'll take advantage of the extra type safety and build action creators. Strongly typing actions using action creators involves three steps, define the action types as a set of named constants, build the action creators, and define a union type for those action creators. Hmm, what does this all mean? Let's walk through these steps. We begin by defining the action types we want a particular feature to provide. For the product feature, the user can toggle the display of the product codes. The user can select a product, making it the current product to display for edit. If the user deletes a product, we clear that current product selection, and if the user clicks to add a new product, we initialize the current product so it displays appropriate default values. Let's start with these four action types. Since action types are strings, we can define the set of valid action types using an enum. An enum is a TypeScript type that allows us to define a set of named constants. Using enums makes it easy to document intent, which is exactly what we want for our actions. This enum will ultimately provide the comprehensive list of all actions that can be performed against the product feature state. To make these enum constants available to the application, we export the enum. We could name it anything, but by convention we'll name it based on our feature, ProductActionTypes. We then clearly name each action type with an enum constant. The action constant name often begins with an action verb, such as toggle, set, clear, initialize, or load. We assign each action type name to an appropriate string. The strings show up in the DevTools, so following a good naming convention makes these actions easier to track. Here we begin each action string with the name of the slice of state that is effected by the action, then we specify the action type name, but with spaces to make it readily readable. You and your team may want to get more specific with these strings, including the name of the page or API that dispatches the action. That provides a better context of the event source and can make it even easier to use the DevTools to examine where the actions were dispatched. We'll stick with the shorter names for our simpler application. Our next step is to build an action creator for each action. An action creator expresses an action as a simple class with two properties, a type and a payload. Sound familiar? It's called an action creator because we use it to literally create the actions we dispatch. Expressing actions as classes makes it possible to strongly type the actions when we dispatch and process them in the application. Let's talk through this code. To ensure these action creator classes are available to other parts of the application, we export the class. By convention, we name the class the same name as the action type constant defined in the enum. To define the class as an action, we implement the Action interface provided by the NgRx store. Then we declare the two properties. We define the type property as read-only so it is never changed. We assign it to the associated enum constant. We define the payload property in the constructor. By specifying the accessibility here in the constructor, we take advantage of a TypeScript shortcut that declares a property and assigns that property in the constructor. So these two class definitions are the same, the first one is just shorter, and by defining the payload data type here, we enforce that type for this particular action. The last step to strongly typing our actions is to define a type that unions all of the action creator classes. We do this so we can limit the possible actions to only those defined by one of these creator classes. To expose this union type to the other parts of the application, we export it. Then we give it a name based on our feature. We assign it to the union of our action creator classes, using the pipe character to union the classes into one consolidated type, one type to rule them all. Now let's give this a try and strongly type our actions.
-
Demo: Building Action Creators
In this demo, we'll build action creators to strongly type our actions. We are back in VS Code. Recall the first step to building an action creator, we define our action types as a set of named constants, using an enum. The question, once again, is where? Where should we define our actions? Since there is quite a bit of code required to build the action creators, let's define them in their own file. Here in the state folder, we'll add a new file and call it product.actions.ts. In that file, we define an enum that lists all of our product actions as named constants. At this point, we are really only using one action, TogggleProductCode, so we define one constant, assigning it to a string that will appear in the DevTools. As we identify more action types for our feature over time, we add those types here. The next step is to build the action creator. The action creator represents the Action as a class with a type and payload. To define this class as an action, we implement the Action interface. We'll use the quick fix to add its associated import statement. Note that the quick fix provides many options, be sure to pick the one for the NgRx store. We now have a class that expresses exactly what our ToggleProductCode action should look like. We'll use this class to create that action. Lastly, we define a type to union all of our action creators. This union type is not very exciting at this point, since we only have one action creator and nothing to union it with. To better see what this will look like, let's add a few more actions. So what was our first step? We add the action types to the enum following our conventions, assigning an easily traceable string. What's next? That's right, we build action creators for each of the new actions. The SetCurrentProduct action updates the currently selected product in the store, so the payload is the currently selected product. We use the quick fix to add the product import. The ClearCurrentProduct action does not need a payload. The reducer for this action will set the currently selected product in the store to null, so we don't have to pass the null here. Since we don't have a payload, we could define a constructor with no parameter. Alternatively, we could leave off the constructor, since TypeScript provides an empty constructor for us by default. The InitializeCurrentProduct action is similar. The reducer for this action will define an initialized product so we don't need a payload. And our last step, yep, we union these new classes into our union type. This provides us with a single union type that represents all of our action creators. We use the pipe character to define the union. We read the pipe here as or, using this union type strongly types the actions to one created with the ToggleProductCode class or the SetCurrentProduct class or the ClearCurrentProduct class, and so on. Now let's see how to use these types and action creators.
-
Using Strongly Typed Actions
Recall that we are using our action in two places. In the component we dispatch the action to kick off our one-way data flow. And in the reducer we process the action update the state. Let's see how to add strong typing to our reducer first. Here is the current reducer code and here is the code with strongly typed actions. We use the union type we created to strongly type our action parameter. Recall that our union type defines all of the possible action creators. Using this type here ensures only actions with an action creator are passed in to this function. Since each action type is defined with our enum, we use an enum constant here now instead of a hardcoded string. Our action is now strongly typed and we can't make a spelling or typographical error without the compiler notifying us. How about dispatching our actions? Here is the code we currently have in our component to dispatch an action. We are manually creating the action object using a hardcoded string for the type and we can put literally anything in for our payload. Here is the code that creates the action for us using our action creators. Wow that looks different. Instead of passing in an object literal describing our action, we use the new keyword and create a new instance of our action creator class. This creates the action object. Let's remind ourselves what our action creator looks like. We don't specify the action type when we dispatch the action because it's already defined here in the class. When we create the new instance, we pass in the payload as defined by the class constructor. By using a class to create our action, instead of using an object literal, we ensure that the action is created correctly every time. Where is this productActions coming from? It's using the same name spacing technique that we used for our interfaces and selectors. Note that you don't have to use this name spacing technique, you can instead import each action individually from our actions file. Let's go back to the demo and give this a try.
-
Demo: Using Strongly Typed Actions
In this demo, we'll modify the reducer and component to use our strongly typed action types and action creators. We'll start in the reducer by strongly typing the reducer functions action parameter. We'll set its type to ProductActions and use the quick fix to add the import statement. This is our union type, defining all of the valid action creators for the product features. Our simple string here is no longer valid. It now expects one of our product action types. Type ProductActionTypes dot and we get the list of all valid action types. Wow. Select ToggleProductCode. Now our case statement is strongly typed. Let's add the cases for the other three actions we added. When the user selects a product, we update the state to reflect a new currentProduct. We return our existing state, spread to copy over all of its properties and the new currentProduct set to the action payload. One important thing to note here, we are passing a reference to our currentProduct into the store. That means if we update a property of the object in our component, we mutate the product in the store as well. To prevent this, we make a copy of the object here, using the spread operator. The code to clear the currentProduct is similar, but it sets the new currentProduct value to null. And the code to initialize the currentProduct sets the currentProduct to an object literal, defining an initialized product. This prepares a set of defaults when the user selects to add a new product. With that, our reducer case statements all take advantage of strong typing. Let's move on to the product-list.component. Here when the user checks the Display Product Code checkbox, an action is dispatched. To use our new action creators, we'll start with the import statement. We'll import * from our actions file and add a namespace so we can easily reference any of the classes in that file. Scrolling back down, we'll change the code that dispatches the action to use our new action creator. We delete the object literal and instead create a new instance of our action creator class. Notice that when we type our namespace, the list of possible actions are displayed, making it much easier to select and ensure we dispatch the appropriate action. We then see a syntax error. The strong typing is telling us that it is expecting a Boolean value to be passed into the constructor as the payload. We'll pass in the value, passed into this checkChanged method, which is the checked state of the checkbox. This code is now cleaner and easier to read. We dispatch a new ToggleProductCode action, passing along the checked value. Let's try it out in the browser. Our state starts as checked and the product codes appear. Uncheck and the product codes disappear. It works! Notice that our new action strings now appear in the DevTools. But what about the other action creators we build to manage the current product?
-
Using Actions and Selectors for Component Communication
So far, we've seen how to use NgRx to toggle a simple Boolean flag used by a single component. But NgRx can do so much more. We can use it to communicate data between multiple components. When the user selects a product, the product edit component wants to be notified, so it can display the appropriate product for edit. And the product-list.component must be notified so it can highlight the item in the list. How do we handle this communication the NgRx way? We use actions and selectors. Let's walk through an example. When the user selects a product, the product-list.component dispatches a SetCurrentProduct action. The reducer processes that action and updates the store with the currently selected product. The product edit and product list components subscribe through a selector to receive change notifications. When they receive the notification, they modify their local properties, Angular's change detection kicks in, and the views are updated with the selection. When the user clicks to save their changes, the save operation dispatches the SetCurrentProduct action again to set the updated product as the current product in the store. So we use our actions and selectors to communicate the current product selection to multiple components. We can use this technique for simple and complex component communication. The flow is similar for the delete. If the user clicks the Delete button, the product is deleted and the delete operation dispatches a ClearCurrentProduct action. The reducer processes that action and sets the currentProduct to null. The product edit and product list components are notified and their views update accordingly. If the user clicks the Add button, the product list component dispatches an InitializeCurrentProduct action. The reducer processes that action and updates the store with an initialized product. The product edit and product list components are notified and the view is updated with default values, ready for data entry. When the user clicks to save the new product, the save operation dispatches the SetCurrentProduct action to set the new product as the current product in the store. Let's do it. In this demo, we'll use actions and selectors to communicate information between our components. Here is the product-list.component. Currently when the user selects a product from the list, this method uses a service to retain the selected product. Let's dispatch an action instead. We call this.store.dispatch and dispatch the SetCurrentProduct action, passing in the selected product as the payload. That was easy. Now when the user selects a product, we dispatch the SetCurrentProduct action and retain the current product in the store. Currently when the user clicks Add to create a new product, the newProduct method uses the same service to initialize a product and set it as the selected product. Let's, again, dispatch an action instead. We use this.store.dispatch and this time we dispatch the InitializeCurrentProduct action. This action sets the current product in the store to an initialized product, ready for data entry. Now that we are dispatching actions, we want to subscribe to the current product selector, so we receive change notifications. The product-list.component receives notifications so it can highlight the current product in the list. We often subscribe to our selectors in the ngOnInit method. Notice that we already have code here that is watching for change notifications from the service. We are no longer providing the selected product to the service, so let's modify it to use our selector instead. We replace the beginning of this line with this.store.pipe and use the select operator. We want a selector from our product slice of state, so we specify the namespace we defined fromProduct. We then get a list of all of our selectors. To watch for changes to the current product, we pick getCurrentProduct. For clarity, let's change the argument here as well. Now when the currentProduct changes, this code sets the local selectedProduct property to the current selected product from the store. And let's add a TODO comment here so we remember to unsubscribe. We'll talk more about unsubscribe strategies later in this course. For now, let's delete the subscription and the unsubscribe. Our product-list.component now receives notifications whenever the currentProduct changes, so it can highlight the selected product. But we also want to notify the product-edit.component. Let's look at that next.
-
Demo: Communicating with the Edit Component
In this demo, we extend the work we did from the last demo using actions and selectors to communicate with our product-edit.component. We have the product-edit.component open. Since this is the first time we are using the store from this component, we need the import statements. We import the store, the select operator, our selector functions, and our actions. While we're here, let's delete this subscription. Next, we inject the store into the constructor. We need product information, so we specify the extended state interface from our product reducer for the generic argument. Now we can modify the ngOnInit to watch for changes from the store, just like we did in the product-list.component. We'll change this to this.store.pipe and use the select operator. We again specify our fromProduct namespace and get a list of all of our selectors. To watch for changes to the currentProduct, pick getCurrentProduct. And for clarity, let's, again, change the argument here. We'll add TODO comment so we don't forget to unsubscribe, and let's delete the current unsubscribe. Our product-edit.component now receives notifications whenever the currentProduct changes, so it can display that product for edit. This edit component also programmatically changes the currently selected product in several places, as we discussed in the prior clip. Down here, when the user deletes a product, we need to ensure no product is shown as selected in the product-list.component. So we change the currentProduct to null in the store by dispatching an action. We call this.store.dispatch to dispatch the ClearCurrentProduct action. The logic in this code performs a delete if the product has an assigned ID. If the ID is 0, the product was created and deleted before it was ever saved, so we dispatch the ClearCurrentProduct action here as well. When the user selects to save, this code checks the products ID. If the ID is 0, we create a new product, otherwise we save any changes to the existing product. In both cases, we want to set the current product in the store to the saved product so it appears selected in the list. We call this.store.dispatch and dispatch the SetCurrentProduct action here on a create, and here on a update. The current product in the store will then always reflect the saved changes. We'll talk more about this code later in this course. We now have some code in our service that we are no longer using. The original implementation of this service used a BehaviorSubject, which is a special type of RxJS observable. The BehaviorSubject retains the last selected value and broadcasts change notifications to any subscribers. We won't spend time walking through this existing implementation. If you are interested in more information about using BehaviorSubject for component communication, check out the Angular Component Communications course here in the Pluralsight library. That course walks through building this service. We are no longer using this behavior subject since we are now using actions and selectors instead. To clean things up, let's remove the unused code now from this service. Delete the BehaviorSubject and its observable, the associated import, the changeSelectedProduct method, which broadcasts change notifications. And since our reducer is now initializing the product, delete the newProduct method as well. Let's see if it still works. Bring up the browser, ensure you have the DevTools open, and select to watch the state. The Display Product Code checkbox still works. We see its action dispatched here and its state change here. Click on a product and it appears in the edit page. Yay! It also appears selected in the product list. We see its action here, and state here. Update the productName and click Save. The currentProduct is updated with the new name. Delete a product and the currentProduct is cleared in the list. We can see its action here. And trying out the Add, the currentProduct is updated with the initialized values, and its action appears here. It works. Congratulations, you are now an action hero. Ready to take on the action challenge for more complex operations?
-
Defining Actions for Complex Operations
The actions we've dispatched so far support simple operations. We toggle a Boolean property and set, clear, or initialize the current selected product. Sometimes our actions invoke more complex operations, such as calling a backend server to load, update, create, or delete entities. When defining an action for a more complex operation, a single action is often not enough. We kick off the complex operation and set up actions to respond based on the result of that operation. Hmmm, let's walk through the load operation as an example to clarify this. When a component is initialized, such as our product-list.component, we retrieve the products for display from a backend server. Performing this operation the NgRx way means that the component dispatches a Load action. This action has no payload, as it is simply a request to load the data. the reducer processes this action, but since it has no data, it does not generate new state. Rather, it calls our product data service to issue an http get request to get the data. But recall that our reducer should be a pure function with no side effects. We'll deal with that in a moment. Since the HTTP request is asynchronous, at some later point in time, the data is returned from the server. And then what? We dispatch another action. A LoadSuccess action with a payload containing the retrieved data. The reducer processes this action and the existing state to create new state and updates the store. The store then retains the full list of products. The component subscribes to this store through a selector, watching for changes to the list of products. It sees the change and updates its local property. Angular's change detection kicks in and the products appear in the list. And what happens if there's an error retrieving the data from the server? We have should have an action for that too. For complex operations, we often define three actions, one for the operation itself, one of successful completion of the operation, and one for an error or failure. Let's give it a try.
-
Demo: Defining Actions for Complex Operations
In this demo, we define an action for a complex operation, namely the Load operation. We are back in VS Code with the product.reducer open. Notice that we already have a property defined here in our product state interface for our array of products. We want an action to load the data for this array. Switching over to the product actions we build action creators for the Load, LoadSuccess, and LoadFail actions. Recall that building an action creator requires three steps. Define an action type constant in this enum, build a class that is our action creator, and define a union type for the action creators. Since we've already gone through this process and you have an example here to work with, consider trying this yourself. Pause the video and add the three action creators following these steps. Are you ready to see my solution? Our first step was to add our three action types to our enum, following our pattern. Then we build an action creator for each action type. The Load has no payload, since it has no data. The LoadSuccess has the returned array of products, so we pass that as the payload. And for the LoadFail, we'll pass a string as the payload to specify an error message. Lastly, we add these new action creators to our union type. Is that similar to what you came up with? Now that we have the actions for our complex operation, how do we use them and perform that complex operation? Well that's a question for the next module. For now, let's finish up this module with some checklists for strongly typing our actions.
-
Checklists and Summary
Strong typing an action requires three steps. First we define the action types. One good way to define the action types is to use an enum and specify each type as a named constant. Be sure to specify clear action type strings, as these are used by the DevTools when displaying the dispatched actions. The second step to strongly typing an action is to build an action creator. An action creator is a class with properties for the action type and payload. We use the action creator when dispatching the action. Here we create we create a new instance of the action creator, passing true into the constructor as the payload. The last step to strongly typing an action is to union the action creators. Define a union type of all action creators. This represents all of the possible actions for our feature. We use this union type in the reducer to limit processed actions to those in that union type. For more complex operations, we may need to define an action to kick off the operation and additional success and fail actions to handle the result of that operation. Next up, let's see how to implement complex operations with NgRx effects. But first, an optional homework assignment. The prior homework assignments involved adding state for the applications user feature. In this assignment, we'll add to that earlier work using techniques from this module. Your task is to add strong typing for the user feature actions. First, create a user.actions.ts file, then add an enum to define the action type. We only have one at this point. Next, build the associated action creator and build the union type to union our single action creator. Then modify the reducer to use the union type and modify the login component to use the action creator. The completed steps are in the APM-Demo2 folder in the same GitHub repo as the starter files. Enjoy!
-
Working with Effects
Introduction
Sometimes our actions require asynchronous or other operations with side effects, such as retrieving or setting data to a backend server. Welcome back to Angular NgRx: Getting Started from Pluralsight. My name is Duncan Hunter and in this module we examine the Effects library from NgRx. Predictable cause and effect in our Angular applications can be achieved by writing side effect free code, but some of our code will have side effects by design, and this is where the NgRx Effects library can help us. In this module, we'll look at what NgRx effects are and why use them. We'll install the @ngrx/effects package. We'll define an effect, step by step, register an effect, use effects by dispatching actions acted on by effects, look at how to unsubscribe from observables, and discuss exception handling in effects. Let's get started.
-
What Are Effects?
Operations in our application may have side effects. In this context, a side effect is an operation that depends on or interacts with an external source, such as external state, devices, or an API. So using HTTP to access a backend server is an example of a side effect. Rather than having the logic to manage these side effects in your components, you can use NgRx's architecture to handle such code, to keep your components pure. Most applications need to retrieve data. At first glance, we may look at our existing code and think we can simply inject the store and the services into the constructor, then dispatch an action after the service retrieves the products. But in NgRx components are not the best place to manage code with side effects, like our product services HTTP requests here, as we want to keep our components pure. We do not want to handle side effects in reducers either, as their pure functions you can't modify state here in an async callback, in fact, you can't even dispatch actions from a reducer when you do get a response back because reducers are designed to be pure functions, not classes, so there's no constructor to inject the store into to be able to dispatch an action. We could get an instance of the store by other means, but it's not the reducer's role and it wouldn't be conventional, so this won't work and is a bad idea, and you break the purity of the store reducer. So the best place to side effects is in an effect, which work by taking in an action, doing some work, and dispatching a new action, often a success or a fail action. Let's review our NgRx pattern diagram again, but this time we will use effects to deal with the async side effect operation of fetching products from our server. The component dispatches an action on initialization with a type of load and no payload because we don't need to call our server with any parameters to get all products. We now have two places in our application we could be listening for this action, either in a reducer or an effect. In this case, it's our effect that we configure to listen for the load action after the action goes via the reducer. As we know, async requests have side effects and we can't do async work in our reducers. Remember, our effects job is to take in actions, do some work, and dispatch other actions. In this case, the work is getting the products from our server, so the effect talks with our Angular product service that makes a HTTP request to our server to get the products. Let's just tackle the successful response in this example and discuss errors later. So imagine the server responds successfully and passes an array of products to the service, the service then passes the products to the effect that dispatches another action. This time a LoadSuccess action with the Products array as its payload. The reducer takes that action with the array of products and existing state to create new state with the added products. We can then use our get products selector to subscribe to state changes in our component and pass the Products array to the view. Before we dive into defining an effect in code, let's review why you would want to use effects in the first place. Effects keep your components pure by removing the code that deals with side effects. Effects help isolate side effects into a central place, and effects make it easier to test side effects in isolation from the components that use them.
-
Defining an Effect
When first learning NgRx effects, many people find the complex observable streams and multiple chained RxJS operators a real handful. I know I did. So let's take the time to step through our first effect piece by piece and look at how they work. An effect is a type of Angular service, so at its core it should just look like any other Angular service with that injectable decorator on top of a TypeScript class, except by convention, we'll call it ProductEffects versus ProductService. In the constructor, we inject the Actions observable from the NgRx library, which emits an action every time one is dispatched in our application. We also inject the ProductService to do the work of fetching our products from the server. We then create an effect by making a variable, we will call it loadProducts$, and registering it with NgRx as an effect by adding an effect decorator on top of it, which comes from the NgRx Effects library. The dollar suffix is an optional community convention to show that this variable is an observable, to be very clear about when you're dealing with the observable or the observables value. It's just syntactic and completely optional, so it's up to you if you want to follow this convention. To listen to all actions in our application, we can use the actions$ observable we injected into our effects constructor. However, we need a way to filter out the actions we are not interested in, except for our load action. We can do this with an RxJS operator that comes from NgRx called ofType, which accepts the action type names you want to listen for. We start with a pipe, which is the preferred method for using operators in RxJS 6+, passing in the ofType operator and configuring it to listen for our Load action, which we can access from our ProductActionTypes enum in our productActions file. Now whenever the Load action is dispatched in our application, it will pass through the ofType operator and then we can use the mergeMap operator. MergeMap maps over every emitted action calling Angular services who return observables, then merges these observables into a single stream. So next we call our injected productService's getProducts method, which will call our server and return an array of products or an error. We'll talk more about the error scenario later. As our service returns an observable of products, we'll need to use another pipe with a map operator to map over the emitted products array and return a LoadSuccess action with the products as its payload. We still need to configure our products reducer to have a case statement to listen for this LoadSuccess action and update the store with the fetched products, but we'll get to that shortly. We've now looked at all the steps to make our first effect, and this might seem like a lot to learn, but really it boils down to effects taking actions, do some work, like calling a service, and dispatching new actions.
-
Demo: Defining an Effect
Now that we have looked at what effects are, let's jump into a demo and install and define our first effect. We're back in Visual Studio Code with the APM sample application open and we'll press Command+J to open up the terminal and in the terminal we can install effects with npm by typing npm install @ngrx/effects. This installs the effects package into our application. You may again see some warnings, you can likely ignore for now. When the installation is finished, you'll see the package version logged to the console and now the effects package is installed and ready to be used in our application. Next press Command+J to hide the terminal and give us a little bit more room and navigate through our explorer to our products module state folder and make a new file called product.effects.ts. Pressing Command+B to close the explorer, we're going to use a code snippet to make an Angular service, but by convention we'll name it ProductEffects versus ProductService. In the constructor, let's inject and register a private variable called actions$ of type Actions, which is not giving us the correct auto import path, so I'll change this to /effects. Also, we'll import and register another private variable called productService of type ProductService and let that auto import above. Next we'll use an @Effect decorator from NgRx to register a loadProducts$ observable as an effect with NgRx. First off, we want to listen for all actions being dispatched in our application via the injected this.actions$ observable and use an RxJS pipe to pass in our first operator. The ofType operator that we can get from NgRx effects and pass in a string of the action types we want to filter on. So we are going to use imports * as product actions from prdouctAction.actions as a namespace to access everything that's exported from our productActions file. Hanging off this is our productActions type enum, and from this we can choose the load action type we want to filter on. Next we'll use the mergeMap operator to map over the emitted actions and return the result of calling our injected projectService's getProducts method, using another pipe to pass in a map operator to map over the results and get the products, which will be of type Products array. We will then return a new productActions.Load productSuccess action passing in the products as the payload. Press Alt+Shift+F to order format and clean up the page a little bit, and now we've defined our first NgRx effect. At the moment we are dispatching a LoadSuccess action, but we've not configured our product reducer to have a case statement to listen for this action. Pressing Command+P to open up the command palette and searching for the product reducer. In our reducer function, I'll add another case statement using our ProductActionTypes enum to find our LoadSuccess action type and return a new object. And similar to before, I'll spread the current state and set the products variable to the actions payload. Now we've defined our first effect and configured our reducer to listen to its dispatched LoadSuccess action, let's take a minute to clean up our product service that currently holds onto the state of our Products array. We no longer need our service to hold onto any shared product state, as this is now the job of our NgRx store. Pressing Command+P and navigating to our Product service, we can see we have a local variable called products that we are setting down here in our get Product method by tapping into the observable stream of products returned from our server. Now we have NgRx to manage our products state, let's go through the service and remove all the logic that's associated with our services, this.products variable. Pressing Command+F and searching for this.products, we can see that it's in use in 8 places. So let's get rid of it. Starting at the top, let's get rid of our products variable, pressing Command+Shift+K to remove the line, then moving down to remove this if block previously checking the value of the removed products variable, and then remove this tap operator on line 21 where we were initializing the products value. We are also using the removed variable in our create method, so let's remove its tap operator, and the same again in the delete method, and also our update method. Now we've gone and broken the ability to update, delete, and add products because we have not set up any update operations with NgRx. But don't worry, we'll do this in the next module.
-
SwitchMap Versus MergeMap
Often your effects will be about making HTTP requests that return observables. Depending on the operator you choose, you might cancel in-flight requests or make requests out of order if new actions are dispatched against the same effect. We used an RxJS merge map operator in our effect to map over our current actions observable and merge any inner observables returned from calling our Angular service into a single observable stream, which most of the time will likely be the operator you want, but not always. We could have used a switchMap operator, which you'll see in a lot of demo code. But choosing the wrong operator here can be a source of unexpected race conditions. So let's walk through the operators you have to choose from. You'll use the switchMap operator least often in your effects because it cancels the current subscription if a new value is emitted. This means if someone dispatches, for example, a second save product action before the first save product action's HTTP request returns to your effect, the first in-flight HTTP request will be canceled and the product might not get saved, leading to potential race conditions. So use the switchMap operator for get requests or cancelable requests, like searches in a type ahead. ConcatMap runs HTTP requests in order and will wait for the last request to finish before starting the next, which is less performant, but the safest. So use concatMap for get, post, and put requests when order is important. MergeMap runs HTTP requests in parallel and is more performant than concatMap, but doesn't guarantee order. So you use mergeMap for put, post, and delete methods when the order is not important. ExhaustMap will ignore all subsequent actions dispatched until the current request completes. Use exhaustMap, for example, during logins when you do not want to make or queue up anymore requests until the initial one is complete.
-
Registering an Effect
Initializing the effects library in an application is similar to initializing the store, but rather than passing in reducers, we pass in an array of effects. In our root app module, we use the EffectsModule.forRoot method, and here we are passing in an empty array because we currently have no effects we want to register on app load. We will lazily load product effects when we load our ProductModule. Just like registering reducers for our product feature module, we use the EffectsModule's forFeature method to register our ProductEffects with NgRx. Let's dive into a demo where we initialize the effects module and register our product effects. We are back in Visual Studio Code, pressing Command+P to open up the command palette. We can search and navigate to our app.module. In our imports array, next to our StoreModule, we can type in EffectsModule.forRoot and pass in an empty array. That is all we need to do in the app module to add NgRx effects to our application. But we need to register our product effects over in our product feature module. I'll press Command+2 to open another panel in VS Code so we can see both files side by side, pressing Command+P and searching for product module. We can navigate down to its array and add the EffectsModule.forFeature and pass in an array literal with our product effects that has been auto imported for us by clicking Enter. And now we've made and registered our product effect, it's time to use it in our components.
-
Using an Effect
Before using NgRx effects, we would be used to doing this, communicating directly with our Angular services from our components. Whenever we made a request to load data, we would get back a response from the same method we used to make the request. This might seem like a small difference, but it's important to wrap your head around the fact that now the process of dispatching an action is separate from selecting and getting the data. To use our effects, we just dispatch an action from our product-list.component by injecting the store into the constructor of the component, then we dispatch the action using the store's dispatch method, passing along a created action. This would be acted on by our effect. Finally, we will need to swap out our call to the Angular service to getProducts, with a subscription to the store, selecting our products with a getProducts selector. Once we have finished this step, we'll be able to see our effects in action via our DevTools, showing the Load and LoadSuccess action and we'll also be able to see our loaded products in the store state. Let's dive into a demo where we use our first effect. We're back in Visual Studio Code pressing Command+P to open up the command palette and searching for the product-list.component. As you can see, here we are using our productService to get and set our component's product variable to the returned value. Now we want to separate out this getting and setting the products into dispatching a load action and listening to the store with a getProducts selector. I'm going to comment out this section and the first thing we'll do is use an already injected store to call this.store.dispatch and pass in a new productActions.Load action. This will be picked up by our effect and if we successfully get products from our server, these will be added to the store. Next we listen to the store and select this product state. So I'll use the store again by typing this.store, but this time I'll use a pipe to pass in a select operator with a getProducts selector. Then we can subscribe to this observable, which will give us an array of products of type Product array as the return value, and then we can initialize our this.products variable to the returned products. Over in the browser, with our DevTools open, we can navigate to our product list page and we can see our Load action was dispatched. We can also see our Load Success action was dispatched from our effect and our store state now has the loaded products over here on the right.
-
Unsubscribing from Observables
You need to unsubscribe from the store. Unlike the HTTP router and forms observables that Angular will unmanage, unsubscribing for you. Currently here in our ProductListComponent, we are subscribing to the store to get the product state. To avoid a creating a possible memory leak by not unsubscribing, let's use a common unsubscribe strategy in Angular, which is to use the takeWhile operator. We want to keep subscribing to this store and take while our component is still active. To do this, let's initialize a property called componentActive to true at the top of our component, then in an Angular ngOnDestory lifecycle hook, which will be fired when the component is destroyed, we can change this variable to false. This allows us to say take while or keep subscribing until the component is destroyed. We can then use a pipe before our subscribe block to add a takeWhile operator and test if our componentActive predicate is true. If not, the subscription will be unsubscribed from. There are other RxJS operators you may want to check out, like takeFirst and takeUntil, which have a similar effect, but it's beyond the scope of this course to cover all of them. You may be looking at this going, hmm, that is a pain to have to add this unsubscribe code to every piece of state I subscribe to. This is where Angular's async pipe can help, as it automatically subscribes and unsubscribes to observables for you. In our Product List Component, rather than subscribe to the product state, we can leave it as an observable of product and use it to initialize a product$ variable to use in our template. In the Product List View, we use the async pipe to subscribe to the product$ observable and pass the emitted products to the ngFor. The beauty of this approach is now Angular will handle the subscribing and unsubscribing automatically for you. So when should you use the async pipe versus subscribing in the component class? Subscribe in the component when you need the observable's value in the component class, so to work with it or to change the value before using it in the template. If you do not need to do anything but subscribe and use the value in the template, then it's much easier to use an async pipe. If you don't need the value in the component, then technically it's just a syntactic difference and it's up to you which you'd prefer, as long as you unsubscribe. Personally, I find when using NgRx you have a lot of small pieces of state to subscribe to and our code is a lot easier to read with async pipes. Let's update our demo to ensure we unsubscribe from the store and change over to using the async pipe to automatically manage unsubscribing for us. Back in the Visual Studio Code, we're on the product-list.component. In our ngOnDestory method, I'll make a new variable called this.componentActive and ste it to false. Then use VS Code's quick fix, pressing Command+. to auto declare the property on the class. But let's change our componentActive to be initialized to true when the component's loaded. With this done, we can come down to where we are selecting the products from the store and add a new line before the subscribe method. We add a takeWhile operator and pass it a function that returns a componentActive predicate. And we're done. And as long as the componentActive property is true, which we set when we load the component, and turn to false when we destroy the component, we'll get updates from the store until the ngOnDestroy method is fired and we'll unsubscribe. Let's change this now to use an async pipe so we don't have to manage unsubscribing from the store, pressing Command+Shift+K to delete these two lines. We will make a new property called products$ and set it to the value of the observable of products in the store. Again, using the quick fix in Visual Studio Code, I'll auto declare the property and import the missing observable symbol from RxJS. Hopping over to the template for the component, you can see we have this ngIf hiding this piece of HTML until the products are loaded. For now, I'm just going to comment this out and come back to it in a second. Down here in the ngFor where we are using our products array, which we changed to be an observable of Products array, I'm going to add an async pipe to do the subscribing and unsubscribing for us. This works and is perfectly valid, but what about this ngIf above and how do we use that with an async pipe? Well we can also use the async pipe with an ng if using the as syntax, that allows us to subscribe and set a local template reference to the value of the observable. Changing our ngFor back to not use an async pipe, I'm going to come back up to the ngIf and add the async pipe using the products$ observable, but this time use an as products syntax to set a local variable we can use inside of the template. This can be extremely handy for these scenarios where we want to hide the HTML until we get a value from the observable.
-
Exception Handling in Effects
Currently we have no error handling in our effect, and if our server returns an error we need a way to dispatch and respond to a LoadFail action. Looking at our effect, we can see that we are calling our prdouctService and mapping over the result and dispatching a LoadSuccess action. To listen for errors, we can use another operator called catchError. If our productService returns an error, we can get a hold of it here and return a LoadFail action with a payload of the error. The catchError operator does not automatically return an observable back into the stream, like our map operator, so we can use the observable of to create an observable from our LoadFail action and return that. Now we need to respond to this LoadFail action in our product reducer. We've not added any product state properties to be able to store an error in the state, so first we add to the ProductState interface in our product reducer file an error property of type string. Then we initialize its value in the initialState object to an empty string. After this, we can make a new selector called getError to select this error state property from the store. Finally, we'll need to add another case statement to our product reducer for our LoadFail action, setting the products to an empty array and adding the actions payload with the error to our error state property. Let's make it happen and add exception handling to our demo app. Back in Visual Studio Code, pressing Command+P and we can navigate to our product effects file. In here, after our map operator, we can add a catchError operator, which will pass us any emitted errors from our productService. We can make a new observable using the observable of and making sure we choose the right import statement and passing in the new productActions.LoadFail action with a payload of the error. Now we're dispatching a fail action, let's go up there to our product reducer to listen for the LoadFail action. Pressing Command+P, we can navigate to our product reducer. In our product state interface, I'm going to add a new error property of type string and initialize it in our initial state object to be an empty string. To select error state, we need to make a new selector by exporting a const called getError and using a createSelector method, passing in our getProductFeatureState selector and a function returning state.error. At the bottom of our reducer functions switch statement, I'm going to add a new case statement for our LoadFail action and return a new product state object, first spreading the existing state and then making the products array empty so we don't end up with any stale data if we have an error, and setting the error state to be the error string in our action.payload. Now we have a piece of state to hold onto any error as a string, let's make sure we clear the error property in our LoadSuccess case statement here. In case we have a stale error in the UI, now redundant after a subsequent getProducts request was successful. And that's all we need to do to be able to listen for errors in our effect and to update the store with an error property. But to be able to notify the user of an error, we need to navigate over to our product-list.component and in our ngOnIt, add an errorMessage$ observable and set its value to the result of calling this.store.pipe and passing in a select operator, and then we can pass in our new getError selector. Then we can auto declare the property, which we can check was made correctly as an observable of string. Over in the components template, at the bottom, we can swap out our ngIf using the old subscribe to error message variable for the errorMessage$ observable using the async pipe as syntax to set the result to a local property of errorMessage. Over in the browser, the app is still running fine, but we need a way to force an error, so I'll go back to Visual Studio Code and open up our product service and change our product URL endpoint to be purposely incorrect with a bunch of z's. Back over in the browser, we can see in the DevTools that our Load Fail action was dispatched and the HTML is showing our error message. Before we forget, let's go back to our service and change the error back to what it was and ensure that our browser is still running and everything's working as we expect. Now let's finish up with a quick checklist and some operational homework.
-
Checklists and Summary
There is a basic set of steps to use NgRx effects in your applications. First, npm install the @ngrx/effects package. Build an effect to process an action and dispatch the success and fail actions. Initialize the effects module in the root module. Register feature module effects, and finally process the success and fail actions in the reducer. Following these steps can help you implement NgRx effects. Up next, we'll examine how to perform an update operation using effects. But first, let's look at an optional homework assignment. We've still not unsubscribed from the store in all of our components. To ensure that we properly unsubscribe from all our subscriptions, try these steps. Identify all places in the app where we are subscribing to the store. Hint, there are some TODOs to unsubscribe in our code. Add an OnDestroy lifecycle hook to the component. Initialize a componentActive flag to true on component load. Convert the componentActive flag to false in the ngOnDestroy method and add a takeWhile operator before the subscribe method and use the componentActive property as the predicate for the operator. You can find the finished homework exercise in the APM-Demo3 folder in the GitHub repo.
-
Performing Update Operations
Introduction
Most applications allow the user to add, update, or delete entity data. Welcome back to Angular NgRx: Getting Started from Pluralsight. My name is Deborah Kurata and in this module we examine how to perform update operations with NgRx. Sometimes it only takes a small change to have a big impact. So it's often important to allow the user to make changes to the application's data. Here the user clicks Add to add a new product or selects a product to modify its properties, or clicks Delete to remove the product. Each of these operations requires a set of actions and effect to perform the operation and reducer code to process the operation's success and fail actions, replacing the state and the store with new state. Our components subscribe through selectors to watch for state changes and the view updates to reflect those changes. Our objective in this module is to implement NgRx, start to finish, for one operation, the update operation. In this module, we start by identifying the state and actions for our operation. We strongly type the state with an interface and build selectors and strongly type the actions by building action creators. We dispatch an action for the operation and build the effect to process that action and perform the operation. Then we modify our reducer to process the operation's success and fail actions. Let's get started.
-
Identifying the State and Actions
Every task should start with a clearly defined goal. Without a goal, the task is difficult to implement and is challenging to determine if we met our objectives. In this case, our goal is to update a product. To meet that goal, we allow the user to select a product, edit its properties, and save the changes to the backend server, or cancel the changes, then we display the updated product in the list. Now that we've defined our goal, let's identify the state required to accomplish this goal. At first glance, it appears we need two properties to implement the update operation, the list of products so the user can select which product to update, and the CurrentProduct. It is this currentProduct that is displayed in the edit form. We should also have an error string to hold any error information returned from the update operation. With the state defined, let's walk through the update process and define our actions. It is the actions that make our application go. To perform an operation, we kick it off with an action. Since an update operation uses HTTP to save the data to the backend server, the operation is asynchronous. As we learned earlier in this course, asynchronous operations require a set of actions. When the user edits a product's properties and clicks Save, the component dispatches the UpdateProduct action to kick off the update. This executes the effect and issues the HTTP put request to save the user's changes. At some later point in time, the server returns a response. The effect dispatches the UpdateProductSuccess action to process a success response or the UpdateProductFail action to process a failure. The reducer takes that action with the updated product and the existing state to create new state. When creating the new state, it is important to consider the structure of our existing state. Notice that we are retaining the product in two places in the store, in the products array and in the currentProduct property. When processing the UpdateProductSuccess action, we would need to update the product in the array of products and update the currentProduct. To minimize duplication and prevent the possibility of a mismatch in the store between the data in the array and the currentProduct, let's consider changing the currentProduct property to a currentProductId and store the Id instead of the product object. That way, there is only one source of truth for a product's properties in the store. Continuing with our flow, the reducer then replaces the state in the store with the new state, updating the product in the array. Any components that are watching for state changes through a selector are then notified and Angular's change detection updates the view to reflect the changed product. Now we've identified our set of actions and our state, let's implement them, starting with the state.
-
Strongly Typing the State and Building Selectors
To implement the state for an operation, we define an interface for each bit of state, set an appropriate initial value, and build selectors so our components can access the state without knowing the structure of the store. Let's do it. In this demo, we define the strongly typed state required for the update operation. We are back in VS Code with the APM sample application open. Looking at the product reducer, we see that we already have our interface defined, but let's change our currentProduct property to an Id, that way we won't have two copies of the currentProduct's properties in the store. Let's specify the type of our Id as a number or null, that way we can set it to null when there is no currentProduct selected. We then change the property name here where we set the initial values for the state. We'll leave a value of null since no product is initially selected. Scrolling down, notice that we now have a syntax error in our selector. This points out two benefits of using selectors, first, since everything is strongly typed, we immediately know we have an error. Second, since the components access the currentProduct through this selector, we can easily change the code here without impacting the components. Let's create a selector to provide the currentProductId. Note that the selector must be physically above the getCurrentProduct selector because we'll reference it here. Then we change the getCurrentProduct selector to get the product by its Id. We'll use the composition technique discussed earlier in this course to compose our selector. I'll paste the code and we'll talk through it. We compose the selector from the getProductFeatureState selector, which provides the product slice of state, and the getCurrentProductId selector, which provides the Id of the currently selected product. We start by checking for a product Id of 0, if the product has an Id of 0, we assume it is a new product that has not yet had an Id defined. We then return an object with appropriate default values. This provides support for the product create operation. We have similar code in our reducer function that we'll delete in a moment. If the Id is not 0, we use the JavaScript ternary operator to check the currentProductId. If it is not null, we use the array find method to find the product in the list and return it. If the Id is null, we simply return null. This indicates that no product is currently selected. Scrolling down a bit further, we have a few additional syntax errors to fix in the reducer function. First, when setting the current product, we now set the Id instead. Since the Id is a number and a number is a value type, not a reference type, we don't need to spread an object. We simply assign the currentProductId property to the Id provided in the payload. When clearing the currentProduct, we set the Id to null. In our original implementation, we retained the initialized product in the store. Now that our selector provides the initialized product, we don't need this code. Rather, when initializing a product, we set the currentProductId to 0 to denote a new product and the selector handles the rest. Let's see if the application still works. Bring up the browser, click on a product, and the product appears for edit on the right. Click the Add button and the edit page displays the default values ready for entry of a new product. Note that the Save doesn't work at this point, we haven't yet finished our implementation. Next, let's strongly type our new update actions.
-
Strongly Typing the Actions with Action Creators
To define strongly typed actions for our update operation, we'll define the action types as named constants in an enum, build the action creators, and define a type that unions the action creators. This code should look familiar, as we've created actions similar to this earlier in this course. Let's jump back into a demo. In this demo, we'll define the strongly typed actions for the update operation. Here is our current set of actions. We start by adding the update action types as constants in this enum. Next, we build the set of action creators, one for each of our new actions. For the UpdateProduct creator, we specify a payload of product to pass in the updated product. We'll pass this product along to the effect to save the updates to the server. In the UpdateProductSuccess creator, we also specify a payload of product. This is the product that is returned from the update operation and used to replace the item in the array of products. The UpdateProductFail creator specifies a payload of string for an error message. Lastly, we add these action creators to our union type, this ensures that our reducer accepts our new actions. Wow, quite a bit of work for a single operation. All this code is why you may have heard some developers lament about the amount of boilerplate required for NgRx. Luckily there are ways to reduce the amount of code you write. We'll talk about those in a later module of this course. For now, writing this code by hand is useful as you practice and enhance your skills with NgRx. Now we are ready to dispatch our update action.
-
Dispatching an Action
We provide a form in the product-edit.component for the user to edit a product. In Angular there are two ways to implement a form, template-driven and reactive. Template-driven forms leverages two-way binding with ngModel. Two-way binding binds the user's entry to our model, directly and immediately updating our state as the user makes changes. This goes against the NgRx principle of immutable data. When using NgRx, we instead use reactive forms, whereby the user's entries are stored in a form group structure. When the user clicks the Save button, we dispatch an action to save those values. For more information on reactive forms, check out the Angular Reactive Forms course here on Pluralsight. To dispatch an action from a component, we first inject the store into the constructor of that component. Then we dispatch the action using the store's dispatch method, passing along a created action. We create the action to pass into the dispatch method using the action creator. First we import * from our productActions and optionally use the as keyword to define a namespace. Then we specify the new keyword to create an action using our action creator. We pass in the updated product. Let's make it so. In this demo, we dispatch an action to kick off the process that saves an updated product to our backend server. Here is our product-edit.component. We define the reactive form group data structure here in the ngOnInit. Down here, when the user clicks the Save button, the event binding calls the saveProduct method. In this method, we first check the form's validity, using the valid flag built in to the reactive form group data structure. If the form is valid, we determine if it's been changed by checking the built-in dirty flag. We don't save it if it hasn't been changed. Next, we create a new product, p, using the spread syntax. We first spread the original product to copy its values, then we spread the built-in value object to copy over the user's changes from the form group data structure. We can't just use productForm.value because we have some product data that is not displayed on our form, such as the product Id. Then we check the product If, if it is 0, we call our productService to create a new product. Otherwise, we call the productService to update the product. Since we are working on the update operation, now instead of calling the productService directly here to save the product, we'll dispatch our UpdateProduct action and pass in our updated product. That looks nice. You'll make a similar change here when you implement the createProduct action in the homework assignment. We can't try this out yet because we don't have the effect to process this action. Let's build that next.
-
Building the Effect
We went through in detail how to build an effect in the last module. First we build a service for the feature effects and inject actions into its constructor. Then we specify a property with the effect decorator. This identifies the property as an effect. Next we build out the effect. In this example, the effect watches for any actions of type UpdateProduct and calls our productService to update the product. If the operation was successful, it dispatches an UpdateProductSuccess action. If the operation fails, it dispatches the UpdateProductFail action. We'll walk through this code in more detail in the demo. In this demo, we build an effect to save an updated product to our backend server and dispatch success or fail actions. We've already created a ProductEffects service for our product effects. We'll add the effect to update our product to this same class. Notice how similar this code is to the effect we created in the last module. We start with the Effect decorator and define a property that provides an observable of action. We'll use the quick fix to add the import for the product interface. This new property watches for actions of type UpdateProduct. When it receives one, it maps the action to pull off the payload, which, if you recall, is the product to update. We then call our productService updateProduct method and pass in that product. However, that service call also returns an observable. We don't want nested observables here, so we use mergeMap to merge and flatten the two observables, the one from our action and the one from our product service. If we receive a successful response from our productService, we receive the updatedProduct and use the action creator to dispatch the UpdateProductSuccess action, passing along that updatedProduct. If there is an error, we catch it and use the action creator to dispatch the UpdateProductFail action, passing in the error. We still can't try out the update operation because we are not handling the success or fail actions. But let's ensure we don't have any syntax errors. Looking here in the terminal, we look okay. So let's finish up our implementation with our success and fail actions.
-
Processing the Success and Fail Actions
So far we've implemented the code to dispatch our updateProduct action, process it with an effect, and dispatch our UpdateProductSuccess or UpdateProductFail actions. All that is left is to process those actions in our reducer, to create new state. First, we add a case for the updateProductSuccess action in our reducer function switch statement. In that case, we build a new array. We use the array map on the existing products array to examine each item in the array. If the item has the same Id as our updated product, we place the updated product in the same location in the new array, otherwise we place the existing item in the new array. Hmm, let's look at how that works. Here is our original array, and we are creating a new array. Say the user updated the item with an Id of 3. This map statement examines each array element, looking for the item with an Id of 3. Not this one, so we copy it into the new array. Not this one either, so we copy it. This one matches, so we copy our updated product into that position. And these last two are copied as well. Our new array is then a copy of the original array with the updated product replaced. We then return new state by spreading the existing state, replacing the product array with our new array, replacing the currentProductId with the updatedProductsId, and clearing any error. Note that because we update the currentProductId with the updatedProductsId here, we no longer need to dispatch the set currentProduct action after the save. We also add a case for the UpdateProductFail action. Here we simply update the error with the action payload. Before we move on, let's talk a bit more about the syntax here. Wouldn't this code be easier written as a forEach loop? When working with NgRx, we need to take care to never mutate existing state, and instead to always create new state in our reducer functions. How do we know which array methods will mutate our state? An immutable object or array cannot be modified after it is created. Instead we create a new object or array. Let's take a moment to look at a few common array methods and examine whether they are mutable or immutable. The push method pushes one or more elements onto an array. Mutable or immutable? Mutable, because it directly adds the elements to the existing array. The concat method creates new array from two or more existing arrays. Mutable or immutable? Immutable, because it does not modify the existing arrays. And of course, we've been using the spread operator, mutable or immutable? Yep, immutable. We use it to copy all of the elements without modifying the original array. How about the shift operator? It removes the first element from an array. Mutable ore immutable? Mutable. And splice, it changes the contents of an array by removing or adding elements. Mutable or immutable? Yep, that's also mutable. The filter method creates a new array with all elements that pass the test provided by the arrow function. Mutable or immutable? Immutable. It provides an immutable way to create an array with a subset of elements, such as needed when an element is deleted. The map method creates a new array by calling the arrow function on every element in the original array. Mutable or immutable? Immutable. We use map anytime we need to process each element of an array. How about old friend forEach? The forEach method executes the arrow function once for each element in the array. Mutable or Immutable? Mutable. Was that one a surprise? It modifies the items directly in the array. With NgRx, we want to use the immutable methods to ensure we are creating a new array and not mutating an existing array. Now let's jump back into a demo.
-
Demo: Processing the Success and Fail Actions
In this demo, we process the success and fail actions and complete our implementation of the product update operation. Looking at the product reducer, we add the code to process our success and fail actions to our reducer function. Scrolling down to the end of the function, I'll paste both actions. This is the same code from the slides, so we've already talked through it. But notice that we are using the map method here to ensure we create a new array and not mutating an existing array. As we've seen over these past two modules, some operations have side effects, such as an async operation that calls a backend server, like our load and update operations. These operations are handled in an effect, but their success and fail actions are handled in the reducer. We're finally ready to try out the update operation. Bring up the browser, select a product to update, update its properties, and click Save. The updated product should appear in this same location in the list, and it does. Looking at the DevTools, we see the Update Product action, followed by the Update Product Success action. It's all working. We completed the update operation from start to finish with NgRx. Yay! Note, however, that the create and delete operations are not yet working. You can implement them as part of the upcoming optional homework assignment. Now let's finish up this module with a checklist.
-
Checklists and Summary
There is a basic set of steps to follow each time you perform an operation with side effects using NgRx. Identify the state needed for the operation and actions required to modify that state. Strongly type the state with an interface and build selectors. Strongly type the actions by building action creators. Dispatch an action to kickoff the operation. Build the effect to process that action, perform the operation, and dispatch a success or fail action. And process the success and fail actions in the reducer. Use these steps for any operation that your application performs that has side effects, such as async operations. Following these steps help you implement NgRx from start to finish. Up next, we'll examine additional architectural considerations when using NgRx. But first, an optional homework assignment. We implemented the update operation in this module, but not the create or delete. To implement the create and delete operations, try these steps. Identify the state and actions you need for each operation, define a state interface and selectors. For these operations, the existing interface and selectors may be sufficient. Build action creators for each of the defined actions. Dispatch the action to kick off each operation. Dispatch the create operation as part of the product-edit.component save product method, similar to how we did the update operation. And dispatch the delete in the same components delete product method. Build the effects to process the actions. These will look similar to the update effect. Process the success and fail actions with cases in the reducer. With these changes in place, the product- edit.component no longer needs to access the product service, so you can remove it from the constructor. The completed steps are in the APM-Demo4 folder in the same GitHub repo as the starter files. Enjoy!
-
Architectural Considerations
Introduction
All teams, when starting to use NgRx in their applications, have architectural decisions to make. Welcome back to Angular NgRx: Getting Started from Pluralsight. My name is Duncan Hunter and in this module we cover architectural considerations when using NgRx. Good architecture needs good plans and Angular architecture is no different. Keeping Angular applications performant, maintainable, bug free and readable, as the code base and teams that work on them grow, is challenging. Following key architectural patterns can keep your code and team operating at its best. In this module, we examine keeping your state folders by feature or by function, discuss the presentation and container components pattern. We'll look at Angular's change detection strategy called OnPush for increased view performance. And we examine adding an index.ts file for each state folder to manage our selectors and state interfaces. Let's get started.
-
Folder by Feature or Function
One of the first decisions a team using NgRx needs to make is should it have its state folders by feature or by function? In the APM demo app, we've been structuring all our state folders by feature already. We have a state folder on the root of our app's folder structure and then we have a state folder for each feature module, like our products module and our user module. If we had structured our state folders by function, we would have needed to taken out all of the products state folders contents and moved it, along with all other state, into one main state folder and then now would be following the by function approach, and this is not the recommended way. The benefits of having your state folders by feature versus function are that it follows the Angular style guide's recommendations to organize your code by feature versus function, which makes it easier for a developer to locate and identify what each file does at a glance, and as your app grows, it stops it from becoming cluttered. Let's move on to look at another pattern that makes it easy to reuse your code in the next clip.
-
Container Presentational Component Pattern
A benefit of NgRx is that it takes a lot of logic out of your components and moves it into NgRx's effects, reducers, and Angular services. This makes it easier to move towards a presentation or container component pattern, which is a way to divide components into two categories. You may have heard of these categories being called fat and skinny, smart and dumb, stateful and pure, and screens and components, but let's stick with the presentational and container components as it's the most common way to name these. Well before going into the characteristics of each category, it's important to note, it's more a mindset than a rigid classification. You still need to be pragmatic, making components that sometimes will have both characteristics. Presentational components are concerned with how things look. They're about the HTML markup and specific CSS styles to render your views. They have no dependencies on the rest of your app, such as injected services or the store. They don't specify how the data is loaded or changed, but they emit events via @Outputs to trigger container components to do the work. They receive data exclusively via Angular @Inputs and may contain both presentational and container components as their children. It's not a strict requirement that presentational components only have other presentational components underneath them. Some examples of presentational components would be nav menus, sidebars, user info panels, and many list type components. Container components are concerned with how things work and render little to no HTML markup and specific CSS styles. They do have dependencies on the rest of your app, such as injected services or the store. They're stateful and can specify how the data is loaded or changed. They are often top level routes you navigate to. It's very common to have all of your routes load container components that have a tree of mostly presentational components underneath them. They may also contain both presentational and container components underneath them. The performance is a key benefit of following this pattern by utilizing Angular's change detection strategy called OnPush. OnPush allows skipping change detection on presentational components whose @Inputs have not changed. We'll examine exactly how to do this later. Composing pages of presentational components that are less tightly coupled makes it easier to reuse them while also understanding your UI and app a lot better. Imagine we wanted to use our product list component to render either a list of products, available products, products on sale, or even a list of products based on a search filter. The first time we wanted to show a different list, we would have to start adding more logic to the product list component and making it more brittle, and this would go on and on for each new scenario. Instead, we could make a presentational component so it doesn't know how to get the filtered data or what to do when a product is selected, except emit an event, which makes it a lot easier to reuse this component. Presentational components are much easier to test with no injected dependencies or services to mock and spy presentational components, usually pure components who always return the same result for the same inputs, also making it simpler to know what to test in your components. Container components also become simpler to test as you separate the concern of getting data with rendering the view. Focusing in on our demo applications product module, let's separate out our product show component and our product list component into a container component and a presentational component. Currently our demo application has all of the product module's components under its root folder. The first step in following the presentational container component pattern is to separate the different component categories into folders, adding the product-list and product-edit components as presentational components under a folder called components and the product-shell component in a folder called containers. This naming of containers and component folders is a convention I like to follow and is also used by the NgRx team's detailed sample application, but it's your choice what you name them. Our product shell component at the moment is truly a shell with no logic or injected dependencies, it's just a container for our product list component. The product list component, however, can become a presentational component if we move the injected store dependency, which is used for selecting multiple pieces of state and to dispatch multiple actions. Moving all this logic over to the product shell component means now our product list component doesn't know or care how to get its data, it just knows it will be passed data via @Inputs. Any changes to the state or that are external to the component, will be delegated to the container component via @Output EventEmitters, for example selecting a product to edit or setting the state property's show product code flag to true. The components still has methods, but all they do is listen for button clicks and emit events. The component no longer needs a constructor and becomes a pure component, which is very simple to rationalize about and reuse. The presentational product list component sitting on the template of our product shell component, may now look more complicated, needing to pass all of these properties and events. However, following this pattern, it becomes normal to pass multiple pieces of data into the component and to listen for multiple events being passed out, as you will see in the next clip, as we do this to the demo app.
-
Demo: Container Component
Let's dive into a demo where we refactor our product shell component into a container component. Back in Visual Studio Code, we can go over to our products feature folder here. We want to make two folders here, one called containers and the other components, and drag our product-list folder into our components folder and drag our product-shell component into our containers folder. We're going to leave the product-edit folder for optional homework to discuss later. The next thing we need to do is fix up some of the import paths that have been broken by nesting these component folders one level deeper. Going over to our product module, and change our product-shell path to be /containers and our product-list component's path to be /components. With that done, closing this file with Command+W, and we want to open our shell component to the left and our product-list component on the right. On the right, we can see we have a bunch of broken imports from nesting our component one level deeper and all our paths are wrong. But first, let's not fix them here, but let's turn this into a presentational component. Let's go through the process of stripping this down to be a much simpler pure component. The first thing we're going to do is take out all the imports and logic that know how to deal with getting and setting data and move them over to the left here into our product-shell component. All of our import paths are now a level deeper so we're just going to get a hold of them with Command+D and make these one level deeper so they're pointing to the right path. Next we want to take out all of the logic in our presentational component, starting by taking out lifecycle hooks and basically stripping out all of the guts of its logic here, except for moving the title. Let's move it all over to the product-shell component, so we're just going to paste that over the top of our empty logic. With that done, we want to take advantage of following this pattern and change our mixture here of getting and setting our properties from the store, some as observables and some as the value of those observables. So we went to get rid of everything here and make it much cleaner and get everything here as an observable. The container component no longer needs to get or project any of the values from these observables. All it needs to do is get them and pass them down to the presentational component using async pipes. Because we are going to use async pipes, we can clean up this component even more as we do not need to do any subscribing here, so let's move up our only single dispatched action, the productActions.Load action, and the rest of everything in this ngOnInit is just methods to select state from the store. The first two we have as observables and then next two are subscribing in the component and getting the values. But we want to clean all of this up now and get all of these as observables, so I'm just going to paste in the logic for that here and we're now going to leave all of those pieces of state as observables. With this done, we can get rid of our ngOnDestroy here as we're using async pipes for that now. We'll keep the three methods we took and placed in here to be able to dispatch our checkChanged action or newProduct action or the productSelected action. What I really like about this approach is even though you would consider this a smart component, what we call a container component, it's very easy to think about and understand as all it does is subscribe to a bunch of state and initialize these observables and pass them down to the presentational component, and when we need to we can dispatch actions against the store to change state. You can see already just how much cleaner and easier it is to rationalize about this component as it no longer needs to deal with anything to do with rendering HTML. This is the job of the presentational component now. So let's go and pass all these values down to the presentational component on the template of this container component. Pressing Command+P and searching for the product-shell HTML, we can come over here and paste in all of the inputs we want to pass down. So we want to subscribe and pass down the value of our error message, display code, products, and selected product observables using the async pipe. And we're going to pass them in as these values into our presentational component via inputs that we're about to go make on the presentational component. And when anything changes and we want to listen for these changes from our presentational product-list component, then we're going to have these event listeners I'll paste in, which will do the opposite of the inputs and call the checkChanged, newProduct, and productSelected methods on our container component, which will then dispatch the actions against our store. So in the next clip, let's go add these inputs and outputs to our presentational product-list component.
-
Demo: Presentational Component
In this demo, we'll finish converting our product-list component into a presentational component. So first, we're going to paste in the code for our @Inputs and we're going to need to import a couple of symbols, including our product interface and the @Input symbol from @angular/core. So these are the things we want to be able to receive, no longer as observables, but as straight values, and we can paste in events we want to be able to output from our component. We'll need to import a couple more types, including @Output from @angular/core and also EventEmitter, also from @angular/core. Now that we have these events we can emit, let's paste this in here and now we are going to be able to emit an event here for each of these three functions. If someone clicks Show Product Code checkbox, it would trigger this checkChanged function and emit the Boolean value. If someone clicks a New Product button, then it will trigger the newProduct function emitting void, but causing a dispatched action to initialize a new product in our container component. And if someone clicks on a product, it will trigger the productSelected function and emit the selected product. So now we are not using observables in this component. We will need to go over to its template and remove any async pipes. If we do a search for async, we actually have two async pipes here on our page. The first one is up here on the ngIf where we're using the async as syntax to set a local variable. But now we can just use the products variable directly passed into the component. The other async pipe is down here in our error message, and now we do not need to subscribe to that either and we can just use the actual error message property that gets passed into the component. Now we've separated the responsibilities of these two components, we can open up the terminal and run ng s for serve and go to the browser. If we navigate to the product list page, we can see our product application is still working as before, showing the Load and Load Success actions being dispatched, and if we select a product, it will load the product details into the product-edit component. Next up, we'll see how this pattern not only helps us to have a clearer separation of concerns, but also allows us to implement a more performant change detection strategy called OnPush.
-
Change Detection OnPush
Following the presentational container component pattern allows us to more easily take advantage of an Angular change detection strategy called OnPush to optimize our view performance. Reading the Angular API documentation for ChangeDetectionStrategy OnPush, it says that change detector's mode will be initial set to CheckOnce. So what does this mean for our performance?, as this is a little bit vague. Basically this strategy tells Angular that a component depends solely on @Inputs and only needs to be checked if it receives a new input reference or if the component or its children trigger a DOM event, like a button click. When I say input reference, I mean that in order to trigger change detection in our component, we need to change the input object reference, not just mutate it. This is another example of how dealing with immutable store data with NgRx can help us take advantage of more advanced patterns. It's important to note any asynchronous API events, like XHR or promise-based events, will not trigger change detection once you change to this strategy of OnPush and the components template will not get updated. By default, Angular uses the ChangeDetectionStrategy.Default change detection strategy. The default strategy doesn't assume anything about the application, therefore every time changes in your application as a result of any user events, timers, XHR requests, promises, etc, change detection will run on all components. This means anything from a click event to a data received from an HTTP call, causes change detection to be triggered, potentially causing performance issues in your application as it checks every component. If we look at this example component tree, the way Angular's change detection system works is that if a button click, for example, occurs down here at the bottom of the tree, it will trigger a round of change detection. Angular's change detection starts at the top at its root component and goes down the component tree, checking every component, even if it has not changed. In contrast, if a button click occurs again, but this time we change this component's change detection strategy to OnPush, Angular will still run around a change detection starting at the root component and working its way down the component tree, however, the component marked with OnPush and all of its children will be skipped. This can make a real world difference in an application with a lot of components loaded with thousands of potential expressions to be checked every time a button is clicked. Using this strategy with the presentational container pattern is pretty simple. On all your container components, you change their ChangeDetectionStrategy to OnPush. This is done in the component's app component decorator. By default the ChangeDetectionStrategy is set to default, but nobody every needs to specify the default. Let's jump into a demo and apply this change detection strategy to our product-shell container component. In VS Code with the product-shell component open, in the @Component decorator, let's add a changeDetection property and set it to ChangeDetectionStrategy.OnPush. That is all we need to do and it's very simple, but you need to remember that now unless a new input reference is passed or a DOM event is raised in your component or its children, the view will not get updated.
-
Creating a Barrel with Index.ts Files
Looking at the product reducer file, we have a lot of code before we get to the actual reducer function. We have our ProductState interface and the initialState object that implements it directly related to this reducer function. But our state interface really belongs to the whole product module, which might have multiple reducers, not just the product reducer. So for example, hypothetically, imagine we had an inventory reducer added to this feature module. How do we know which of these two reducer files to put this state interface in? Also, if we need to make a selector that uses state from both our inventory and product reducers, again, how do we decide which reducer we should put it in? For these reasons, it's common in NgRx to put selectors and state interfaces in a more central place. A barrel is a way to roll up exports from several ECMAscript modules into a single convenient module. The barrel itself is a module file that re-exports several exports from other modules. Barrel files are named index.ts as a convention because most module loaders will look for this by default when resolving absolute paths and this will allows us to emit the file name from the path and just point to a folder versus pointing to a folder/index.ts. For example, imagine we have an app/index.ts file that exports Foo from app/foo.ts, Bar from app/bar.ts, and everything from app/baz.ts under a new namespace of baz with the * as Baz syntax. Now rather than adding three import statements to a file that wanted to consume these, we could get them all from one convenient place, the index.ts file. By importing Foo, Bar, and Baz from /app, note that we do not need to add the index.ts to the file path as it's implied by convention. It's very common in NgRx applications to make an index.ts file for each state folder to re-export all state interfaces and selectors from a single file, becoming like a public API for each piece of feature state. Usually each state folder will have its own index.ts file that re-exports shared interfaces and selectors. The benefits of using an index.ts file for each state folder are the index.ts file becomes a public API for feature state modules, explicitly saying what should be shared from this module instead of importing state from individual reducer files. We have a clearer separation of concerns as our reducers become responsible for updating state, and we have a place for our shared selectors. Finally, we have a more readable and cleaner code, firstly in our reducers with less code, and in other parts of our application that can now use a single import statement to access each feature module's state selectors and interfaces. Let's jump into a demo where we make an index.ts file for our product features state folder. In VS Code, I'm going to navigate to our products state folder in the Explorer and add a new index.ts file. Next I'll open our product reducer file to the right and close the Explorer to give us a little bit more room. First we'll grab a hold of the product state interface and the initial state object that implements it and move them down the page to sit above the reducer function they directly relate to. Then we'll just grab everything from here up, including the import statements, feature state interface, and our selectors, and paste them into our index.ts file. We'll need to fix some missing import statements with the quick fix tool for the ProductState. Over in the product reducer, we'll need to do the same for our Product interface, ProductActions and ProductActionTypes. Now we've changed the files where our selectors and state interfaces live, we need to fix the import statements in our product-shell container which use them. Closing all the files down, pressing Command+P and searching for our product-shell component, we can change our import statement here from import * as fromProduct from ../state to get everything from our new index.ts file. We'll also need to do the same in our product-edit component, changing that also to just be ../state. Opening up the terminal, we can see our application is still running. And over in the browser, we'll not see any changes, but our application is still working. We now finish up this module with a checklist and walk through an optional homework assignment to convert the product-edit component into a presentational component.
-
Checklists and Summary
The benefits of the presentational container component pattern are view performance improvements as it makes using OnPush change detection easier. Presentational components have a better separation of concerns and are more reusable, and being more reusable they're easier to compose pages from. Pure components are also much easier to test with no injected dependencies or services to mock and spy. The OnPush ChangeDetection strategy will skip change detection unless the components inputs receive a new value or object reference. TO use it, add ChangeDetectionStrategy.OnPush to the component's decorators ChangeDetection property. It's easier to use this when categorizing components into presentational or container components. Barrels are a rollup of exports from several ECMAscript modules into a single module. They work like public APIs for feature state modules. To use barrels, make an index.ts file in each state module, add selectors and state interfaces to these index.ts files, and re-export other feature state for other modules. Up next, we'll briefly look at other NgRx packages you might want to consider using in your apps. But first, some optional homework assignments. First assignment is to convert the product-edit component into a presentational component. Try these steps, move the Product Edit component into the components folder, change the import file paths of all files that import the Product Edit component, remove the injected store from the constructor, pass all required store state properties down as inputs from the product-shell component, move all dispatched actions to the product-shell component and trigger them by emitting events from the Product Edit component. Lastly, add OnChanges lifecycle hook to the Product Edit component, to listen and capture the changes to the products inputs, to be able to call the patch form method on changes. The completed steps are in the APM-Demo5 folder in the sample GitHub repo. The second assignment is to add an index.ts file to the user module like we did the product module. Try these steps. Add an index.ts file to the User state folder, copy the State interface and selectors to the index.ts file, add back any missing import statements, change any files that imported the state interface or selectors to point to the User state folder versus the user reducer file. The completed steps, again, are in the APM-Demo5 folder in the sample GitHub repo. Enjoy!
-
Final Words
Introduction
As you have seen throughout this course, NgRx helps us actively manage our application state and clearly structure our component interactions. Welcome back to Angular NgRx: Getting Started from Pluralsight. My name is Deborah Kurata and the final words in this course include a brief recap, information about other NgRx libraries, and a few pointers to additional information. Let's jump right in to this last module.
-
Recap
Using NgRx gives our application a firm foundation and stabilizes our often complex state management. This is especially useful for large or complex applications. NgRx is based on the Redux pattern. For each user event and operation that modifies application state, we create and dispatch an action. The reducer processes the action using the action payload and existing state to define new state and it replaces the state in the store with that new state. For operations with side effects, such as async operations that communicate with a backend server, we build effects that perform the operation and dispatch a success or fail action. Components watch for changes to the store through a selector. When a change is detected, Angular's change detection updates the view. We began with a simple implementation for goggling a single flag property, ignoring all of the orchestration and ceremony. That helped us walk through the pattern without all of the abstraction. Next we examined the developer and debugging tools. We saw a really sweet way to view our actions and state at runtime. This tool helps with debugging and is a great way to remind ourselves or introduce a new team member to the operation of the application. To help us avoid trying to put a round peg in a square hole, we looked at how to strongly type our state using interfaces and how to simplify access to that state using selectors. We can be an action hero by strongly typing our actions with action creators. We use the action creators to create the actions we dispatch throughout our application and save ourselves from hard to find errors. Operations in our application may have side effects. In this context, a side effect is an operation that depends on or interacts with an external source, such as external state, devices, or an API. For example, using HTTP to access a backend server is a side effect. To process these operations, we build an effect. The effect watches for specific actions, performs the requested operation, and dispatches success or fail actions. Sometimes it only takes a small change to have a big impact, so it's often important to allow the user to make changes to the application's data. We walked through how to implement NgRx from start to finish for an update operation that uses effects to process the update. Lastly, we examined architectural considerations, including the container presentational pattern. We looked at the OnPush ChangeDetectionStrategy for improved performance. And we walked through how to build an index.ts file in each state folder for our interfaces and selectors. This file basically defines a public API for each piece of feature state. For your reference, here is a summary of the state, actions, and effects we implemented in this course. Recall that this resulted in quite a bit of code. Let's look at some additional libraries that you can use with NgRx to simplify, generate, or completely eliminate some of that boilerplate code.
-
Additional NgRx Libraries
There are additional libraries available as part of NgRx, plus one that may be part of NgRx soon. Why didn't we include these libraries as part of this course? Well, this is a getting started course and we wanted to focus on clearly covering the basics. Once you have the basics down, it's easier to understand other techniques and libraries. Plus the course had a limited length. We couldn't do justice to all of this additional information in our allotted time. But let's spend a moment and define what each of these additional NgRx libraries are and when you may want to use them. Most Angular applications manage entities, such as products. As we've seen in this course, there is quite a bit of code required to build the actions and reducers for our entity, Create, Read, Update and Delete, or CRUD, operations. NgRx entity is the library that provides helper functions for managing collections of entities and building the actions, reducers, and selectors, minimizing the boilerplate for CRUD operations. Use NgRx entity anytime you want to reduce the amount of code required to manage entities in your NgRx application. Schematics are a scaffolding library provided as part of the Angular CLI. If you've ever used ng new or ng generate, you've seen how the CLI uses schematics to generate code. You and your team can build your own schematics to change how the CLI generates code or add more specific code generation specifications. The NgRx schematics library is a set of schematics specifically for generating NgRx code using the Angular CLI. You can generate the code to set up the application with a store. You can generate actions, reducers, and effects, and NgRx schematics has blueprints for generating code for feature modules, container components, and NgRx entities. In this course, you learned how to manually create the NgRx pieces. You can save yourself time by letting the Angular CLI, with the NgRx schematics library, generate, scaffold out, and wire up the pieces for you. You can then add the code that is unique for your application. Angular's routing helps us move the user from one view of the application to another. We can connect the Angular router to the store using NgRx router store. This library dispatches router navigation actions so we can process them like any other actions in the application. Instead of helping us with NgRx by generating code, the ngrx-data library takes a different approach and instead abstracts it all away. With a little configuration, and by following a few conventions, ngrx-data handles the rest, but only for our entities. It does not handle any other type of application state. With ngrx-data, when working with entities, we don't need to create actions or action creators. No reducers, no selectors, no effects, and no code generation. Instead, we define some metadata, register the appropriate module, and everything else is handled in the ngrx-data library, and it offers extension points for customization as needed. Use this library if you want the benefits of NgRx for your entity but don't want to write any of the code. But note that this also means you are giving up some control for how NgRx works with your entities in your application. Now that you know the basics of NgRx, try out these other libraries and see which ones work for you, your team, and your project. Next, let's look at some resources for learning more about NgRx.
-
Learning More
This course covered many NgRx concepts, but there's always more to learn. Here are some related Pluralsight courses. "Play by Play Angular and NgRx" introduces and demonstrates NgRx in a more conversational way and is a great follow on to this course. "Angular Component Communication" details numerous component communication techniques that provide alternatives to NgRx, such as behavior subject. Check out these courses to learn more.
-
Closing
Congratulations, you've made it through the basics of NgRx. Yay! We've covered a lot of techniques you can use as you build your own applications and you'll find more as you continue learning. We would love to hear about your experience with NgRx. Reach out to us on Twitter or on the discussion page for this course. Thanks for listening and we hope you enjoyed getting started with NgRx.