Skip to content

Commit

Permalink
feat: add post about mixing action styles
Browse files Browse the repository at this point in the history
  • Loading branch information
brandonroberts committed Jun 25, 2020
1 parent 231ae9b commit 558ba50
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 1 deletion.
149 changes: 149 additions & 0 deletions content/posts/2020-05-14-mixing-action-styles-ngrx.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
---
title: Mixing Action Styles in NgRx State
description: 'Mixing Action Styles in NgRx State'
published: true
slug: 2020-05-14-mixing-action-styles-ngrx
publishedDate: 2020-05-14
---

## Mixing Action Styles In NgRx State

<a href="https://unsplash.com/@franckinjapan?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" title="Photo by Franck V. on Unsplash" class="center">
<img src="/assets/posts/franck-v-miWGZ02CLKI-unsplash.jpg" class="center" width="100%"/>
</a>

Prior to version 8 of the NgRx platform, actions were created using enums, classes, and union types. Many people thought this approach was too noisy, and refer to it as [boilerplate](https://www.youtube.com/watch?v=t3jx0EC-Y3c&t=325s) 😉. In [version 8](https://medium.com/ngrx/announcing-ngrx-version-8-ngrx-data-create-functions-runtime-checks-and-mock-selectors-a44fac112627), we introduced the new creator functions for actions, reducers, and effects. Recently, the question was asked, if you have an existing application, can you use the old syntax with the new syntax? Let's mix things up.

<iframe src="https://giphy.com/embed/RLPtfIGR5YoKQhHUSA" width="100%" height="270" frameBorder="0" class="giphy-embed" allowFullScreen></iframe><p><a href="https://giphy.com/gifs/rastamouse-cooking-masterchef-baking-RLPtfIGR5YoKQhHUSA">via GIPHY</a></p>

For this example, I'll start with the [counter example](https://ngrx.io/generated/live-examples/store/stackblitz.html) using StackBlitz from the [NgRx docs](https://ngrx.io).


### Using with Reducers

To separate the two action styles, put them in different files. In the `counter.actions.ts` file, there is an action creator.

```ts
import { createAction } from '@ngrx/store';

export const increment = createAction(
'[Counter Component] Increment'
);
```

Create a new file named `legacy-counter.actions.ts`. In the actions file, define an Increment action using the action class syntax.

```ts
import { Action } from '@ngrx/store';

export enum CounterActionTypes {
Increment = '[Counter Component] Legacy Increment'
}

export class Increment {
readonly type = CounterActionTypes.Increment;
}

export type Union = Increment;
```

The action type is different than the modern action using the creator function. In the counter.reducer.ts file, import the legacy actions. Before mixing the types of the legacy and modern syntax together, you need to create a union type of the two. The `@ngrx/store` package contains a helper utility function named `union` for returning the types of a dictionary of creator functions.


- Import the actions from `counter.actions.ts` using module import syntax
- Pass the object to the union function using the spread operator

```ts
import * as CounterActions from './counter.actions';

...

const CounterActionsUnion = union({...CounterActions});
```

This returns you the return types of the action creators. You already have an existing union of legacy counter actions, so you create a superset of the unions.

```ts
type Actions =
| LegacyCounterActions.Union
| typeof CounterActionsUnion;
```

The reducer creation function handles action creators, but you still need a way to handle action classes. Use a simple switch case to handle this scenario. The switch case handles your legacy actions, and the default uses the created reducer function.

```ts
import { createReducer, on, union } from '@ngrx/store';
import * as LegacyCounterActions from './legacy-counter.actions';
import * as CounterActions from './counter.actions';

export const initialState = 0;

type State = number;

const counterReducer = createReducer(initialState,
on(CounterActions.increment, state => state + 1)
);

const CounterActionsUnion = union({...CounterActions});

type Actions =
| LegacyCounterActions.Union
| typeof CounterActionsUnion;

export function reducer(state: State | undefined, action: Actions) {
switch(action.type) {
case LegacyCounterActions.CounterActionTypes.Increment:
return state + 1;
default:
return counterReducer(state, action);
}
}
```

The reducer handles both actions with the same type safety as before. To read more about the redesign of actions in NgRx, visit [NgRx: Action Creators redesigned](https://medium.com/angular-in-depth/ngrx-action-creators-redesigned-d396960e46da) by NgRx team member [Alex Okrushko](https://twitter.com/alex-okrushko).

### Dispatching Actions

Dispatching actions hasn't changed. For an action class, dispatch the action using the created instance. For an action creator, dispatch the action using the called function.

```ts
export class MyCounterComponent {
...
increment() {
this.store.dispatch(CounterActions.increment());
}
legacyIncrement() {
this.store.dispatch(new LegacyCounterActions.Increment());
}
...
}
```

### Using with Effects

Using the two different action styles with Effects is more straightforward. Let's look a counter effects example.

```ts
import * as LegacyCounterActions from './legacy-counter.actions';
import * as CounterActions from './counter.actions';

@Injectable()
export class CounterEffects {
increment$ = createEffect(() => {
return this.actions$.pipe(
ofType(
LegacyCounterActions.CounterActionTypes.Increment,
CounterActions.increment
),
tap(count => console.log('incremented'))
)
}, { dispatch: false });
constructor(private actions$: Actions) {}
}
```

The `ofType` operator takes multiple actions, and knows how to distinguish each action correctly. Underneath, the operator is looking at the `type` property of the action creator, or the `type` property on the action class instance. If you need to pass along some metadata with the action, you will need to specific the union types on the `Actions` generic.

That's It! To see a working example, see the completed [StackBlitz](https://stackblitz.com/edit/ngrx-mix-actions?embed=1&file=src/app/my-counter/my-counter.component.ts).

Follow me on [Twitter](https://twitter.com/brandontroberts), and [Twitch](https://twitch.tv/brandontroberts). If you like this content, consider [sponsoring me on GitHub](https://github.com/sponsors/brandonroberts).
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/assets/scully-routes.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
[{"route":"/about","title":"About Me","description":"About Me","published":true,"slug":"about","sourceFile":"about.md"},{"route":"/talks","title":"Tech Talks and Appearances","description":"Stuff","published":true,"slug":"talks","sourceFile":"talks.md"},{"route":"/blog/posts/2019-03-04-handling-error-states-with-ngrx","title":"Handling Error States with NgRx","description":"Handling Error States with NgRx","published":true,"slug":"2019-03-04-handling-error-states-with-ngrx","publishedDate":"2019-03-04T00:00:00.000Z","sourceFile":"2019-03-04-handling-error-states-with-ngrx.md"},{"route":"/blog/posts/2019-03-27-custom-route-matching-angular-router","title":"Custom Route Matching with the Angular Router","description":"Custom Route Matching with the Angular Router","published":true,"slug":"2019-03-27-custom-route-matching-angular-router","publishedDate":"2019-03-27T00:00:00.000Z","sourceFile":"2019-03-27-custom-route-matching-angular-router.md"},{"route":"/blog"},{"route":"/"}]
[{"route":"/about","title":"About Me","description":"About Me","published":true,"slug":"about","sourceFile":"about.md"},{"route":"/talks","title":"Tech Talks and Appearances","description":"Stuff","published":true,"slug":"talks","sourceFile":"talks.md"},{"route":"/blog/posts/2019-03-04-handling-error-states-with-ngrx","title":"Handling Error States with NgRx","description":"Handling Error States with NgRx","published":true,"slug":"2019-03-04-handling-error-states-with-ngrx","publishedDate":"2019-03-04T00:00:00.000Z","sourceFile":"2019-03-04-handling-error-states-with-ngrx.md"},{"route":"/blog/posts/2019-03-27-custom-route-matching-angular-router","title":"Custom Route Matching with the Angular Router","description":"Custom Route Matching with the Angular Router","published":true,"slug":"2019-03-27-custom-route-matching-angular-router","publishedDate":"2019-03-27T00:00:00.000Z","sourceFile":"2019-03-27-custom-route-matching-angular-router.md"},{"route":"/blog/posts/2020-05-14-mixing-action-styles-ngrx","title":"Mixing Action Styles in NgRx State","description":"Mixing Action Styles in NgRx State","published":true,"slug":"2020-05-14-mixing-action-styles-ngrx","publishedDate":"2020-05-14T00:00:00.000Z","sourceFile":"2020-05-14-mixing-action-styles-ngrx.md"},{"route":"/blog"},{"route":"/"}]

0 comments on commit 558ba50

Please sign in to comment.