A Complicated NGRX Selectors Example

Continuing my series on NGRX selectors (see my previous article on the top 5 ways to misuse NGRX selectors), this one focuses on describing a full featured example of selectors for your reference. I won’t go through all the code but mainly focus on selectors.

Github Code

The App

 

The application I wrote is a simple (with somewhat complicated NGRX 😊) Star Wars API front-end that looks like this:

The user can navigate to the Episode to see the opening crawl:
The user can search for people on the API:
That’s it. The data is stored in NGRX and selectors are used so let’s get into the NGRX setup.

NGRX Setup

In the app.module, the root store is setup as shown:

        • The main thing here is using “forRoot” for StoreModule, EffectsModule and StoreRouterConnectingModule
        • Also important to instrument for devtools, which gives Redux DevTools (a chromium extension) support

    The reducers are setup as shown:

      • In this case, there is just AppState and the router reducer
      • AppState is defined as:
      • currentPerson keeps track of the guy we show on the main page (Luke)
      • lastPersonId keeps track of the personId that was used to get the currentPerson since that isn’t on the People object
      • apiStuff is the objects retrieved from the urls given in the films, starships and vehicles arrays
          — Used a different object here because when I put the hydrated films, starships, etc. on People, it caused too many state changes
      • Effects, reducers and actions are pretty standard and you can see them on github

Selectors

The root app state selectors are done as shown:

      • These are the only ones that should use getAppState
          — Never use getAppState in a component (maybe don’t even export it here??)
      • The other selectors will build off these
          — This is so important for memoization so that only things that change are recalculated

People Component Selector

For example, get only the properties we will use off the currentPerson:

      • This is done so that a view model can be returned using these four properties only – don’t care about the other properties from the API (getPeopleComponentData)
          — No need to return data to the component that it won’t use
      • Also, the height is in cm so we want to calculate that to show in the feet/inches format (e.g. 6’ 2”)
      • The frac method has been memoized so it only must run when it changes
      • Yes, this could have been done in a pipe but this gives the same performance with it being a memoized selector function and the logic for the height remains in the view model code

Opening Crawl Page

Now let’s look at the opening crawl page selector.

      • getFilms is a selector function to return all the film objects hydrated from the API
      • getEpisodeNumberFromRoute is using the selectRouteParam selector function
      • provided by NGRX (see my code for this) to get episodeNumber from the route

          — Here is the route definition (note the “:episodeNumber”):
      • The selector looks for the episode_id using episodeNumber in the films array
        • — If a film is found, it returns the opening_crawl string property
          — Otherwise it returns ‘Film not found!’
          — Note this selector is using existing data from state, no need to call an API since it has already been retrieved

Main Page Get Person Selector

Let’s look at the complicated situation of the main page. I wrote about this in the last post. Here it is to refresh our memory:

    Consider the following example where I have a Star Wars API project and I’m getting the person ID from the URL (in router state). There is also a text field where you can change the person ID and it is bound to selectedPerson. To do this right, many things need to be accounted for:

      • The URL must be updated when the person ID changes from the text field
      • The text field must be updated when navigating to the page from the URL
      • We don’t want to get the person from the API if the last person id is equal to the current (user hits back button)
      • When the user navigates away from the page, the selector shouldn’t get invoked so we don’t try to get the person with an undefined value

This is talking about this part of the app and the retrieving of the person from the API:

      • The “1” is in the URL and bound to the Select Person input box
        • — We want these to stay in sync

Here is the selector to accomplish this:

      • Here is getLastPersonId again being used
      • getPersonNumberFromRoute is using the same selectRouteParam function to get personNumber
      • Return a view model for the container component (main component of this first page)
          — currentPersonId is used to set the value of the input box (Select Person)
          — shouldGetPerson is a Boolean that tells the component whether to get or re-get the person

            • It’s nice to have that decision made in the selector because if another page needs to make the same decision, the code for it is already done

Now in the component, it is used as shown:

getPerson:
      • getPerson just dispatches the fetchPerson action to get it from the API
        • — It is only called when the route changes and we get a new view model from the selector
      • changeRoute is called when the Get Person button is clicked on the UI

When I enter 14 and click Get Person, I see this:

      • The 14 in the route is the source of truth for this page
      • The input box contains it and can change it
      • The selector prevents us from retrieving the info for 14 when we navigate around the site and come back to the main page

Feature Module Selectors

A complicated example of NGRX selectors wouldn’t be complete without showing how to setup a feature module selector.

The Search People page on the app is a feature module that lazy loads when you click on the link.

This is the setup in the search.module:

      • Notice that forFeature is now used for StoreModule and EffectsModule
      • searchFeatureKey (“SearchState” here) is the string that will be the root node of this store on the state tree

Selectors on the Feature

searchFeatureKey is also used to create the feature selector:

These are the rest of the selectors:
      • These all build upon each other
        • — getSearch uses getSearchState
          — getSearchResponse uses getSearch
          — getSearchResults uses getSearchResponse

The state is defined as shown:

      • IPeopleFull is just the People object from the API/
      • ISearchResponse is what is returned from the API call
      • ISearch is for the whole feature to give it room to grow

Notice on the search page, there is a link “Back to main page”. This has the routerLink of “/person/:personNumber” so I need to get the personNumber but that isn’t in the URL on the search page. So, what I do is get the lastPersonId from root state.

In the template then:

Conclusion

There is a lot in this article on selectors but there is even more in the github code so I encourage you to look at it. Even just to look at the NGRX setup, reducers, effects, etc. I will be adding unit testing to the repo shortly so check that out when the next article comes out.

About Intertech

Intertech is a Software Development Consulting Firm that provides single and multiple turnkey software development teams, available on your schedule and configured to achieve success as defined by your requirements independently or in co-development with your team. Intertech teams combine proven full-stack, DevOps, Agile-experienced lead consultants with Delivery Management, User Experience, Software Development, and QA experts in Business Process Automation (BPA), Microservices, Client- and Server-Side Web Frameworks of multiple technologies, Custom Portal and Dashboard development, Cloud Integration and Migration (Azure and AWS), and so much more. Each Intertech employee leads with the soft skills necessary to explain complex concepts to stakeholders and team members alike and makes your business more efficient, your data more valuable, and your team better. In addition, Intertech is a trusted partner of more than 4000 satisfied customers and has a 99.70% “would recommend” rating.