# 20. Create effect

@ngrx/effects provides an API to model event sources as actions. Effects:

1. Listen for actions dispatched from @ngrx/store.
2. Isolate side effects from components, allowing for more *pure* components that select state and dispatch actions.
3. Provide [new sources](https://martinfowler.com/eaaDev/EventSourcing.html) of actions to reduce state based on external interactions such as network requests, web socket messages and time-based events.

## 1. npm install @ngrx/effects

* Execute the following command to install the effects library.

```
npm i @ngrx/effects
```

## 2. Dispatch new LoadAttendees action from the EventComponent

Before we can use effect we need to dispatch some actions from our EventComponent for our effects to listen to. We will not need the service in our component when we finish but for now we still need it to add Attendees.

* Dispatch actions to `LoadAttendees`.
* Use the new `EventState` to limit the auto completion of what we are meant to be able to access from the store. Note we can still access everything as the store is a global object.
* Remove getAttendees method which will now be handled by the effect we are about to write.

{% code title="src/app/event/containers/event/event.component.ts" %}

```typescript
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { Store, select } from '@ngrx/store';

import { Attendee } from '../../../models';
import { EventService } from '../../services/event.service';
import { StartSpinner, StopSpinner } from '../../../state/spinner/spinner.actions';
import { getSpinner } from '../../../state/spinner/spinner.selectors';
import { LoadAttendees } from '../../state/attendees/attendees.actions';
import { State } from '../../state';

@Component({
  selector: 'app-event',
  templateUrl: './event.component.html',
  styleUrls: ['./event.component.scss']
})
export class EventComponent implements OnInit {
  spinner$: Observable<boolean>;
  attendees$: Observable<Attendee[]>;

  constructor(
    private store: Store<State>,
    private eventService: EventService
  ) {}

  ngOnInit() {
    this.store.dispatch(new LoadAttendees());
    this.spinner$ = this.store.pipe(select(getSpinner));
    this.attendees$ = this.store.pipe(select(state => state.event.attendees.attendees));
  }

  addAttendee(attendee: Attendee) {
    this.store.dispatch(new StartSpinner());
    this.eventService.addAttendee(attendee).subscribe(() => {
      this.store.dispatch(new StopSpinner());
    });
  }
}

```

{% endcode %}

## 3. Create an effect

Effects are all about listening for actions doing work and dispatching new actions. So in our example we will listen for the `LoadAttendees` action do the work of getting them via a service and then dispatch a `LoadAttendeesSuccess` action.

NgRx effects are a deep dive into observables and if you have not done a lot with RxJS or reactive streams in other languages can take some time to step through.

* Create and attendees.effects.ts file in our state/attendees folder path.
* Inject the `actions` observable from NgRx that will emit each action dispatched in our application and the `EventService` to get the attendees.
* Add an `@Effect` decorator on top of a variable name of the effect.
* List the injected actions and filter on them with the `ofType` operator from NgRx.&#x20;
* Use the `switchMap` operator to switch from the actions stream to a new observable returned from our EventService and return an `LoadAttendeesSuccess` action.
* Add a `catchError` operator and return an observable of `LoadAttendeesFail` with a payload of the error.

{% code title="src/app/event/state/attendees/attendees.effects.ts" %}

```typescript
import { Injectable } from '@angular/core';
import { Actions, Effect } from '@ngrx/effects';
import { ofType } from '@ngrx/effects';
import { switchMap, map, catchError } from 'rxjs/operators';
import { of } from 'rxjs';

import { EventService } from '../../services/event.service';
import {
  AttendeesActionTypes,
  LoadAttendees,
  LoadAttendeesSuccess,
  LoadAttendeesFail
} from './attendees.actions';
import { Attendee } from '../../../models';

@Injectable()
export class AttendeesEffects {
  constructor(private actions$: Actions, private eventService: EventService) {}

  @Effect()
  getAttendees$ = this.actions$.pipe(
    ofType(AttendeesActionTypes.LoadAttendees),
    switchMap((action: LoadAttendees) =>
      this.eventService.getAttendees().pipe(
        map((attendees: Attendee[]) => new LoadAttendeesSuccess(attendees)),
        catchError(error => of(new LoadAttendeesFail(error)))
      )
    )
  );
}

```

{% endcode %}

## 4. Update reducer to listen for success and fail actions

* Add an error property to the State object to hold any errors.
* Update the attendee reducer to update the store with the attendees array form the affect.
* Add a case for the LoadAttendeesFail action and set its payload to the error property of the state object.

{% code title="src/app/event/state/attendees/attendees.reducer.ts" %}

```typescript
import { Attendee } from '../../../models';
import { AttendeesActions, AttendeesActionTypes } from './attendees.actions';

export interface State {
  attendees: Attendee[];
  loading: boolean;
  error: any;
}

export const intitalState: State = {
  attendees: [],
  loading: false,
  error: null
};

export function reducer(state = intitalState, action: AttendeesActions): State {
  switch (action.type) {
    case AttendeesActionTypes.LoadAttendees: {
      return {
        ...state,
        loading: true,
        error: null
      };
    }

    case AttendeesActionTypes.LoadAttendeesSuccess: {
      return {
        ...state,
        loading: false,
        attendees: action.payload,
        error: null
      };
    }

    case AttendeesActionTypes.LoadAttendeesFail: {
      return {
        ...state,
        loading: false,
        error: action.payload
      };
    }

    default: {
      return state;
    }
  }
}

```

{% endcode %}

## &#x20;5. Add effects array to index.ts file for EventModule

Our index.ts file in our event state folder is our public API so we want to add our different feature states effect to.

* Re-export effects from the index.ts.

{% code title="src/app/event/state/index.ts" %}

```typescript
import { ActionReducerMap } from '@ngrx/store';

import * as fromRoot from './../../state/state';
import * as fromAttendees from './attendees/attendees.reducer';
import { AttendeesEffects } from './attendees/attendees.effects';

export interface EventState {
  attendees: fromAttendees.State;
}

export interface State extends fromRoot.State {
  event: EventState;
}

export const reducers: ActionReducerMap<EventState> = {
  attendees: fromAttendees.reducer
};

export const effects: any[] = [AttendeesEffects];
```

{% endcode %}

## 6. Register the feature effects in the EventModule

* Register the effects with the `EffectsModule.forFeature` method.

{% code title="src/app/event/event.module.ts" %}

```typescript
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
import { EffectsModule } from '@ngrx/effects';
import { StoreModule } from '@ngrx/store';

import { EventComponent } from './containers/event/event.component';
import { AddAttendeeComponent } from './components/add-attendee/add-attendee.component';
import { EventListComponent } from './components/event-list/event-list.component';
import { reducers, effects } from './state';

@NgModule({
  imports: [
    CommonModule,
    RouterModule.forChild([{ path: '', component: EventComponent }]),
    ReactiveFormsModule,
    HttpClientModule,
    StoreModule.forFeature('event', reducers),
    EffectsModule.forFeature(effects)
  ],
  declarations: [EventComponent, AddAttendeeComponent, EventListComponent]
})
export class EventModule {}

```

{% endcode %}

## 7. Add default EffectsModule registration to root AppModule

For us to use effects in our app and reducers we need to have a root reducer and effect to start with. The root reducer could just be an object and the effects an empty array but we need to have them. The feature effects and reducers we make are then added to them as we lazily load them.

* Add forRoot with an empty array for the AppModule.

{% code title="src/app/app.module.ts" %}

```typescript
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { RouterModule } from '@angular/router';
import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
import { StoreModule } from '@ngrx/store';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { EffectsModule } from '@ngrx/effects';

import { HomeComponent } from './home/containers/home/home.component';
import { AppComponent } from './app.component';
import { InMemoryDataService } from './app.db';
import { reducer } from './state/spinner/spinner.reducer';
import { environment } from '../environments/environment.prod';

@NgModule({
  declarations: [AppComponent, HomeComponent],
  imports: [
    BrowserModule,
    RouterModule.forRoot([
      { path: '', pathMatch: 'full', redirectTo: 'home' },
      { path: 'home', component: HomeComponent },
      { path: 'event', loadChildren: './event/event.module#EventModule' }
    ]),
    HttpClientModule,
    HttpClientInMemoryWebApiModule.forRoot(InMemoryDataService, {
      delay: 1000
    }),
    StoreModule.forRoot({ spinner: reducer }),
    EffectsModule.forRoot([]),
    StoreDevtoolsModule.instrument({
      name: 'NgRx Demo App',
      logOnly: environment.production
    })
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}

```

{% endcode %}

* Even before we write selectors we can see our app should working using our new effects.

![Image: Redux dev tools showing LoadAttendeesSuccess Action firing](/files/-LMWwbBVAypWD6gPJcQa)

## 8. Create a getAttendees selector

Similar to making selectors for our spinner we will now make them for our attendees reducer. You will end up having a selectors file for each reducer and they can become very logic heavy and need unit test but for now ours are quite simple.

* Create selector for selecting attendees.

{% code title="src/app/event/state/attendees/attendees.selectors.ts" %}

```typescript
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { EventState } from '..';

export const getEventState = createFeatureSelector<EventState>('event');

export const getAttendeeState = createSelector(
  getEventState,
  state => state.attendees
);

export const getAttendees = createSelector(
  getAttendeeState,
  state => state.attendees
);


```

{% endcode %}

## 9. Use selector in EventComponent

* Use getAttendees selector.

{% code title="src/app/event/containers/event/event.component.ts" %}

```typescript
---------- ABBREVIATED CODE SNIPPET ----------

  ngOnInit() {
    this.spinner$ = this.store.pipe(select(getSpinner));
    this.attendees$ = this.store.pipe(select(getAttendees));
    this.store.dispatch(new LoadAttendees());
  }
  
---------- ABBREVIATED CODE SNIPPET ----------
```

{% endcode %}

## &#x20;RxJS Operators and higher order observables

{% hint style="warning" %}
It is important to know which higher order observables to use with your effects to avoid race conditions. A higher order observable is just a fancy name for an observable that emits observable like the ones below.
{% endhint %}

| Operator   | When to use                                                                                                                                                                       |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| switchMap  | <p>Cancels the current subscription/request and can cause race condition</p><p><strong>Use for get requests or cancelable requests like searches</strong></p>                     |
| concatMap  | <p>Runs subscriptions/requests in order and is less performant</p><p><strong>Use for get, post and put requests when order is important</strong></p>                              |
| mergeMap   | <p>Runs subscriptions/requests in parallel</p><p><strong>Use for put, post and delete methods when order is not important</strong></p>                                            |
| exhaustMap | <p>Ignores all subsequent subscriptions/requests until it completes</p><p><strong>Use for login when you do not want more requests until the initial one is complete</strong></p> |

## StackBlitz Link

{% embed url="<https://stackblitz.com/github/duncanhunter/angular-and-ngrx-demo-app/tree/20-create-effects>" %}
Web Link: Link to the demo app running in StackBlitz
{% endembed %}

## Extras and Homework

{% hint style="danger" %}
These extra sections are for doing after the course or if you finish a section early. Please move onto the next section if doing this as a workshop when the instructor advises.

WARNING: Some of these extra sections will make it more difficult to copy and paste the code examples later on in the course.

You might need to apply the code snippet examples a little more carefully amongst any "extras section" code you may add. If you are up for some extra challenges these sections are for you.
{% endhint %}

### Try out the @ngrx/schematics to scaffold out an application.

Schematics are Angular's way of being able to have custom code generators. You can read more here <https://blog.angular.io/schematics-an-introduction-dc1dfbc2a2b2>

You will be wondering why we did not do this from the start and save us a lot of typing? Well first you need to know what you are doing before you can use the scaffolding tools as many students get lost in all the code it makes versus building it up from from nothing for the first time.

Steps:

1. Run this command to install the schematics \
   `npm i @ngrx/schematics -D`
2. Run this command to set them as the default schematics `ng config cli.defaultCollection @ngrx/schematics`
3. Run this command to make a default empty root store. \
   `ng g store State --root --statePath state --module app.module.ts`
4. Run this command to make a container component\
   &#x20;`ng generate container Event2 --state reducers/index.ts --stateInterface Event2`
5. Run this command to make the new feature module\
   `ng g module party ng g c party/containers/party --module party/party.module.ts`&#x20;
6. Run this command to make the NgRx feature parts like reducer and effects etc\
   `ng generate feature party/party --flat false`


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://duncanhunter.gitbook.io/angular-and-ngrx/20.-create-effect.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
