-
Notifications
You must be signed in to change notification settings - Fork 1
NgRx New Syntax PluralSight
-
NgRx is a framework for building reactive applications and angular and directs provide state management, isolation of side effects. NgRx uses the popular state management pattern called Redux tailored to angular.
-
The Redux pattern helps us manage our applications state by providing a one way data flow throughout the application.
- Let's start with the view. In this example. We have a check box at the bottom of our view that the user can select to include the product code in 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 check box. The view uses event binding to notify the component of the user event. The component then creates an action representing that event. The component dispatches this action toe 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. This store is a single in memory client side state container. It only stores are application state during execution of the application.
-
Using this pattern, our state is a mutable, 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 the 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 on the store, the component is notified of that new state and bound values and 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 users.
-
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 toe. Add that data to our store so we can better manage it. The components subscribe through a selector to watch for any changes to that store data. This then defines our one way data flow.
-
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 will 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 has a basic get method that loads the products from a back end server using http. The component calls that service 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 a client site cache, so we don't need to get the data from the server every time the component is initialized.
-
Without NgRx, we have to explicitly handle complex component interaction. Example: each time the user selects a new product from the list, the application notifies the parent component and other associated sibling components so it can display the appropriate detail. It can be a bit of work to ensure each component is notified of the change without closely coupling the components within NgRx.
-
Each time the user selects a product, the component dispatches an action. 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 products 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 status 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 roll back and do time travel debugging more on that later.
-
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
- you may not need NgRx if you and your team already have a good state management pattern in place for your applications.
The Redux Pattern, which NgRx is based on, has three main principles.
- 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.
- Changes to the store are made using pure functions called Reducers
Store:
- The store is literally a JavaScript object that holds all your application state. You can think of it like a client side database with angular.
- Do you have to put every pace of state in the store? No, you don't. So what shouldn't go in the store is:
- 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, 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'll not be able to track those changes with actions, which is the second principle of Redux.
Actions
- All relevant user events dispatches the actions affecting reduces who update the store. Some examples of actions you might dispatch to change the store are:
- a log-in action after a log in form submission
- a toggle side menu action after clicking a button, retrieve data action when initializing her component
- start a global spinner action when saving data.
- Actions are events that have a type property describing the action and can have optional data associated with them.
- When we say the store is read only and to change state we dispatch actions, you shouldn't mutate this state, and you should follow the principle of a mutability, meaning that if you need to change this state of the store, then replace the whole state object and not just mutate part of it in mutability.
Reducers
- Reduces 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 user details state property on log
- in toggle aside menu visible state property to true on a button click set
- successfully retrieved data on component initialization
- set a global spin a visible property to true.
- While saving data, not all dispatched actions can directly update this store by the reducer. Some actions have side effects.
- Reducers are responsible for handling transitions from one state to the next state in your applications.
- Reducer is the functions that handle these transitions by determining which action to handle based on the actions-type.
- Reduces are pure functions and handle each state transition synchronously. Each reducer function takes the initial state and a selection of functions that handle state changes for their associated actions. These functions take the current state and an action and return new state.
Advantages of the Redux Pattern
-
Having a centralized, immutable state tree makes state changes more explicit and predictable with the 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 state from local storage or when doing service 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 is all.
-
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 debugging angular application, and most developers air surprised at how much less traditional debugging they do like console logging and setting break points.
-
Another benefit of Redux is simpler Component communication and directs makes it easy to access shed state by injecting the store into a component versus passing data between components.
-
The Redux Pattern is not great for making simple things quickly. It's great for making really hard things simple.
Action
export const toggleProductCodeAction = createAction(
'[Product] Toggle Product Code'
);
Reducer
export const productReducer = createReducer<ProductState>(
initialState,
on(ProductsAction.toggleProductCodeAction, (state: ProductState) => {
return {
...state,
showProductCode: !state.showProductCode
};
}),
on(..),
on(..),
);
Selector
const getProductFeatureState = createFeatureSelector<ProductState>('products');
export const getShowProductCode = createSelector(
getProductFeatureState,
state => state.showProductCode
);
State
export interface State {
users: UserState;
products: ProductState;
}
Effects
loadProducts$ = createEffect(() => {
return this.action$.pipe(
ofType(ProductActions.loadProducts),
mergeMap(() => this.productService.getProducts().pipe(
map(products => ProductActions.loadProductsSuccess({ products }))
)
)
)
});
FLOW
General Steps
-
Store
- 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 state 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 a feature name initialized the store both in the root application module and in each feature module that uses the store.
-
Action
- An action represents an event that changes the state of the application, such as these 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 changed data that must be tracked after the component is destroyed.
-
Reducer
- A reducer is responsible to dispatched actions and replaces the reducer slice of state with new state Based on the action.
- Build a reducer function.
- To process the actions, you'll often have one reducer function per feature.
- Implement the reducer function using create reducer and don't forget to spread the state as needed. This copies these existing state to a new object and allows updating that copy before overriding the existing store state.
- With that copy, we have the state actions and reducers in place, we are ready to dispatch an action.
-
Dispatching an action
- Dispatching an action is often done in response to a user event. Has checking a box or selecting an item or foreign operation, such as retrieving data?
- Inject the store in the components constructor to dispatch an action.
- Call the dispatch method of the store and pass in the action to dispatch the action is dispatched to all reducers and process by any matching on functions for any of our components.
- To react to changes in the store they subscribe.
-
Subscribing to the store
- 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.
- Inject the store into the components constructor, then use the store.
- Select function 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.
- Subscribing to the store is often done in the
Steps to build selectors:
- Strongly type our application State by defining an interface for each slice of state.
- Compose those interfaces to define the global application state.
- Use the interfaces to strongly typed the state throughout.
- 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 initializer the state by setting the reducer functions state parameter to this object literal build selectors to define reusable queries against the store state. These selectors decouple the component from the store and can encapsulate complex state of 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 type state and selectors to access that state.
Steps to strongly type actions:
- Define the set of actions appropriate for a specific feature.
- Then, for each defined action export a constant that calls create action.
- Be sure to specify a clear and unique action type string as the first argument past to create action.
- If the action requires data, use props to specify metadata, defining the structure of that Associated Data. 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.
Steps to use NgRx effects in your applications:
- Add NgRx effects -
@ngrx/effects
. - Build the effect to process in action and dispatch the success and fail actions.
- Initializer the effects module in the root module.
- Register feature module effects.
- Process the success and fail actions in the reducer.
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 typed the state with an interface and build selectors.
- Strongly typed the actions by building action creators.
- Dispatch in action to kick off the operation
- Build the effect to process that action and dispatch a success or fail action.
- 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.