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 types to our reducer
  • 2. Add a global state interface
  • 3. Use global interface in EventComponent
  • 4. Create action creators
  • 5. Use action creators in our components
  • 6. Use ActionTypes and Actions type in reducer
  • StackBlitz Link
  • Extras and Homework
  • Learn more about Action Hygiene from the NgRx team in this ngConf you tube video

15. Strongly type our store

In this section we will add strong typing using TypeScript to our applications state logic. We skipped this step to make it simple to see what redux is and how the pattern works.

1. Add types to our reducer

By community convention most people put the types for each slice of state with the reducer, which makes sense as this is where you update the slice of state. Here will will make an interface for shape of the slice of state and use it to type a constant to make our initial state. It is a good idea to always make initial state so we can be confident what we will get when we start subscribing to the store before any actions that might update the state are dispatched.

  • Add an interface and initial state to our spinner reducer.

src/app/state/spinner.reducer.ts
export interface State {
  isOn: boolean;
}

export const initialState: State = {
  isOn: false
};

export function reducer(state = initialState, action): State {
  switch (action.type) {
    case 'startSpinner': {
      return {
        isOn: true
      };
    }

    case 'stopSpinner': {
      return {
        isOn: false
      };
    }

    default:
      return state;
  }
}

2. Add a global state interface

This is a difficult task as most applications lazily load the majority of their state, meaning we can only specify the state that is always loaded when the app loads. We can then extend our feature state objects to have these "global" state reducers.

  • Create a state.ts file in our state folder.

  • Add a global interface to describe all the global state that will be initialised up front in our application on load.

src/app/state/state.ts
import * as fromSpinner from './spinner/spinner.reducer';

export interface State {
  spinner: fromSpinner.State;
}

3. Use global interface in EventComponent

Now we can inject the Store into our components with a type. This is useful to say what state reducers we should be able to interact with from this component but in reality the store is a global object and these types do not stop us getting the whole store.

  • Update the store to use the new State interface.

src/app/event/container/event/event.component.ts
----------- ABBREVIATED CODE SNIPPPET ----------
  
  constructor(
    private store: Store<State>,
    private eventService: EventService
  ) {}


----------- ABBREVIATED CODE SNIPPPET ----------

4. Create action creators

Action creators are a big part of making an Angular application that uses NgRx more robust. It helps get type inference and code hints in our app and stops users dispatching actions with mistakes or the wrong actions with maybe the wrong payloads.

The SpinnerActionTypes enum is useful for having a strongly typed list of the actions we can dispatch. It is also handy to have detail in them so when we see them in our dev tools we can also see the place and time they are dispatched from, making debugging easier when we are less familiar with the code written by other or even ourselves.

  • Create a spinner.actions.ts file in our state/spinner folder.

  • Make action creators to strongly type our actions.

src/app/state/spinner/spinner.actions.ts
import { Action } from '@ngrx/store';

export enum SpinnerActionTypes {
  StartSpinner = '[Spinner Page] Start Spinner',
  StopSpinner = '[Spinner Page] Stop Spinner'
}

export class StartSpinner implements Action {
  readonly type = SpinnerActionTypes.StartSpinner;
}

export class StopSpinner implements Action {
  readonly type = SpinnerActionTypes.StopSpinner;
}

export type SpinnerActions = StopSpinner | StartSpinner;

5. Use action creators in our components

  • Use action creators in EventComponent and remove our un typed actions we are dispatching.

src/app/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 { State } from '../../../state/state';
import { StartSpinner, StopSpinner } from '../../../state/spinner/spinner.actions';

@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.getAttendees();
    this.spinner$ = this.store.pipe(select(state => state.spinner.isOn));
  }

  getAttendees() {
    this.attendees$ = this.eventService.getAttendees();
  }

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

6. Use ActionTypes and Actions type in reducer

We will break our unit tests by using our SpinnerActionTypes which now have a different string and words for the type argument of our actions but we will fix these in the next section.

  • Type the action argument passed into our reducer function as SpinnerActions.

  • Change our case statements to use our new SpinnerActionTypes.

src/app/state/spinner.reducer.ts
import { SpinnerActionTypes, SpinnerActions } from './spinner.actions';

export interface State {
  isOn: boolean;
}

export const initialState: State = {
  isOn: false
};

export function reducer(state = initialState, action: SpinnerActions): State {
  switch (action.type) {
    case SpinnerActionTypes.StartSpinner: {
      return {
        isOn: true
      };
    }

    case SpinnerActionTypes.StopSpinner: {
      return {
        isOn: false
      };
    }

    default:
      return state;
  }
}

StackBlitz Link

Extras and Homework

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.

Learn more about Action Hygiene from the NgRx team in this ngConf you tube video

Steps:

  1. Watch the video

Previous14. Test reducerNext16. Update reducer tests

Last updated 6 years ago

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