Angular and NgRx
  • Introduction
  • Setup
  • 1. Create an application
  • 2. Create a home component
  • 3. Test HomeComponent
  • 4. Create event feature module
  • 5.Create AddAttendeeComponent
  • 6. Test AddAttendeeComponent
  • 7. Listen to child component events
  • 8. Add test for the event emitter
  • 9. Create EventListComponent
  • 10. Test EventListComponent
  • 11. Create EventService
  • 12. Test EventService
  • 13. Add simple NgRx spinner
  • 14. Test reducer
  • 15. Strongly type our store
  • 16. Update reducer tests
  • 17. Store dev tools
  • 18. Create selectors
  • 19. Create feature state
  • 20. Create effect
  • 21. Test an effect
  • 22. Use Entity Adapter
  • 23. Add attendee logic
  • 24. Router store
  • 25. Fix EventComponent tests
Powered by GitBook
On this page
  • 1. Add new action creators
  • 2. Add effect
  • 3. Update reducer
  • 4. Update EventComponent to dispatch AddAttendees
  • StackBlitz Link

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));
  }
}

StackBlitz Link

Previous22. Use Entity AdapterNext24. Router store

Last updated 6 years ago

Duncanhunter - Angular And Ngrx Demo App - StackBlitzStackBlitz
Web Link: Link to the demo app running in StackBlitz
Logo