NgRx Effect vs Reducer vs Selector
Tags: angular, ngrx, typescript, challenge
Published: 2022-11-18 14:12:20

## Welcome to Angular challenges #2.

> _The aim of this series of Angular challenges is to increase your skills by practicing on real life exemples. Moreover you will have to submit your work though a PR which I or other can review; as you will do on real work project or if you want to contribute to Open Source Software._

The idea of the second challenge comes from a real life exemple. In NgRx Store, you will find the following concepts: **Effects**, **Reducers** and **Selectors**. And I often see that developers misused them, and more importantly **Selectors** _(which is a key concept)_ are often misunderstood and underused.

In this challenge, you have a working application using **NgRx global store** to store our data. But you will have to refactor it to **transform** the necessary data in the template at the **right place** using the **right NgRx concept**. 

If you haven't done the challenge yet, I invite you to try it first by going to [Angular Challenges](https://github.com/tomalaforge/angular-challenges/blob/main/apps/ngrx-1/README.md) and then coming back to compare your solution with mine. _(You can also submit a PR that I'll review)_

---

For this challenge, you have to display the full list of activities containing the following information (name, main teacher, all teachers practicing the same activity if user is admin)

![activity dashboard](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/10oecsgipwanmeim1uae.png)

To do this, we will start by fetching the backend to retrieve our user and all activities that has the following shape:

```typescript
export const activityType = [  'Sport',  'Sciences',  'History',  'Maths',  'Physics',] as const;
export type ActivityType = typeof activityType[number];

export interface Person {
  id: number;
  name: string;
}

export interface Activity {
  id: number;
  name: string;
  type: ActivityType;
  teacher: Person;
}
```
For side effect, NgRx use a concept called **Effect**. This let us isolate our backend request from the rest of the application. To trigger our Effect, we need to dispatch an **Action**. An Action is a **unique** event. 

> _**NgRx Hygiene: UNIQUE** is a very important word: You **should not reuse** action even if you want to trigger the same Effect or Reducer_

Let's create two actions: One for fetching the user information, and one for fetching the list of activities.

```typescript
export const loadActivities = createAction('[AppComponent] Load Activities');
export const loadUsers = createAction('[User] Load User');
```

> _The shape of an action should follow some rules : within square brackets shows where the action is being dispatched followed by a brief description of what the action is doing. This can be very helpful when you need to debug your application using the Redux DevTool._

We can now dispatch our action inside our component hook ngOnInit.

```typescript
ngOnInit(): void {
  this.store.dispatch(loadActivities());
  this.store.dispatch(loadUsers());
}
```

> _**NgRx Hygiene:** you should not have multiple dispatch. A single Action can trigger multiple Effects or multiple Reducers. _

So we can already modify this piece of code:

```typescript
// single action to dispatch multiple effect to fetch all necessary data
export const initApp = createAction('[AppComponent] initialize Application');

// ngOnInit hook inside our AppComponent
ngOnInit(): void {
    this.store.dispatch(initApp());
  }
```
Now we can write our effect to trigger our data fetching:

```typescript
@Injectable()
export class UserEffects {
  loadUsers$ = createEffect(() => {
    return this.actions$.pipe(
      // we listen to only initApp action
      ofType(AppActions.initApp),
      concatMap(() =>
        this.userService.fetchUser().pipe(
          map((user) => UserActions.loadUsersSuccess({ user })),
          catchError((error) => of(UserActions.loadUsersFailure({ error })))
        )
      )
    );
  });

  constructor(private actions$: Actions, private userService: UserService) {}
}
```

```typescript
@Injectable()
export class ActivityEffects {
  loadActivities$ = createEffect(() => {
    return this.actions$.pipe(
      ofType(AppActions.initApp), // listen to the same event as UserEffect
      concatMap(() =>
        this.ActivityService.fetchActivities().pipe(
          map((activities) =>
            ActivityActions.loadActivitiesSuccess({ activities })
          ),
          catchError(() =>
            of(ActivityActions.loadActivitiesFailure())
          )
        )
      )
    );
  });

  constructor(
    private actions$: Actions,
    private ActivityService: ActivityService
  ) {}
}
```
If the http request complete successfully, a new success action is triggered which will update the store. To update the store, we need to use a **Reducer**. 

> _**Reducers** are a set of pure functions which let use transform the current state of our store to a new state with the action payload_. 

