23. Add attendee logic

In this section we will add the logic to add an attendee using NgRx which we will build on in the rest of the course.

1. Add new action creators

  • Add the add ability to our feature state action creators.

src/app/event/state/attendees/attendees.actions.ts
import { Action } from '@ngrx/store';
import { Attendee } from '../../../models';
export enum AttendeesActionTypes {
  LoadAttendees = '[Attendees Page] Load Attendees',
  LoadAttendeesSuccess = '[Attendees Page] Load Attendees Success',
  LoadAttendeesFail = '[Attendees Page] Load Attendees Fail',
  AddAttendee = '[Attendee Page] Add Attendee',
  AddAttendeeSuccess = '[Attendee API] Add Attendee Success',
  AddAttendeeFail = '[Attendee API] Add Attendee Fail'
}
export class LoadAttendees implements Action {
  readonly type = AttendeesActionTypes.LoadAttendees;
}
export class LoadAttendeesSuccess implements Action {
  readonly type = AttendeesActionTypes.LoadAttendeesSuccess;
  constructor(public payload: Attendee[]) {}
}
export class LoadAttendeesFail implements Action {
  readonly type = AttendeesActionTypes.LoadAttendeesFail;
  constructor(public payload: any) {}
}

export class AddAttendee implements Action {
  readonly type = AttendeesActionTypes.AddAttendee;
  constructor(public payload: Attendee) {}
}
export class AddAttendeeSuccess implements Action {
  readonly type = AttendeesActionTypes.AddAttendeeSuccess;
  constructor(public payload: Attendee) {}
}
export class AddAttendeeFail implements Action {
  readonly type = AttendeesActionTypes.AddAttendeeFail;
  constructor(public payload: any) {}
}
export type AttendeesActions =
  | AddAttendee
  | AddAttendeeSuccess
  | AddAttendeeFail
  | LoadAttendees
  | LoadAttendeesSuccess
  | LoadAttendeesFail;

2. Add effect

  • add effect

src/app/event/state/attendees/attendees.effects.ts
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,
  AddAttendee,
  AddAttendeeSuccess,
  AddAttendeeFail
} 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)))
      )
    )
  );

  @Effect()
  addAttendee$ = this.actions$.pipe(
    ofType(AttendeesActionTypes.AddAttendee),
    switchMap((action: AddAttendee) =>
      this.eventService.addAttendee(action.payload).pipe(
        map((attendee: Attendee) => new AddAttendeeSuccess(attendee)),
        catchError(error => of(new AddAttendeeFail(error)))
      )
    )
  );
}

3. Update reducer

  • Update reducer to have a case for adding one attendee to the store using the adapter.addOne method.

src/app/event/state/attendees/attendees.reducer.ts
import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';

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

export interface State extends EntityState<Attendee> {
  loading: boolean;
  error: any;
}

const adapter: EntityAdapter<Attendee> = createEntityAdapter<Attendee>();

export const intitalState: State = adapter.getInitialState({
  loading: false,
  error: null
});

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

    case AttendeesActionTypes.LoadAttendeesSuccess: {
      return adapter.addAll(action.payload, {
        ...state,
        loading: false,
        error: null
      });
    }

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

    case AttendeesActionTypes.AddAttendeeSuccess: {
      return adapter.addOne(action.payload, { ...state, error: null });
    }

    case AttendeesActionTypes.AddAttendeeFail: {
      return { ...state, error: action.payload };
    }

    default: {
      return state;
    }
  }
}

export const {
  selectIds,
  selectEntities,
  selectAll,
  selectTotal
} = adapter.getSelectors();

4. Update EventComponent to dispatch AddAttendees

  • Update component and remove spinner, we can now use loading state property for the attendees slice of state.

src/app/event/containers/event/event.component.ts
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,
  AddAttendee
} from '../../state/attendees/attendees.actions';
import { State } from '../../state';
import { getAttendees } from '../../state/attendees/attendees.selectors';

@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.attendees$ = this.store.pipe(select(getAttendees));
    this.store.dispatch(new LoadAttendees());
  }

  addAttendee(attendee: Attendee) {
    this.store.dispatch(new AddAttendee(attendee));
  }
}

Last updated