Angular Redux - NgRx Angular, NgRx store, NgRx Effects, NgRx selectors

In this post you will learn why do you need NgRx inside Angular and how you can bind it to your Angular application.

The first and most important question here is what is NgRx? To understand why do you need NgRx you must understand the concept of Redux.

Redux is a state management with a single flow of data

What is Redux

We have our state, then our component can dispatch an action, then our action can change state and all our components are subscribed to our state to apply some changes to our component.

If you don't know or understand Redux it doesn't make any sense for you to dive into NgRx. To master NgRx you first need to understand the concepts of Redux.

NgRx is simply an implementation of Redux inside Angular

What typically we want to do with NgRx?

  • We have global state and we can apply changes to it
  • All our data in application is in single place
  • We can do asynchronous things (HTTP calls for example)

And the question that I get really often is "Why do I need NgRx?" and "Why NgRx is so popular?" if I can just use Angular service inside component and that's it.

This approach is not sharable at all. We are not talking here about state management, you just fetch some data and render them in component. If you have lots of components and modules and you have a huge application then you need something for state management. You need some rules how people must work with your state. This is why Redux and NgRx are so popular and this is why I never saw a company who are doing Angular without usage of NgRx.

Installing packages

The goal of this post is on a simple example to bind NgRx to Angular project and see how it works. First of all we must install several packages.

yarn add @ngrx/store
yarn add @ngrx/store-devtools
yarn add @ngrx/effects

We need @ngrx/store for core functionality of NgRx, @ngrx/store-devtools to debug our application and @ngrx/effects to implement asynchronous things inside NgRx.

I also already prepared for us a small project. We have there a posts component which is empty and a service where we get a list of posts. We also have here an interface for our post.

// src/app/posts/types/post.interface.ts
export interface PostInterface {
  id: string;
  title: string;
}
// src/app/posts/services/post.service.ts
@Injectable()
export class PostsService {
  getPosts(): Observable<PostInterface[]> {
    const posts = [
      { id: '1', title: 'First post' },
      { id: '2', title: 'Second post' },
      { id: '3', title: 'Third post' },
    ];
    return of(posts).pipe(delay(2000));
  }
}

Our service returns for us a stream of data with delay like an API.

Binding store

Now it is time to bind NgRx store to our application.

// src/app.module.ts
NgModule({
  imports: [
    ...
    StoreModule.forRoot({}),
  ],
})
export class AppModule {}

As you can see we don't provide anything inside because to want to define reducers in every module separately.

After this it is really important to bind NgRx devtools for debugging.

// src/app.module.ts
NgModule({
  imports: [
    ...
    StoreDevtoolsModule.instrument({
      maxAge: 25, // Retains last 25 states
      logOnly: environment.production, // Restrict extension to log-only mode
      autoPause: true, // Pauses recording actions and state changes when the extension window is not open
    }),
  ],
})
export class AppModule {}

We injected new module from NgRx where inside we defined that we can see last 25 items, in production it is restricted to logs and we pause devtools when they are not opened.

Redux devtools

As you can see in browser we have empty Redux Devtools which means we correctly configured a package.

Creating state

Our next step will be to create a state for our posts. So posts is our module and we want to store some data inside.

// src/app/posts/types/postsState.interface.ts
import { PostInterface } from './post.interface';

export interface PostsStateInterface {
  isLoading: boolean;
  posts: PostInterface[];
  error: string | null;
}

Here we defined a state for our posts module. As we load data to the page we want isLoading to show spinner, posts where we will store our data and error if we need to show an error to the user.

Creating actions

Our next step here is to create actions. What are actions? These are user events which can change our properties isLoading, posts, error inside a state.

// src/app/posts/store/actions.ts
import { createAction} from '@ngrx/store';

export const getPosts = createAction('[Posts] Get Posts');

Here we defined an action. We use createAction function to create it. Also we prefixed name of action with [Posts] so we directly see that they belong to posts module.

Creating a reducer

Now we must create a reducer.

