Building Single Page Applications Should be Easy, Angular and ngrx to the Rescue!
Because all Superheroes need Sidekicks, welcome ngrx, a Redux inspired state management system supercharged with RxJS.
Building Front-end applications should be easy, that’s what I used to say.
Is it different now? We’ll see…
It’s important for an application to have a clearly defined architecture, coding styles and an overall sense of structure.
Our job is to create a list of awesome GitHub users (demo), that we might be interested to follow.
Once the following action is performed by clicking the button, the related user is marked as followed.
Modeling the application’s state
A view is just a representation of state at some point in time. If I were to write a JSON structure which describes our application, it would look like this:
Analyzing the data, we can deduct that our app has already fetched a collection of users from Github. We know this because the items array contains the user “sindresorhus” (big Hello).
The xhrStatus inProgress and hasError properties, tell us the Ajax request is finished with no errors.
The followedUsers object is normalized and contains information about which user is already followed.
Notice that our state is mixed, composed of server and purely front-end data. You can read more about the types of application state.
Identifying the data models
GitHub, in its might, gives us the Users and User DTO’s (domain transfer objects).
We keep track of which user is followed, by creating the FollowedUsers model.
The FollowedUser model is derived from the complete User.
The fact that we duplicate the login information is to highlight a pattern often found in NoSQL databases, where duplication is used to improve performance and limit the number of queries.
Imagine that the following user button needed the login information. We would be able to serve it on the spot, instead of querying the User items to get it.
Our last model is also purely front-end, the XhrStatus allows us to know when Ajax requests are in progress and when errors happen.
Putting it all together, we finally have our AppState’s model.
Soon, we’ll find out what to do with it! Until then, we have another task on our hands.
Analyzing the design and building the presentational components
Imagine that in your left hand you are holding the JSON we just saw, the data model of our app, and, in your right hand, you’ll hold the HTML markup built with pure, presentational components.
The presentational components do not need any fancy integration. Make sure they are able to handle the data model and mock everything out.
If we look closer at our design, we realize that it’s composed out of a Card component, repeated over and over again.
The Card holds the user info, being composed of smaller components, like Avatar and FollowedButton.
This component uses multiple slot transclusion. This way it’s possible to nest other components inside, like the Avatar and FollowedButton.
Building the application’s data store
At this point, ngrx comes in. We need a place to store our data and this library provides an ideal place to do it.
@ngrx/store — RxJS powered state management for Angular applications, inspired by Redux
Setting up the store is pretty easy; for our app, I have decided to use it as a separate module.
Say hello to reducers
Reducers are pure functions. They are the backbone of the store and they represent the way state gets updated.
A reducer receives the current state and returns a new, immutable one, with the desired changes.
The reducers are combined, each one handling a slice of the state.
In our application, we’ll only have three reducers: usersReducer, followedUsersReducer and xhrStatusReducer.
Its job is to set the user data from GitHub. When the reducer function is called by the ngrx store, it will receive the initial state and an action. If the action type is SET_USERS, the reducer will return the payload as the state, populating our store with the data.
The followedUsersReducer responds to the FOLLOW_USER and UNFOLLOW_USER actions, enabling us to mark a user as being followed or not.
The last reducer in our belt is the xhrStatusReducer. When the SET_XHR_SUCCESS action happens, the isProgress and hasError properties are set to false. In case the store gets dispatched an action of type SET_XHR_ERROR, the hasError property is set to true.
Reducers update the state when actions are dispatched to the store.
The store has a dispatch method which accepts actions. Actions are emitted by container components.
An action has a type and a payload property, which holds the actual data for the interested reducers to process.
Reducers react selectively, depending on the action type.
Getting familiar with side effects
@ngrx/effects — Side Effect model for @ngrx/store to model event sources as actions.
- Listen for actions dispatched from @ngrx/store;
- Isolate side effects from components, allowing for more pure components that select state and dispatch actions;
- Provide new sources of actions to reduce state based on external interactions such as network requests, web socket messages and time-based events.
Real life explanation
Imagine we dispatch the GetUserAction and the SetUserAction onto the store.
The usersReducer which reacts to the SET_USER action type, get’s the action’s payload (in our case the users from GitHub) and returns a new shiny slice of the state.
But wait, there are no users to set! The users are not in our state, they are the result of an async operation.
This is where effects come in. They hook into actions, generate side effects (like Ajax calls), returning other actions packed with data needed to update the store.
The getUsers$ effect hooks into GET_USERS action and, instead of letting it pass, calls the userService to fetch the users from GitHub.
In case the operation is successful, it dispatches the SET_USERS and SET_XHR_FINISHED actions.
Querying data using selectors
Selectors enable us to query the state just like a database.
They are powerful, memoized and can be composed to generate more complex selectors.
The getUsers selector is composed of the getUsersState and getItems selectors. It navigates through the state tree, giving us the desired property.
The isUserFollowed selector is a perfect example of state computation, allowing us to interpret the state and generate derived data to fit our needs.
Connect the application’s state with the presentational layer
Finally, we reached the point where we can integrate the store with our presentational components.
This fancy procedure is done using two container components, UsersListComponent and FollowUserContainerComponent.
Containers have really shallow templates and they use presentational building blocks like the Card, Avatar, Followed and FollowButton. They are the only DOM elements allowed to interact with the store.
Containers subscribe to the store through selectors to get the needed data.
The component gets the users from the store and iterates over them.
UsersList component’s HTML template
FollowUsers container component
By subscribing to the store and checking if the user is followed or not, the component decides to show the FollowButton or the Followed components.
That’s it, job done
This is how you build an app quickly, with a clear and extensible architecture.
Just remember, always start with modeling your state, think of it as a NoSQL database.
Do it hand in hand with building the presentational components. Use single and multiple slot transclusion as much as possible and keep them really granular, enforcing the SREP principle.
When you’re done, create the store, reducers, actions, effects, and selectors.
The last part is only about creating container components to hook everything up together.
Do consider using Angular’s style guide and a modular architecture.
I hope you enjoyed this article and its fast pace. The next one will be focus on testing our application.