Tim Deschryver

Common and easy-to-make mistakes when you’re new to NgRx

This post is aimed at newcomers to NgRx.

In the first part of the post, we’ll be creating a new Angular project and we’ll implement a FizzBuzz implementation with NgRx. For the initial setup we’ll use the Angular CLI, and to scaffold the NgRx files we’ll be relying on the NgRx schematics.

The second part of the post will be used to refactor and reason about the implementation of the first part. The goal is to have a more maintainable code base.

Before we start, let’s get everyone up to speed with FizzBuzz. FizzBuzz is a task that prints out numbers incrementally starting from 1, if the number is divisible by three the number get replaced by the word fizz and if the number is divisible by five it gets replaced by buzz. If the number is divisible by both three and five, the word fizz buzz will be printed out.

The output starts as follows:

1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, FizzBuzz, 16, 17, Fizz, 19, Buzz, Fizz, 22, 23, Fizz, Buzz, …

Let’s get started! We can create the project using the ng new command and we’ll install the NgRx packages using the NgRx schematics. These schematics will install the package and will also automate the needed configurations to wire up NgRx correctly.

ng new ngrx-fizzbuzz
ng add @ngrx/store
ng add @ngrx/effects --spec=false --group=true
ng add @ngrx/store-devtools
yarn add @ngrx/schematics --dev

With the project ready, we can start to implement FizzBuzz. We do this by creating the fizzbuzz reducer, using the reducer schematic.

ng generate @ngrx/schematics:reducer fizzbuzz --reducers=reducers/index.ts --group=true --spec=false

In the reducer:

export interface State {
  counter: number
  message: string
}

export const initialState: State = {
  counter: 1,
  message: '',
}

export function reducer(state = initialState, action: Action): State {
  switch (action.type) {
    case 'NEXT':
      const counter = state.counter + 1
      let message = ''

      if (counter % 3 === 0) {
        message += 'Fizz'
      }
      if (counter % 5 === 0) {
        message += 'Buzz'
      }

      return {
        counter,
        message: message || counter.toString(),
      }

    default:
      return state
  }
}

Now that we’ve defined the fizzbuzz state and the fizzbuzz reducer, it’s time to print out the fizzbuzz output. Inside the AppComponent, we inject the Store to get the fizzbuzz message and to dispatch the NEXT action to invoke the reducer.

To get the message from the store, we use the select operator. Because State is the whole application state, we first have to access the fizzbuzz state in order to retrieve the message. This gives us an RxJS stream, consisting of messages. To print the messages out, we use the Angular async pipe.

To trigger a state change, we have to invoke the fizzbuzz reducer. Because we can’t invoke the reducer directly, we use the dispatch function. We send the action NEXT to the store which will invoke the fizzbuzz reducer, resulting in a new message in the message stream.

@Component({
  selector: 'app-root',
  template: `
    {{ fizzbuzzMessage | async }}
  `,
})
export class AppComponent implements OnInit {
  fizzbuzzMessage: Observable<string> = this.store.pipe(
    select(state => state.fizzbuzz.message),
  )

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

  ngOnInit() {
    setInterval(() => this.store.dispatch({ type: 'NEXT' }), 1000)
  }
}

If we now start the application, we’ll see the fizzbuzz messages! 🎉

Before we walk through the refactoring, I would encourage you to roll up your sleeves first and refactor the current code yourself on StackBlitz.

The first step in the refactoring journey would be to create actions. We do this to remove magic strings, but maybe even more important, to allow action type checking. In this small example, the full power of this step won’t be visible. In larger applications, you’ll notice that TypeScript can infer the action’s properties inside the reducers.

We can create the action by using the action schematic.

ng generate @ngrx/schematics:action fizzbuzz --group=true --spec=false

The above schematic creates the action file and an example action inside of it, consisting out of an action enum, an action creator, and an action union type. We can modify this to fit our fizzbuzz application as follows:

import { Action } from '@ngrx/store'

// action enum
export enum FizzBuzzActionTypes {
  Next = '[AppComponent] Next',
}

// action creator
export class Next implements Action {
  readonly type = FizzBuzzActionTypes.Next
}

// actions union type, add more Actions using a pipe '|'
export type FizzBuzzActions = Next

Now, we can remove the magic string NEXT inside the fizzbuzz reducer and inside the AppComponent.

For more information about actions see the official docs and a previous post Let’s have a chat about Actions and Action Creators within NgRx.

The problem with the working code is that we hold multiple versions of the same state inside the store state, this can make it hard to maintain over time when the application keeps on growing. That’s why we’re going to extract the fizzbuzz message inside a selector.

Before we can create the selector, we first have to provide a getter to retrieve the counter within the fizzbuzz state. When this is done, we can create our message selector to compute derived state.

// `getter` in the reducer file reducers/fizzbuzz.reducer
export const getCounter = (state: State) => state.counter

// selectors in reducers

// First, select the fizzbuzz state from app state
export const getFizzBuzzState = createFeatureSelector<fromFizzbuzz.State>(
  'fizzbuzz',
)

// Second, wrap the getter inside a selector
export const getCounter = createSelector(
  getFizzBuzzState,
  fromFizzbuzz.getCounter,
)

// Third, create the message selector based on the counter state
export const getMessage = createSelector(getCounter, counter => {
  let message = ''
  if (counter % 3 === 0) {
    message += 'Fizz'
  }
  if (counter % 5 === 0) {
    message += 'Buzz'
  }

  return message || counter.toString()
})

For more info about selectors see the official docs and a previous post Sharing data between modules is peanuts.

With the action and the selector created, it’s time to clean up the reducer. This is done by:

export interface State {
  counter: number
}

export const initialState: State = {
  counter: 1,
}

export function reducer(state = initialState, action: FizzBuzzActions): State {
  switch (action.type) {
    case FizzBuzzActionTypes.Next:
      return {
        counter: state.counter + 1,
      }

    default:
      return state
  }
}

For more info about reducers see the official docs and for more info about state normalization see a previous post Normalizing state.

I’m a big fan of effects and I’m using it to put every piece of logic that isn’t specific to the component’s logic. The most used and known example of this are AJAX requests, but the use cases of effects can be broadened out to everything that is causing your component to become impure.

Inside the effects/app.effects.ts file created by the ng add command, we’re going to move the logic to dispatch the Next action on every second. For this, we’re using the RxJS interval instead of the setInterval method, creating a continuous stream that emits a Next Action on each time interval. These actions will be dispatched to the store, invoking the reducer that on his turn triggers the getMessage selector resulting in a re-render with the new message output.

@Injectable()
export class AppEffects {
  @Effect()
  fizzbuzzes = interval(1000).pipe(mapTo(new Next()))
}

For more info about effects see the official docs and for more effects usages see Start using ngrx/effects for this.

With these steps completed, we can now go back to the AppComponent and:

@Component({
  selector: 'app-root',
  template: `
    {{ fizzbuzzes | async }}
  `,
})
export class AppComponent {
  fizzbuzzes = this.store.pipe(select(getMessage))

  constructor(private store: Store<{}>) {}
}

If you keep these little tips in mind, each boundary inside the application has its own responsibility. This makes it easier to reason about and easier to maintain in the long run. If something goes wrong you can quickly scope it down to a specific boundary and you immediately know where to look.

To end this post, see the this Blitz for the refactored version.

Share on Twitter Discuss on Twitter Edit on GitHub

Send Tim a message