// src/app/posts/store/reducers.ts
import { createReducer, on } from '@ngrx/store';
import { PostsStateInterface } from '../types/postsState.interface';
import * as PostsActions from './actions';

export const initialState: PostsStateInterface = {
  isLoading: false,
  posts: [],
  error: null,
};

export const reducers = createReducer(
  initialState,
  on(PostsActions.getPosts, (state) => ({ ...state, isLoading: true })),
);

Inside reducer we define our initialState which is PostsStateInterface. Also we created reducers with createReducer function. We provide inside our initial state and different actions on which we want to react. In this case we say that when getPosts action happens we want to set isLoading in true.

Adding reducer to the store

Now we must inject our StoreModule inside posts module to bind our reducer to it.

// src/app/posts/posts.module.ts
...
import { reducers } from './store/reducers';

@NgModule({
  imports: [
    ...
    StoreModule.forFeature('posts', reducers),
  ],
})
export class PostsModule {}

Here we define posts property inside our Redux state and we pass our reducers in it.

Binding posts state

As you can see now our state is available inside Redux.

Now we get to the most interesting part. We want to change our state from the component. This is why inside our posts component we need to dispatch our getPosts action.

But in order to do that we must create a global state for the whole Redux store.

// src/app/types/appState.interface.ts
import { PostsStateInterface } from '../posts/types/postsState.interface';

export interface AppStateInterface {
  posts: PostsStateInterface;
}

This is just an interface which combines all our states. Now we can use it inside a component

export class PostsComponent implements OnInit {
  constructor(private store: Store<AppStateInterface>) {}

  ngOnInit(): void {
    this.store.dispatch(PostsActions.getPosts());
  }
}

We injected Store from NgRx and provided AppStateInterface inside. Inside ngOnInit we dispatched the action that we created earlier.

First dispatch

As you can see in browser we successfully dispatched our getPosts action on initialize and our Redux state was changed.

So this is the basics of using NgRx: we create reducers for specific module and we create actions that we can dispatch to change the state.

Adding selectors

Now the question is how we can use this state inside our component and how we can render our isLoading property for example? For this we must use selectors. Just to remind you selectors in Redux allow us to get properties from Redux state. And we have something similar in NgRx.

// src/app/posts/store/selectors.ts
import { createSelector } from '@ngrx/store';
import { AppStateInterface } from 'src/app/types/appState.interface';

export const selectFeature = (state: AppStateInterface) => state.posts;

export const isLoadingSelector = createSelector(
  selectFeature,
  (state) => state.isLoading
);

Here we created a new file selectors and defined 2 functions inside. selectFeature is a helper function which we use to create selectors later. It returns a slice of Redux state which returns posts.

After this we create isLoadingSelector by using createSelector function. Inside we use selectFeature and additional function to get isLoading property.

Now we can use isLoadingSelector property inside our component.

export class PostsComponent implements OnInit {
  isLoading$: Observable<boolean>;

