What is Ngrx?
Ngrx enables us to manage the state of our application. It is a client-side data store that contains the memory of the app. This data store can contain frequently accessed information such as user profile information, or app wide preferences such as light or dark mode. It can also store information from backend API calls and store the results of those calls so that the database does not need to be queried again for the same information.
One of the main benefits of having frequently accessed data readily available on the front end is that data can be served faster to the user, cutting down on time needed to make API calls and query the database. This translates into less time waiting for content to appear on the screen and users of our app feel they are using a faster and more responsive application.
Another situation that Ngrx handles well is when you have nested components and a parent component wants to pass properties to a grandchild component without having to pass data through the direct child component to get data to the grandchild component. This process can get messy if the hierarchy is deep, and Ngrx enables the component that needs data to get it from the state directly.
Passing properties from parent to child
44
444
Using store to get access to those same properties
Ngrx implements the Flux pattern. The primary goal of this pattern is to provide a predictable state container, which means that the data contained in the store can only be changed in very strict ways, thus giving us a measure of confidence in the integrity of the data it contains. Ngrx keeps track of a single state and uses actions to handle changes in the state.
3 Principles of Ngrx
There are 3 fundamental principles of the state
1. The State is the single source of truth
There is only one store in the application, which is stored in a single state tree. The main function of the store is to store data and then return this data when requested.
2. The state is read-only
There is only one way to change the state and that is by dispatching an action. An action is an instruction to the state, which can also optionally include some data.
3. Any changes to the state are made with pure functions
A pure function will always return the same output given the same input. A pure function will not mutate or access other properties outside of its scope.
Dispatching an action runs a set of pure functions called reducers. Reducers handle modification of the state in different ways based on the action they receive.
Let describe in more detail the key parts of the Ngrx architecture: the store, actions, reducers
Diagram of Ngrx cycle
The Store
At the core of the Ngrx architecture is the store, which is just a nested javaScript object. You can never just reach in and change a property of the store. It must be done by dispatching an action.
Actions
An action is a plain JavaScript object that is sent to the store to modify the state. Each action contains a type, which is just a string, that contains some instruction to the state. In addition the action can contain an optional payload. The payload is any data that the store might need to update its state. Dispatching an action is the only way to modify the state.
Reducers
A reducer is a plain JavaScript function. The job of the reducer is to return a new version of the state. The reducer should be without side effects. It should only take an action’s payload and return a new state object.
Now we are able to discuss the 4 main steps in the Ngrx cycle
- Inside of a component, most commonly due to some user interaction, an action is dispatched
- The store receives the action and will call the root reducer, with the current state and the action
- The root reducer will combine the output of all of the reducers into a single state tree
- The store saves the complete state tree that was returned by the root reducer. This new state object is now the next state of the app.
Let’s tie everything together with a practical example.
In the app we have a UI element that we want users to see whenever they navigate to send a text message. This element is a text box with tips on how to craft an effective text message. The text box is collapsable and we would like to remember that the user chose to close this box when they go on to send their next message.
1. In the component we need to subscribe to the state.
constructor(
private store: Store<RootState>,
) {
}
2. When uses clicks to collapse the component, we want to dispatch an action.
toggleBestPracticesContent(status: boolean): void {
this.store.dispatch(ToggleTextingBestPractices(status));
}
3. This will fire an action called ToggleTextingBestPractices
export const ToggleTextingBestPractices = createAction(
'[Layout] ToggleTextingBestPractices',
props<{
status: boolean;
}>()
);
4. This action will flow through all the reducers until it finds an action name that matches. We will then return a new version of the state and have modified the property named ‘isBestPracticesHidden’ to false.
export function layoutReducer(state = initialState, action: All): LayoutState {
switch (action.type) {
case LayoutActionTypes.ToggleTextingBestPractices: {
return { ...state, isBestPracticesHidden: action.payload };
}
default:
return state;
}
}
5. We will create a selector in order more easily reference the property we need.
export const getIsTextingBestPracticeHidden = createSelector(
getLayoutState,
(state) => state?.isBestPracticesHidden
);
6. Back in our component, since we are subscribed to the store we will get updates that this property has changed when the user navigates back to the send message page.
constructor(
private store: Store<RootState>
) {
this.store.select(getIsTextingBestPracticesHidden)
.subscribe((status) => this.isTextingBestPracticesHidden = status);
}
7. Finally we can tell the component what open/close behavior to show
This information that we now have regarding the status of Texting Best Practices can be accessed from any component throughout the application, simply by requesting its status from the store.
Hopefully this article has given you a starting point with which to further explore Ngrx.