
Welcome to this review of the Pluralsight course Angular NgRx: Getting Started by Duncan Hunter and Deborah Kurata.
This is an intermediate level course. If you are new to Angular, I recommend starting with one of the beginner level courses. See my Angular Learning path post for reviews of various courses in the Angular learning path on Pluralsight.
My recommendation is to read through the rest of this article and see whether this course, and NgRx, is for you. If NgRx seems too complicated its probably not for you. If you feel that NgRx is the right library for you, but you don’t have 4 hours free to watch the course, either just watch the first couple of modules, or watch Play by Play: Angular and NgRx which is a 1 hour 39 min course.
Angular NgRx: Getting Started begins by explaining what NgRx is. It’s a set or reactive libraries for Angular. There are several different packages and this course explores the most commonly used packages and features. There is:
- Store — Redux inspired State management
- Effects — Side effect model
- Router-store — Bindings to connect the Angular Router to store
- Store-devtools — Store instrumentation that enables a powerful time-travelling debugger
- Entity — Entity state adapter for managing record collections
- Schematics — Scaffolding library
(The Router-store, Entity and Schematics packages are not really covered in this course as this course, only mentioned in the Final Words module)
The most important and the only required package in NgRx is the store.
The store uses the Redux pattern.
Our state is immutable, meaning it is never modified. Rather, new state is created based on the existing state. In other words, state changes are much more explicit, reducing the risk of hard to find bugs in your application.
Why NgRx?
Use NgRx when
- There is state everywhere (retain state between router views)
- Getting data again and again (store has client-side cache)
- Notify everyone (all subscribers)
- Something isn’t working (great tooling)
Don’t Use NgRx When
- Your team is new to Angular
- The app is simple
- Already have a good state management pattern
Redux Principles
- Single source of truth called the store
- State is read only and only changed by dispatching actions
- Changes are made using pure functions called reducers
Redux Pattern
This module is presented by Duncan Hunter. He introduces the Redux Pattern as a way of bringing order to chaos. This pattern is implemented in many popular frontend frameworks:
- React uses Redux
- Vue JS uses VueX
- Angular uses NgRx
Redux Pattern: Store
What Should Not Go in the Store?
- Unshared state
- Angular form state
- Non-serializable state
Redux Pattern: Actions
Dispatch an Action to Change State
e.g.
– Login action
– Toggle side menu action
Use Reducers to Change State
e.g.
– Set a userDetails property on login
– Toggle a sideMenu Visible property to true on button click
– Set successfully retrieved data on component initialization
Redux Pattern: Reducers
Reducers are PURE functions
Impure functions depend on outside variables
Advantages of the Redux Pattern
- Centralized immutable state
- Performance
- Testability
- Tooling
- Component communication
“Redux is not great for making simple things quickly. It’s great for making really hard things simple.” — Jani Evakillio
First Look at NgRx
This module is presented by Deborah Kurata.
Setting up the sample application
The code associated with this course can be found on Deborah Kurata’s Github page. Download this code so that you can try out the examples.
Once downloaded, go to the terminal and type npm install, to install all the packages needed for this application.
There is no proper back-end (such as SQL server) used in this course. Rather it uses Angular’s in memory web API which emulates the server and offers CRUD operations. Any changes are lost when the browser is refreshed or restarted.
The application is ACME Product Management, and we’ve seen various versions of this in different courses on the Angular learning path.
The starting point for this version is very simple — it just has a product list screen with a list of product names. There’s a checkbox for displaying the product code after the respective product name.
There are some state management issues that can be solved using NgRx.
Installing the Store
npm install @ngrx/store
Initializing the Store
import { StoreModule } from ‘@ngrx/store’;
In the NgModule imports add:
StoreModule.forRoot(reducer)
Demo: Initializing the Store
As above, in app.module.ts, we add the imports statement and the StoreModule in the imports array and call its forRoot method.
As we don’t yet have a reducer, we temporarily use an empty object.
In each feature module that uses the store (e.g. product.module.ts) we add
StoreModule.forFeature(‘products’, {})
Again the empty object is just a placeholder for our reducer.
Defining the State and Actions
An action is an object with an action type and optional payload. An example is for toggle our product code. We can call the type TOGGLE_PRODUCT_CODE and have a boolean payload value.
Building a Reducer
Deborah explains that it is the reducer that does the bulk of the work, doing the required processing and returning a representation of the state to the store.
There is more ceremony, if you will, with using reducers to update state, however this more explicit mechanism for changing state makes your application more predictable, which is useful is as your application grows larger.
NgRx does NOT enforce immutability. If you want this enforcement you could try Brandon Robert’s ngrx-store-freeze.
A reducer takes two arguments: state and action, and returns the updated state. A simple implementation of a reducer uses a switch statement with different logic for each action.type value.
Given the same inputs the reducer should always return the same output. This is known as a pure function.
Demo: Building a Reducer
Deborah creates a state folder and underneath it creates product.reducer.ts
The basic structure of a reducer is:
export function reducer(state, action) {
switch(action.type) {
default:
return state;
}
}
Deborah adds the case statement for ‘TOGGLE_PRODUCT_CODE’ and adds console.log statements for illustrative purposes.
In product.module.ts we can now replace the {} with out reducer.
Dispatching an Action
An action represents an event that changes the state of the application, such as the user checking a box of selecting an item.
Nothing happens until we’ve dispatched an action from our component to our reducer.
We must inject the store into our component constructor like this:
constructor(private store: Store<any>) {}
This clip includes another demonstration.
An example of an action is:
checkChanged(value: boolean): void {
this.store.dispatch({
type: ‘TOGGLE_PRODUCT_CODE’,
payload: value
});
}
In the product-list.component.html we call the action:
<input type=”checkbox” (change)=”checkChanged($event.target.checked)” [checked]=”displayCode”>
We see that the mechanism is working but we don’t see the product codes displayed. This is because we first need to retrieve the data from the store
Subscribing to the Store to Get
In this clip we store state changes associated with a checkbox action
The final piece is to supply the component state from the Store
These two statements are equivalent:
this.store.select(‘products’);
this.store.pipe(select(‘products’));
The benefit of the pipe operator is we can use other pipeable operators to it (this will be shown later in the course).
This clip includes a demonstration of how to subscribe to state changes.
The code is added to the ngOnInit method in product-list.component.ts
We append .subscribe to one of the above product select statements, to receive change notifications:
this.store.pipe(select(‘products’)).subscribe(
products => {
if (products) {
this.displayCode = products.showProductCode;
}
})
In the HTML we use the *ngIf directive to display the product code if the value of displayCode is true.
Deborah explains we also need to unsubscribe, and one way to do this is to store the subscription in a variable. This is a poor approach however if we have multiple subscriptions in the same component. More advanced unsubscribe strategies are discussed later in the effects module of the course.
Deborah ends this module with a homework assignment for you. There is a “mask user name” checkbox at the bottom of the login component. Your homework is to update the application to retain the value of the “mask user name” checkbox. The steps are:
- Run the application and examine the login page
- Initialize the store in the user module
- Define the state (maskUserName) and action (MASK_USER_NAME)
- Create the reducer function
- Dispatch an action
- Subscribe to the user slice of state
The completed version is found here.
Developer Tools and Debugging
This is a short 10 minute module presented by Duncan Hunter.
He recommends the Redux DevTools browser extension.
install @ngrx/store-devtools
Time travel debugging
Initialize with this import:
import {StoreDevtoolsModule} from ‘@ngrx/store-devtools’
import {environment} from ‘../environments/environment’;
With the environment object imported we can set to logonly mode when in production.
Strongly Typing the State
This module is presented by Deborah Kurata.
We’ve been using a lot of string values so far, and state of type any.
In this module we fix this by strong typing our state.
Defining Interfaces for Slices of State
With TypeScript we define an interface for each slice of state that we want.
We also create an interface for our global application State, and this is made up of each of the interfaces each representing a feature slice of state.
In this clip Deborah adds a ProductState interface in product.reducer.ts and than creates a state folder with a new file app.state.ts in it.
app.state.ts imports ProductState from product.reducer and defines an interface for our global application state.
Extending the State interface
Lazy loading improves our application startup performance.
Deborah mentions that there’s more information on lazy loading in her Angular Routing course.
We want to establish logical boundaries around our lazy loaded features and see how to define a state interface that extends our global application state interface using the extends keyword.
In the demo: extending the state interface for lazy loaded features:
- delete the product slice from State interface
- delete the ProductState import statement
- add import * as fromRoot statement in product.reducer.ts
- export interface State extends fromRoot.State { products: ProductState; }
Demo: Strongly typing the state
In product.reducer.ts, update the reducer method specifying that the state must be of type ProductState.
Specify that the return type must be of type ProductStat.e
This prevents typo errors, and if a typo is made the VS Code tooling provides us with the list of valid options
In product-list.components.ts, in the constructor specify the type of Store object:
- constructor(private store: Store<fromProduct.State>, private productService: ProductService)
Setting Initial State Values
Demo — setting initial state values
In product.reducer.ts
const initialState: ProductState = { showProductCode: true //etc
};
export function reducer(state = initialState, action): ProductState { //snip }
Products page now displays with the display product code checkbox checked by default
Building Selectors
Problems with Products slice of State:
1. Hard-coded string ‘products’ — risk of spelling error creating a bug
2. Knows the store structure (coupling)
3. Watches for ANY changes — code is notified if any property is changed
We can make this better with selectors!
A selector is a reusable query of our store. They are like Stored Procedures for in memory data.
Benefits of Selectors
- Provide a strongly typed API
- Decouple the store from the components
- Encapsulate complex data transformations
- Reusable
- Cached (memoized)
Types of selector function:
1. createFeatureSelector — simple slice
2. createSelector — customizable/composable
Deborah says a selector should be a pure function.
Demo: Building selectors
Deborah adds the selector in the reducers file, but says the best location for your selectors will depend on your application.
export const getProductFeatureState = createFeatureSelector<ProductState>(‘products’);
export const getShowProductCodeProduct = createSelector(getProductFeatureState, state => state.showProductCode
);
Deborah invites you to pause the video before revealing the selector for getCurrentProduct, and getProducts.
Use VSCode quick fix to add ‘createSelector’ to existing import declaration from @ngrx/store
Checklist:
– Define an interface for each slice of state
– Compose them to define the Global Application state
– Use the interfaces to strongly type the state
– Set initial values and initialize the state
– Build selectors to define reusable state queries
Your Homework for this module is to add strong typing and selectors
The solution is in the APM-Demo2 folder in Deborah’s github repository.
Strongly Typing Actions with Action Creators
Benefits of Strongly Typed Actions
– Prevents heard to find errors
– Improves the tooling experience
– Documents the set of valid actions
The last step is to define a union type for the Action Creators
Checklist
1. Define the action types (Use an enum to specify the set of named constants)
2. Build an action creator (Define a class with type and payload properties. Use the action creator when dispatching the action)
3. Union the action creators (Define a union type of all action creators, use it in the reducer)
Homework:
Create a user.actions.ts file
Add an enum for the action type
etc
See APM-Demo2 for solution
Working With Effects
This module is presented by Duncan Hunter
NgRx Effects Library: manages side effects to keep components pure
Effects take an action, do some work and dispatch a new action
Request:
Component->Action->Reducer->Effects->Services->Server
Response:
Server->Services->Effects->Action->Reducer
Benefits of Effects:
- Keep components pure
- Isolate side effects
- Easier to test
An Effect is an Angular service
Defining An Effect
Steps to define an effect:
1. Create service
2. Define effect
3. Filter actions
4. Map
5. Call service
6. Return new action
Or explained more simply:
– Take an action
– Do some work (call service)
– Return a new action
npm install @ngrx/effects
Checklist and Summary
- npm install @ngrx/effects
- Build the effect to process that action and dispatch the success and fail actions
- Initialize the effects module in your root module
- Register effects in your feature modules
- Process the success and fail actions in the reducer
The Homework assignment is:
- Identify all subscriptions to the store (Look for //TODO: unsubscribe)
- Add an OnDestroy lifecycle hook to the component
- Initialize a componentActive flag to true
- Set the componentActive flag to false in the ngOnDestroy method
- Add a takeWhile pipe before the subscribe method and use the componentActive property as a predicate for this operator
You can find the solution to this exercise here.
Performing Update Operations
This module is presented by Deborah Kurata, and here she teaches us how to update or delete data using NgRx.
In order to do this, we need to use Effects, and this is why Duncan introduced Effects in the previous module.
Identifying the State and Actions
Our goal is to update a product.
The user selects a product, edits its properties, and Saves or cancels the changes. We then display the updated product.
Because the back-end server will take some time to process the update, the operation is asynchronous. When the server returns the response, the Effect dispatches the UpdateProductSuccess, or the UpdateProductFail action, as appropriate.
Strongly Typing the State and Building Selectors
The steps are:
- Define an interface
- Set initial value
- Build selectors
This clips includes a demonstration where we start by changing currentProduct to currentProductId with a type of number or null.
Strongly Typing the Actions with Action Creators
Processing the Success and Fail Actions
Demo: Processing the Success and Fail Actions
Homework assignment:
In this module we saw how to perform the update but not the create or delete. The steps are the same however, for all operations with side effects:
- Identify the state and actions
- Define a state interface and selectors
- Build action creators
- Dispatch an action to kick off the operation
- Build the effect to process that action and dispatch the success and fail actions
- Process the success and fail actions in the reducer
The solution can be found here.
Architectural Considerations
This module is presented by Duncan Hunter.
Should the state folders be by feature or by function?
Throughout this course, we’ve organized by feature. The advantages are:
- Follows Angular style guide
- Easy to find related files
- Less cluttered
Container Presentational Component Pattern
NgRx takes logic out of components.
Duncan says the Container and Presentational Component pattern is a way of dividing your components into two different categories.
Presentational components
- Concerned with how things look
- HTML markup and CSS styles
- No dependencies on the rest of your app
- Don’t specify how the data is loaded or changed, but emit events via @Outputs
- Receive data via @ Inputs
- May contain other components
Container components
- Concerned with how things work
- Have little to no HTML or CSS styles
- Have injected dependencies
- Are stateful and specify how data is loaded or changed
- Top level routes
- May contain other components
The key benefits of this pattern are:
- View Performance (makes using OnPush change detection easier)
- Composability
- Easier to test
Demo: Presentational Component
Creating a Barrel with Index.ts Files
Homework assignment is to convert the product-edit component into a presentational component. Steps are:
- Move the Product Edit component into the components folder
- Change the import file paths
- Remove the injected store
- Pass all required store state properties as inputs
- Move all dispatched actions to the Product Shell, called via emitted events
- Add an OnChanges life cycle hook to listen for and call the patch form method on changes
There is also a second homework assignment if you are feeling adventurous.
This solution to this exercise can be found here.
Final Words
This final module is presented by Deborah Kurata.
NgRx is useful for working with large or complex Angular applications. In this clip, Deborah summarizes each of the modules we have worked through.
The last slide shows that in this course we implemented 4 states, 4 effects, and 16 actions. We can eliminate some of the boilerplate code with some additional libraries:
We didn’t cover @ngrx/entity earlier in this course, but here Deborah explains it is a library that provides helper functions for managing collections of entities. We can use it to reduce boilerplate code.
Schematics are a scaffolding library for generating code using the CLI. We manually created the NgRx pieces in this course, but Deborah says we can save ourselves time by letting the Angular CLI, with the NgRx Schematics library, generate, scaffold out, and wire up the pieces for you.
Router-store dispatches router navigation actions so we can process them like any other actions in our application.
ngrx-data abstracts away the NgRx entity code. It uses configuration and convention, rather than code.
Two related Pluralsight courses:
- Play by Play Angular and ngrx by Duncan Hunter and Lars Klint
- Angular Component Communication by Deborah Kurata
Thanks to Deborah Kurata, Duncan Kurata, and the rest of Pluralsight for producing this course.
For more details on Pluralsight see Pluralsight: The Definitive Guide