Resolving Data in Angular 2, 4, and 5; refactoring components; and moving to ngrx/store

Daz
Daz
Nov 9, 2017 · 7 min read

UPDATE 26th Feb 2018: I have added a new v6 where I switch to using unionize to help reduce the amount of boilerplate needed when working with NgRx.

I’ve worked on a few Angular projects over the past 18 months. There’s a lot to learn from working on real-life apps, a large code base, with lots of components. But, I also like creating simple sketches. Small example apps that let you focus on one specific detail. Here I document a sketch I’ve created that looks at various ways of loading data in Angular.

This particular sketch is a partial review of my journey with Angular. I started with loading data directly in components — easy to understand but not scalable. Then I really got into the Angular Router and started using the data resolvers. Then, eventually, I get into ngrx — the RxJS powered Redux implementation targeted at Angular.

Here’s the sketch. The code (tagged with various milestones) is available on GitHub. I make use of data from the JSONPlaceholder service, and it looks a bit like this:

Simple app to demonstrate data resolving in Angular

I will include snippets of code along the way. If you want to see the whole thing you can git checkout the various version tags to see the code as it develops, browse the version tags on GitHub, or refer to the tags on the repository releases page.

Here’s a model interface to represent the data that’s coming back the JSONPlaceholder API for users:

And I created a simple service that I can inject to access the API. I have added a utility function that will convert a User to a Partial<User> so I can simulate getting a summary feed of users. The JSONPlaceholder API returns the same full data for the list of users and when you request an individual user. The scenario I want to simulate is getting a feed of summary information and then making further requests to get detail information on each record. The trimUser function just picks the id, name, and username fields.

You will also notice that I add a delay to the Observable to simulate a slow network. I want to make it visibly obvious where data is loading. This is a trick I use a lot when developing as it’s easy to forget the difference it makes when running over a network compared to running locally on my super-computer.

There are two methods in this service, one returns the list of users, the other returns detailed data for a specified user:

Initial Version (v0)

In this first version I inject the data service directly into the component:

Notice how I need to keep track of if the user list has been loaded yet, so that I can conditionally display the user list, and give some feedback to the user.

For the contact detail page I need to inject the service, and the route, so that I can subscribe to the ID parameter from the route and fetch the appropriate user record:

User List Resolver (v1.1)

The first improvement is to move the responsibility of fetching the data out of the component, and into a separate object. This object will be used by the Angular Router to load the data before navigating to the page, so when the component is ready, the data has already been fetched.

The resolver is added to the list of routes:

User Detail Resolver (v1.2)

The user detail resolver gets the ID param from the route parameters and passes this into the service call:

It is added to the route config:

And then, in the component, the route data is subscribed to in order to get the user for display:

Container Components (v2)

In the first version (v0) the components were doing several things, breaking the “single responsibility principle”. Each component was responsible for fetching data, and displaying it.

Now we split out the actually resolving of data but the components still have to know how to get the data they require from the context, and display it. This context is injected in to the component (as the route). A popular pattern is to separate out components into “container” components and “presentational” components.

Presentational components take basic data as inputs and know how to render them. They don’t do any logic, they just accept inputs. Container components wrap the presentational components, providing them with the needed data. The container components know how to get the appropriate data from the context they are provided.

Here is the detail component simplified down to just a presentational component:

It just has @Input‘s — there’s no logic. I like to leave a comment to remind me that this is a presentational component — so I don’t come along and complicate it later.

The work in this presentational component is done in the template. It just provides markup and styling of the input content:

I created a separate container component to wrap this. It just knows how to get the required data from the route:

Recap

As things stand, I’m quite happy with the architecture of this simple app. Responsibilities are separated along sensible lines, and nothing really has too much responsibility. The data is fetched by the service, which is called from the router resolvers, which is read by the container components, and displayed by the presentational components. Sounds reasonable.

This is all well and good in a simple example app. But, from my experience working on a large Angular app, I know this kind of architecture is hard to stick to once you start adding in more features, things get a bit more fuzzy, and the boundaries are not clear.

Real world apps need more advanced async handling, data fetching, use of local storage, caching, etc. This is when I got excited about Redux, and in particular the RxJS-based implementation NgRx for Angular. While the boilerplate can make it seem unnecessary on a small sketch like this, I’m sold on the benefits one one-way data flow after working on larger projects.

I made two steps in implementing NgRx in this example. First I added a purely Store and Effects based approach, then I looked at the ngrx Entity package.

NgRx Store (v3)

The main addition in this version is the global state managed by NgRx store. This is not meant to be a general “how to use redux” style example, I’m specifically thinking about strategies for resolving data.

In this first example, the store state has two parts. One for the users list and one for the user detail. It’s defined with the following interface, and the two separate reducers:

Then, local to the user list reducer, I have the state specific to this part. NgRx has a concept known as “fractal state management”. You can separate out code into feature modules. This works with Angular’s lazy loading. I’ve found it easier to have a single global set of definitions, unless I know the state is only ever used in a particular context. Here is the user list state interface and a default initial state:

A common pattern in NgRx is to dispatch a LOAD action to initiate fetching of data. The data is fetched asynchronously and then either a LOAD_SUCCESS or LOAD_FAILURE action is dispatched. This isolates side effects and keeps the rest of the state management pure.

Here’s the reducer, that just updates the state in response to the actions:

The data fetching happens in an NgRx Effect:

The NgRx store and effects are added to the main App NgModule:

To link this up with the Angular components, the presentational components don’t need to change. They only accept inputs, and the container components will provide the same as before. The container components need updating to select the data they want from the store.

On the listing page this is straightforward. The component just selects the users and the loading state:

The template for the component unwraps the Observable, iterates through each item, and passes each user object to the user card presentational component:

NgRx Entity (v4)

Rather than having separate parts of state for the user list and the current user details, I want to have a single repository of users. To do this I will make use of the Partial type in Typescript. In particular, I will define the repository of users to be of type Partial<User> so that I can store both the summary feed and the full user detail objects.

This will improve the performance of the app, by being able to show partial data while loading full details from the server.

I will make use of the NgRx Entity package to reduce the amount of boilerplate code needed to work with a repository of User entities in the store.

Previously I had two distinct sets of actions for the user list and user details. I now have a single set of actions:

I create a userAdapter to manage the repository of users in the state from the provider given by NgRx Entity:

Then, make use of the utilities provided by the adapter when creating the new reducer:

The summary success case adds many results. The detail success case either adds the detail (in the case it doesn’t exist) or updates the existing (possibly partial record).

The changes required to the container components are fairly trivial. They must dispatch using the new Actions, and select the data using the new selectors created using the entity adapter.

Unionize (v6)

It is possible to massively cut down the amount of boilerplate needed when working with NgRx by using the unionize library. I demo this in v6 of the repo. The unionize library contains helpers for creating actions, reducers, filtering the actions$ observable in Effects, and dispatching actions. In this version I refactor the NgRx code into an AppStateModule .

Wrap up

This example has been focused on different strategies for fetching data in an Angular app. Starting with a simple service that is injected directly into components, through various steps of refactoring to improve the design. Some refactoring to separate into container and presentational components, and then moving to a Redux-inspired architecture using NgRx.

This use of Effects may be sub-optimal. As mentioned in this post it can lead to unwieldy code. So the next step will be to implement an improvement based on the recommendations from this other article.

Please get in touch with any questions, or feedback. In particular, I’d love to hear about other strategies or improvements that could be made.

Thanks,

Darren

Daz

Written by

Daz

Geocities Developer Expert. Keywords: Angular, TypeScript, JavaScript, Cyril Live Coding, Functional Programming

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store