Each Effect must return an action. In our case, we will return either a success action or a failure action. _(Don't forget to handle error scenarios!!)_

```typescript
export const loadActivitiesSuccess = createAction(
  '[Activity Effect] Load Activities Success',
  props<{ activities: Activity[] }>() // payload of our success action
);

export const loadActivitiesFailure = createAction(
  '[Activity Effect] Load Activities Failure'
);
```
And our Reducer looks like this :

```typescript
// key of activityState inside Store object
export const activityFeatureKey = 'activity';

export interface ActivityState {
  activities: Activity[];
}

// createReducer is a big switch case
export const activityReducer = createReducer(
  initialState,
  // case 1: success
  on(ActivityActions.loadActivitiesSuccess, (state, { activities }) => ({
    ...state,
    activities,
  })),
  // case 2: failure
  on(ActivityActions.loadActivitiesFailure, (state) => ({
    state,
    activities: [],
  }))
);
```

> _**Remark:** "on" function can listen to multiple actions._ 

This reducer update only Activity state inside NgRx Global Store. We can divided our store into multiple slices. In our case, we have ActivityState and UserState. _(which has a similar reducer)._

The Store is just a big javascript object, and each reducer point to a key of that object.

```typescript
const store = {
     activity: ActivityState,
     user: UserState,
     // ...
}
```
Now, we need to get this data to our component. This part is easily done thanks to **Selectors**. 

> _**Selectors** are pure functions to retrieve piece of our state. We can see them as SQL queries. We will query our store to retrieve what's useful for our template to display necessary information._

```typescript
// select the state under activity key 
export const selectActivityState =
  createFeatureSelector<ActivityState>(activityFeatureKey);

// select the property "activities" defined in Activity State. 
export const selectActivities = createSelector(
  selectActivityState,
  (state) => state.activities
);
```
We can now easily retrieve the useful piece of our Store by selecting the second Selector.

```typescript
// in our AppComponent
activities$ = this.store.select(selectActivities);
```

```html
<!-- template of our AppComponent -->
<h1>Activity Board</h1>
<section>
  <div class="card" *ngFor="let activity of activities$ | async">
    <h2>Activity Name: {{ activity.name }}</h2>
    <p>Main teacher: {{ activity.teacher.name }}</p>
  </div>
</section>
```
NgRx is strongly coupled with RxJs Observable. This allows us to manage all the asynchronous part of our application.
In our case, the view will be updated when the http request is completed and the store will be updated. If later new activities are added to the store, or just updated, the view will magically refresh.

---

Everything is very nice, but this was pretty straight forward. Now let's discuss how to build our **list of available teachers**; called **Status**.

To do this, we need to get our list of activities and our user. This exercice being inspired by a real life exemple, the author chose to use the concept of **Effect** for this task. Here are a few reasons _(bad or good, we will see later)_:

- It's a side effect. The author wanted the data to be processed asynchronously when User and Activities information were available in the store or updated.
- The author wanted to store the result.

```typescript
// Status contains all teachers doing the same activity
export interface Status {
  name: ActivityType;
  teachers: Person[];
}

@Injectable()
export class StatusEffects {
  loadStatuses$ = createEffect(() => {
    return this.actions$.pipe(
      ofType(AppActions.initApp), // we can listen to the action dispatched at startup
      concatMap(() =>
        // we cannot use WithLatestFrom to retreive our state since
        // we need to listen to user and activities changes to update our status
        combineLatest([ 
          this.store.select(selectUser),
          this.store.select(selectActivities),
        ]).pipe(
          map(([user, activities]): Status[] => {
            if (user?.isAdmin) {
              // loop over activities to group all teachers by type of activity 
              return activities.reduce(
                (status: Status[], activity): Status[] => {
                  const index = status.findIndex(
                    (s) => s.name === activity.type
                  );
                  if (index === -1) {
                    return [
                      ...status,
                      { name: activity.type, teachers: [activity.teacher] },
                    ];
                  } else {
                    status[index].teachers.push(activity.teacher);
                    return status;
                  }
                },
                []
              );
            }
            return [];
          }),
          // when is done, we return a new action to update our store
          map((statuses) => StatusActions.loadStatusesSuccess({ statuses }))
        )
      )
    );
  });

  constructor(private actions$: Actions, private store: Store) {}
}
```
And we listen to the success action inside our reducer to update Status state:

```typescript
export interface StatusState {
 // list of status calculated inside the effect
  statuses: Status[];
 // map the type of one activity type to a given list of teachers
  teachersMap: Map<ActivityType, Person[]>;
}

export const statusReducer = createReducer(
  initialState,
  on(StatusActions.loadStatusesSuccess, (state, { statuses }): StatusState => {
    const map = new Map();
    statuses.forEach((s) => map.set(s.name, s.teachers));
    return {
      ...state,
      statuses,
      teachersMap: map,
    };
  })
);
```
Inside the **Reducer**, we can see that the author created a second property teacherMap to easily retrieve the teacher list inside the **Selector** as you can see below:

```typescript
export const selectStatusState =
  createFeatureSelector<StatusState>(statusFeatureKey);

export const selectStatuses = createSelector(
  selectStatusState,
  (state) => state.statuses
);

export const selectAllTeachersByActivityType = (name: ActivityType) =>
  createSelector(
    selectStatusState,
    (state) => state.teachersMap.get(name) ?? []
  );
```
And finally  our component looks like this:

```html
<h1>Activity Board</h1>
<section>
  <!-- loop over activity list-->
  <div class="card" *ngFor="let activity of activities$ | async">
    <h2>Activity Name: {{ activity.name }}</h2>
    <p>Main teacher: {{ activity.teacher.name }}</p>
    <span>All teachers available for : {{ activity.type }} are</span>
    <ul>
<!-- for each type of activity, we get the list of teachers from our selector-->
      <li
        *ngFor="
          let teacher of getAllTeachersForActivityType$(activity.type)
            | async
        "
      >
        {{ teacher.name }}
      </li>
    </ul>
  </div>
</section>
```

```typescript
// inside AppComponent
getAllTeachersForActivityType$ = (type: ActivityType) =>
  this.store.select(selectAllTeachersByActivityType(type));
```
For each activity, we call a function to get the list of available teachers from our Store.

The exemple works but have a lot of issues !!!

### Issues:
- We shouldn't store **derived state**. This is **error prone** because when your data change, we need to remember all places to update it. We should only have **one place of truth** with that data, and every transformation should be done inside  a **Selector**.
- Inside a component, we **shouldn't transform the result of a selector** _(using map operator)_, or we **shouldn't have to call a selector from a function** in our view. **The data useful for our view should be derived inside a Selector** as well.
- Calling functions inside a template is not good for performance. Each time Angular trigger a Change Detection cycle, the whole template is re-rendered and all functions are recalculated. We will often read to set our components to OnPush. This is a bit better but functions will still be re-executed if activities$ steam is triggered. 
- Having a **combineLatest** operator inside an **Effect** should be a **red Flag**.

A lot of people starting with NgRx think that every object needed for the template needs to be stored. Don't think like that; one piece of information should be saved only once.

## Let me explain the power of Selectors.

One very important piece of information often overlooked is that selector can be **combined**. We can listen to as many selectors as we want inside another selector and then combined them to get the desired output.

> _For RxJs developers, **selectors** is only an enhanced **combineLatest** operator with **memoization**._

So the example above can simply be turned into a **Selector**. No need for Effects or Reducers. Just a nice Selector.

```typescript
// we combine two selectors
const selectStatuses = createSelector(
  // be as precise as we can be. Don't listen to the whole user object but 
  // only to the necessary properties. This way, the selector will be ONLY
  // rerun if the user's admin property has changed. 
  UserSelectors.isUserAdmin,
  ActivitySelectors.selectActivities,
  (isAdmin, activities) => {
    if (!isAdmin) return [];

    // same code as in previous effect
    return activities.reduce((status: Status[], activity): Status[] => {
      const index = status.findIndex((s) => s.name === activity.type);
      if (index === -1) {
        return [
          ...status,
          { name: activity.type, teachers: [activity.teacher] },
        ];
      } else {
        status[index].teachers.push(activity.teacher);
        return status;
      }
    }, []);
  }
);
```
This feel more natural. And we deleted StatusEffect and StatusReducer. No need to store Status and teacherMap. And the beauty with memoization is that whenever we call this selector, no calculation is needed, the cached value will be returned. The calculation will only be rerun if the property admin of the user change or the activities have been modified.

And for our template, let's create our activity object.

```typescript
selectActivities = createSelector(
    ActivitySelectors.selectActivities,
    StatusSelectors.selectStatuses,
    (activities, statuses) =>
      activities.map(({ name, teacher, type }) => ({
        name,
        mainTeacher: teacher,
        type,
        availableTeachers:
          statuses.find((s) => s.name === type)?.teachers ?? [],
      }))
  );

 activities$ = this.store.select(this.selectActivities);
```
And now the template can simply be written like below. No more function. All necessary properties are available inside our activities$ stream.

```html
<h1>Activity Board</h1>
<section>
  <div class="card" *ngFor="let activity of activities$ | async">
    <h2>Activity Name: {{ activity.name }}</h2>
    <p>Main teacher: {{ activity.mainTeacher.name }}</p>
    <span>All teachers available for : {{ activity.type }} are</span>
    <ul>
      <li *ngFor="let teacher of activity.availableTeachers">
        {{ teacher.name }}
      </li>
    </ul>
  </div>
</section>
```
**Remark:** To improve performance a bit, we could have added a trackBy function in our ngFor directive.
 
And here we are, we rewrote our application in a simpler, more readable and more maintainable version by applying the right NgRx concept.

## Conclusion:
 
In this simple challenge, we talked about all the key NgRx concepts: Effect, Reducer, Selector and Action.

We have seen that Selectors are often forgotten and misunderstood, and most of the time people chose to store everything. 

> **If you had one thing to remember,  it's to store each piece of information only ONCE. If you need to derive some piece of your store, Selectors should come to mind.**

---

I hope you enjoyed this NgRx challenge and learned from it.

_Other challenges are waiting for you at [Angular Challenges](https://github.com/tomalaforge/angular-challenges). Come and try them. I'll be happy to review you!_

Follow me on [Medium](https://medium.com/@thomas.laforge), [Twitter](https://twitter.com/laforge_toma) or [Github](https://github.com/tomalaforge) to read more about upcoming Challenges!