TL;DR: In this article, we’ll get a quick refresher on NgRx basics and get up to speed on more features of the NgRx ecosystem. We'll then walk through how to add Auth0 authentication to an NgRx app. You can access the finished code for this tutorial on the ngrx-auth GitHub repository.
NgRx: From Humble Beginnings to a New Ecosystem
I remember when Rob Wormald, an Angular Core Team member, first wrote ngrx/store at the end of 2015. It was a tiny library implementing Redux in Angular (
store.ts
was a single file with 66 lines of code!). Fast forward a few years and NgRx is much more than a simple library — it’s an entire ecosystem with over 120 contributors! Learning to think in the Redux pattern, while also keeping up with the various structures and pieces of NgRx, can be a challenge at first. Don’t worry, though — we’ve got your back.In this article, we’ll do a quick refresher on NgRx concepts and learn about what has changed in its ecosystem in the last year. Then, we’ll look at how authentication works in NgRx and how to add Auth0 to an NgRx application.
Let’s get started!
“I'm learning about adding authentication to an NgRx application.”
Tweet This
Basics of NgRx - A Recap
While we have a great tutorial on managing state with
and implementing Auth0, a lot has changed with NgRx since then. Let’s do a quick recap of what hasn't changed — the fundamental concepts of NgRx and the Redux pattern.ngrx/store
State is an immutable data structure describing the current status of parts of your application. This could be in the UI or in the business logic. State can represent a range of different things, from whether to show a navigation menu to whether a user is logged in.
You can access that state through the store. You can think of the store as the main brain of your application. It’s the hub of anything that needs to change. Since we’re using Angular, the store is implemented using an RxJS subject, which basically means it’s both an observable and an observer. The store is an observable of state, which you can subscribe to, but what is it observing?
The store is an observer of actions. Actions are information payloads that describe state changes. You send (or dispatch) actions to the store, and that’s the only way the store receives data or instructions on how to update your application’s state. An action has a type (like “Add Book”) and an optional payload (like an object containing the title and description for the book Return of the King).
Actions are the bedrock of an NgRx application, so it’s important to follow best practices with them. Here are some pro tips on actions from Mike Ryan’s ng-conf talk Good Action Hygiene:
- Write your actions first. This lets you plan out the different use cases of your application before you write too much code.
- Don’t reuse actions or use action sub-types. Instead, focus on clarity over brevity. Be specific with your actions and use descriptive action types. Use actions like
and[Login Page] Load User Profile
. You can then use the switch statement in your reducer to modify the state in the same way for multiple actions.[Feature Page] Load User Profile
- Remember: good actions are actions you can read after a year and tell where they are being dispatched.
Once the store receives an action to change the state, pure functions called reducers use those actions along with the previous state to compute the new state.
If you’re feeling lost on the basics of the Redux pattern, one of my favorite introductory resources is Dan Abramov’s Egghead course on getting started with Redux.
Going Further with NgRx
With the basics covered, let’s look at some additional concepts in NgRx we'll need for this tutorial.
Selectors
Selectors are pure functions that are used to get simple and complex pieces of state. They act as queries to your store and can make your life a lot easier. Instead of having to hold onto different filtered versions of data in your store, you can just use selectors.
For example, to get your current user, you’d set up a selector by first exporting this function:
export const selectAuthUser = (state: AuthState) => state.user;
You typically put these pure functions in the same file as your feature’s reducer functions and then register both the feature selectors and individual state piece selectors in the
index.ts
file where you register your reducers with NgRx. For example:// state.index.ts // Feature selectors get registered first. export const selectAuthState = createFeatureSelector<fromAuth.State>('auth'); // Then you can use that feature selector to select a piece of state. export const selectAuthUser = createSelector( selectAuthState, fromAuth.selectUser, // the pure function that returns the user from our auth state );
You can then easily use that selector in your component or your route guards:
this.user = this.store.pipe(select(fromAuth.selectAuthUser));
To learn more, check out this talk from ng-conf 2018 on selectors by David East and Todd Motto.
Effects
Effects (used with the library
) are where you connect actions to side effects or external requests. Effects can listen for actions, then perform a side effect. That effect can then (optionally) dispatch a new action to the store. For example, if you need to load data from an API, you might dispatch a @ngrx/effects
LOAD_DATA
action. That action could trigger an effect that calls your API and then dispatches either a LOAD_DATA_SUCCESS
or LOAD_DATA_FAILURE
action to handle the result. Here’s how that looks:@Effect() loadData$ = this.actions$.ofType(LOAD_DATA) .pipe(switchMap(() => { return this.apiService.getData().pipe( map(data => new LOAD_DATA_SUCCESS(data)), catchError(error => of(new LOAD_DATA_FAILURE(error))) ); });
You can see that the effect is listening for the
LOAD_DATA
action, and then calls the ApiService
to get the data and return a new action.Using effects ensures that your reducers aren’t containing too many implementation details and that your state isn’t full of temporary clutter. For more information, check out this tutorial from Todd Motto on using effects.
NgRx Schematics
One of the most common complaints about NgRx is the amount of repetitive code that it takes to set up an application and add new features (never say "boilerplate" around NgRx Core Team member Brandon Roberts!). Part of this is due to the NgRx philosophy to focus on clarity over brevity, and to front-load the work of app architecture so that future feature additions are easy and straightforward. At the same time, there can be a lot of repetition in the code you write — adding a new reducer, set of actions, effects, and selectors for each new feature. Luckily, Angular CLI schematics are here to help.
Schematics for the Angular CLI are blueprints to generate new code quickly and are a huge time-saver. NgRx has its own set of schematics that you can take advantage of when writing your application. These schematics follow best practices and cut out lots of repetitive tasks. They also automatically register your feature states, wire up your effects, and integrate into NgModules. Awesome!
There are NgRx schematics for:
- Action
- Container
- Effect
- Entity
- Feature (generate an action, reducer, and an effect and automatically wire them up to a module!)
- Reducer
- Store
To get started with schematics, first install them in your project:
npm install @ngrx/schematics --save-dev
Then, set the NgRx schematics as your defaults in your CLI configuration:
ng config cli.defaultCollection @ngrx/schematics
Once that’s done, you can simply run CLI commands to generate new NgRx items. For example, to generate the initial state for your application, you can run:
ng generate store --name State --root -m app
This will generate a store for you at the root of your application and register it in your
AppModule
. Pretty cool!For more information on NgRx schematics, check out the repository readme and this excellent ng-conf talk by Vitalii Bobrov.
About the author
Sam Julien
Director of Developer Relations