  constructor(private store: Store<AppStateInterface>) {
    this.isLoading$ = this.store.pipe(select(isLoadingSelector));
  }

We created isLoading$ property with this.store.pipe(select()). This is a way to read properties from our Redux store inside Angular. Now it is just a stream of data for us that we can render directly in component.

<div *ngIf="isLoading$ | async">Loading...</div>

As you can see in browser we rendered infinite loading because we don't have any success yet to change it back. But this is how you select fields from Redux state.

Adding effects

And now we must talk about asynchronous effects. Most often we want to get data from API. Now you know everything about dispatch, reducers, actions, selectors but we miss the knowledge how to work with API.

This is exactly what we have inside our service. Our method getPosts will get some data only after 2 seconds. In Redux we typically solved such problem with redux-thunk. In Angular and NgRx we do if different by writing listeners to specific events inside effects.

We just dispatch an action at the beginning of the fetch and then at the end with fetched information.

First of all we need to create 2 additional actions - for success and for failure.


import { createAction, props } from '@ngrx/store';
import { PostInterface } from '../types/post.interface';

export const getPosts = createAction('[Posts] Get Posts');
export const getPostsSuccess = createAction(
  '[Posts] Get Posts success',
  props<{ posts: PostInterface[] }>()
);
export const getPostsFailure = createAction(
  '[Posts] Get Posts failure',
  props<{ error: string }>()
);

The only difference is that we pass a second parameter inside createAction to be able to pass data inside our action.

Now we must update our reducer and apply changes when these 2 actions happen.

export const reducers = createReducer(
  initialState,
  on(PostsActions.getPosts, (state) => ({ ...state, isLoading: true })),
  on(PostsActions.getPostsSuccess, (state, action) => ({
    ...state,
    isLoading: false,
    posts: action.posts,
  })),
  on(PostsActions.getPostsFailure, (state, action) => ({
    ...state,
    isLoading: false,
    error: action.error,
  }))
);

On success we return isLoading to false and we write posts from action to state. On error we update an error field.

We also want to add new selectors for our list of posts and error field.

export const postsSelector = createSelector(
  selectFeature,
  (state) => state.posts
);

export const errorSelector = createSelector(
  selectFeature,
  (state) => state.error
);

Writing effects

Now is the most complicated part. It is difficult but you need to understand it once as most of the effects are written in the same way.

// src/app/posts/store/effects.ts
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { catchError, map, mergeMap, of } from 'rxjs';
import { PostsService } from '../services/posts.service';
import * as PostsActions from './actions';

@Injectable()
export class PostsEffects {
  getPosts$ = createEffect(() =>
    this.actions$.pipe(
      ofType(PostsActions.getPosts),
      mergeMap(() => {
        return this.postsService.getPosts().pipe(
          map((posts) => PostsActions.getPostsSuccess({ posts })),
          catchError((error) =>
            of(PostsActions.getPostsFailure({ error: error.message }))
          )
        );
      })
    )
  );

  constructor(private actions$: Actions, private postsService: PostsService) {}
}
  • Here we created new file effects.ts with class inside
  • We use createEffect function to register an effect
  • We inject actions$ in constructor to get access to the stream of actions which happens in NgRx
  • We use ofType(PostsActions.getPosts) to react to the specific action when it happen
  • We can our getPosts method inside mergeMap to fetch posts from API
  • We handle success with map function which returns our success action that we want to dispatch
  • We handle error with catchError which must return a failure action that will be dispatched

And I totally understand that it is difficult at first. But after you write it again and again several times you will memorize it.

Effects

As you can see in browser when we dispatch getPosts in our component our effects make an API call and dispatch success action with data afterwards.

Rendering information

How we just need to render all fields in our component.

export class PostsComponent implements OnInit {
  isLoading$: Observable<boolean>;
  error$: Observable<string | null>;
  posts$: Observable<PostInterface[]>;

  constructor(private store: Store<AppStateInterface>) {
    this.isLoading$ = this.store.pipe(select(isLoadingSelector));
    this.error$ = this.store.pipe(select(errorSelector));
    this.posts$ = this.store.pipe(select(postsSelector));
  }
}
<div *ngIf="isLoading$ | async">Loading...</div>

<div *ngIf="error$ | async as error">{{ error }}</div>

<div *ngFor="let post of posts$ | async">
  {{ post.title }}
</div>

As you can see NgRx helps tremendously to move whole logic outside of the components. It is extremely efficient for big, scalable applications.

Want to conquer your next JavaScript interview? Download my FREE PDF - Pass Your JS Interview with Confidence and start preparing for success today!

📚 Source code of what we've done
Did you like my post? Share it with friends!
Don't miss a thing!
Follow me on Youtube, Twitter or Instagram.
Oleksandr Kocherhin
Oleksandr Kocherhin is a full-stack developer with a passion for learning and sharing knowledge on Monsterlessons Academy and on his YouTube channel. With around 15 years of programming experience and nearly 9 years of teaching, he has a deep understanding of both disciplines. He believes in learning by doing, a philosophy that is reflected in every course he teaches. He loves exploring new web and mobile technologies, and his courses are designed to give students an edge in the fast-moving tech industry.