|
1 | | ---- |
2 | | -title: "Chapter 5: Actions" |
3 | | -sidebar_position: 5 |
4 | | ---- |
5 | | - |
6 | | -import CodeBlock from "@theme/CodeBlock"; |
7 | | -import Exercise1Actions from "!!raw-loader!./chapter-5/exercise-1/actions"; |
8 | | -import Exercise2Actions from "!!raw-loader!./chapter-5/exercise-2/actions"; |
9 | | - |
10 | | -## What are Actions? |
11 | | - |
12 | | -`Actions` are the most simple core concept of NgRx (and Redux in general). Action is a unique event that is used to trigger a change in the state. What does it mean? For example, we might have an action that says "Home page has been loaded". It might mean some changes in the state. For example, in our application, it might trigger an API call for lists of expenses and incomes, which will in turn trigger an event that puts that data in the `Store`, resulting in a change in the UI. Or we might have an action that says "Add a category", which will create a new category of income/expense in the `Store`, again resulting in a UI change. Again, essentially `Actions` are like commands to the `Store`, or methods that allow to update its contents. |
13 | | - |
14 | | -## What does an Action look like? |
15 | | - |
16 | | -Actions are simple. An action is usually an object with just two properties: a `type`, a string that indicates what _exactly_ that action represents, and an optional `payload` that is kind of like a function argument. In our example, an action that adds a category might look like this: |
17 | | - |
18 | | -```ts |
19 | | -const addCategoryAction = { |
20 | | - type: "[Categories List] Add Category", |
21 | | - payload: { name: "Food" }, |
22 | | -}; |
23 | | -``` |
24 | | - |
25 | | -This is an action that adds a category named `Food` to the list of all categories. Of course, we still haven't written the logic that actually uses this action to put the data in the store, but for now we are focusing on the `Actions`. In NgRx, there is a simpler, built-in way of creating `Actions`, namely the `createAction` function. To start learning about it, let's create a folder names `state` in our `src/app` directory, and a file called `actions.ts` inside it. Now, let's put this code inside that file: |
26 | | - |
27 | | -```ts |
28 | | -// src/app/state/actions.ts |
29 | | -import { createAction, props } from "@ngrx/store"; |
30 | | -import { Category } from "./state"; |
31 | | - |
32 | | -export const addCategory = createAction( |
33 | | - "[Category List] Add Category", |
34 | | - props<{ category: Category }>() |
35 | | -); |
36 | | -``` |
37 | | - |
38 | | -Let's break down this example. First of all, the name `createAction` is a bit deceptive; it does not create an action; in fact, it creates a function which, when called, wil produce an action object. The first argument is the `type` of the action that will be produced. When called, the `addCategory` function will **always** create an action with type "[Category List] Create Category". The second argument uses the bizarre function `props`, which is a generic function that allows us to define the type of the `payload` which the created action will have. Essentially, it explains that in order to create the action using the `addCategory` function, we should call it and provide an object that has a property `category` which is defined in the Interface Category defined in the AppState. Let's do this and `console.log` the result. |
39 | | - |
40 | | -```ts |
41 | | -// src/app/app.component.ts |
42 | | -import { Component, OnInit } from "@angular/core"; |
43 | | - |
44 | | -import { addCategory } from "./state/actions"; |
45 | | - |
46 | | -@Component({ |
47 | | - // component boilerplate omitted for the sake of brevity |
48 | | -}) |
49 | | -export class AppComponent implements OnInit { |
50 | | - ngOnInit() { |
51 | | - console.log(addCategory({category:{ name: "Food" }})); |
52 | | - } |
53 | | -} |
54 | | -``` |
55 | | - |
56 | | -In the console, we will see the following: |
57 | | - |
58 | | -```js |
59 | | -{category: {name: 'Food', type: '[Category List] Add Category'}} |
60 | | -``` |
61 | | - |
62 | | -So essentially, `createAction` provided us with an easy way of creating actions of the same type. `addCategory` in our case is an `ActionCreator`, a function which produces an action object whenever called, and `props` explained what argument that `ActionCreator` function expects. |
63 | | - |
64 | | -### Homework |
65 | | - |
66 | | -Yes, you've read it correctly: we have learned how to write some basic code in NgRx, so it is time for some homework! |
67 | | -_For this homework, assume categories cannot have duplicate names. We will deal with this problem later_ |
68 | | - |
69 | | -1. Create an action for deleting a category. It should receive a string with the name of the category, and in the next chapter we will use that code to write the actual logic for deleting the category. |
70 | | -2. Create an action for updating a category. It must receive a `Category` object (`{name: string}`), and again, we will write the code to update the category in the next chapter |
71 | | - |
72 | | -> You will find solution code for all the homeworks in the end of the chapters |
73 | | -> **Important!** Do not move to the next chapter without adding the homework code! We will be using that code in the next chapters |
74 | | -
|
75 | | -In this chapter, we learned how to create `Actions`, unique events that specify what should happen to the state. In the next one, we will be writing code that actually does the transformation in the state. |
76 | | - |
77 | | -<details> |
78 | | - <summary>Exercise 1 solution</summary> |
79 | | - |
80 | | - <CodeBlock title="actions.ts" className="language-ts"> |
81 | | - {Exercise1Actions} |
82 | | - </CodeBlock> |
83 | | -</details> |
84 | | - |
85 | | -<details> |
86 | | - <summary>Exercise 2 solution</summary> |
87 | | - <CodeBlock title="actions.ts" className="language-ts"> |
88 | | - {Exercise2Actions} |
89 | | - </CodeBlock> |
90 | | -</details> |
| 1 | +--- |
| 2 | +title: "Chapter 5: Actions" |
| 3 | +sidebar_position: 5 |
| 4 | +--- |
| 5 | + |
| 6 | +## What are Actions? |
| 7 | + |
| 8 | +`Actions` are the most simple core concept of NgRx (and Redux in general). Action is a unique event that is used to trigger a change in the state. What does it mean? For example, we might have an action that says "Home page has been loaded". It might mean some changes in the state. For example, in our application, it might trigger an API call for lists of expenses and incomes, which will in turn trigger an event that puts that data in the `Store`, resulting in a change in the UI. Or we might have an action that says "Add a category", which will create a new category of income/expense in the `Store`, again resulting in a UI change. Again, essentially `Actions` are like commands to the `Store`, or methods that allow to update its contents. |
| 9 | + |
| 10 | +## What does an Action look like? |
| 11 | + |
| 12 | +Actions are simple. An action is usually an object with just two properties: a `type`, a string that indicates what _exactly_ that action represents, and an optional `payload` that is kind of like a function argument. In our example, an action that adds a category might look like this: |
| 13 | + |
| 14 | +```ts |
| 15 | +const addCategoryAction = { |
| 16 | + type: "[Categories List] Add Category", |
| 17 | + payload: { name: "Food" }, |
| 18 | +}; |
| 19 | +``` |
| 20 | + |
| 21 | +This is an action that adds a category named `Food` to the list of all categories. Of course, we still haven't written the logic that actually uses this action to put the data in the store, but for now we are focusing on the `Actions`. |
| 22 | + |
| 23 | + |
| 24 | +### Creating an action |
| 25 | + |
| 26 | +NgRx provides some utility functions to help us create actions instead of creating our objects by hand as we did in the previous example. |
| 27 | + |
| 28 | +To start learning about those, let's create a folder names `state` in the `src/app` directory, and a file called `actions.ts` inside it. |
| 29 | + |
| 30 | +#### Using `createAction` |
| 31 | + |
| 32 | +The first way of doing so is by using `createAction` which will create a single action given the name and the parameters provided: |
| 33 | + |
| 34 | +```ts title="state/actions.ts" |
| 35 | +import { createAction, props } from "@ngrx/store"; |
| 36 | +import { Category } from "./state"; |
| 37 | + |
| 38 | +export const addCategory = createAction( |
| 39 | + "[Category List] Add Category", |
| 40 | + props<{ category: Category }>() |
| 41 | +); |
| 42 | +``` |
| 43 | + |
| 44 | +Let's break down this example. First of all, the name `createAction` is a bit deceptive; it does not create an action; in fact, it creates a function which, when called, wil produce an action object. The first argument is the `type` of the action that will be produced. When called, the `addCategory` function will **always** create an action with type "[Category List] Create Category". The second argument uses the bizarre function `props`, which is a generic function that allows us to define the type of the `payload` which the created action will have. Essentially, it explains that in order to create the action using the `addCategory` function, we should call it and provide an object that has a property `category` which is defined in the Interface Category defined in the AppState. Let's do this and `console.log` the result. |
| 45 | + |
| 46 | +```ts title="app.component.ts" |
| 47 | +import { Component, OnInit } from "@angular/core"; |
| 48 | +import { addCategory } from "./state/actions"; |
| 49 | + |
| 50 | +@Component({/* ... */}) |
| 51 | +export class AppComponent implements OnInit { |
| 52 | + ngOnInit() { |
| 53 | + console.log(addCategory({category:{ name: "Food" }})); |
| 54 | + } |
| 55 | +} |
| 56 | +``` |
| 57 | + |
| 58 | +In the console, we will see the following: |
| 59 | + |
| 60 | +```js |
| 61 | +{ |
| 62 | + type: "[Category List] Add Category", |
| 63 | + category: { name: "Food" } |
| 64 | +} |
| 65 | +``` |
| 66 | + |
| 67 | +So essentially, `createAction` provided us with an easy way of creating a single action. `addCategory` in our case is an `ActionCreator`, a function which produces an action object whenever called, and `props` explained what argument that `ActionCreator` function expects. |
| 68 | + |
| 69 | +#### Using `createActionGroup` |
| 70 | + |
| 71 | +As we just saw, creating an action is pretty straightforward. |
| 72 | + |
| 73 | +However, when dealing with multiple actions, this can lead to a more verbose code and prone to typos: |
| 74 | + |
| 75 | +```ts title="state/actions.ts" |
| 76 | +export const addCategory = createAction( |
| 77 | + "[Category List] Add Category", |
| 78 | + props<{ category: Category }>() |
| 79 | +); |
| 80 | + |
| 81 | +export const addAnotherCategory = createAction( |
| 82 | + // 👇 Did you notice the typo? |
| 83 | + "[Catgory List] Add Another Category", |
| 84 | + props<{ category: Category }>() |
| 85 | +); |
| 86 | +``` |
| 87 | + |
| 88 | +With those issues in mind, NgRx published a new function that allows us to create multiple actions at one: the `createActionGroup` function. |
| 89 | + |
| 90 | +Its usage is fairly simple and is based on two parameters: |
| 91 | + |
| 92 | +- The source of the action, in our case `Category List` |
| 93 | +- A series of properties symbolizing our actions and their parameters |
| 94 | + |
| 95 | +Let's go back to the previous example to see what is changing: |
| 96 | + |
| 97 | +```ts title="state/actions.ts" |
| 98 | +export const categoryActions = createActionGroup({ |
| 99 | + source: 'Category List', |
| 100 | + events: { |
| 101 | + 'Add Category': props<{ category: Category }>(), |
| 102 | + 'Add Another Category': props<{ category: Category }>(), |
| 103 | + } |
| 104 | +}) |
| 105 | +``` |
| 106 | + |
| 107 | +By doing so, all our related actions will be grouped together and we still can create them by calling the `categoryActions` variable that automatically generates you the code to create them: |
| 108 | + |
| 109 | +```ts |
| 110 | +categoryActions.addCategory({ category: 'Food' }); |
| 111 | +categoryActions.addAnotherCategory({ category: 'Food' }); |
| 112 | +``` |
| 113 | + |
| 114 | +:::note |
| 115 | + |
| 116 | +Notice that our action types like `Add Category` written in plain English are converted to `addCategory` - a camel case variable name. This magic is done using [TypeScript's template literal types](https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html) and the [mapped types](https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#handbook-content). [The source code for this](https://github.com/ngrx/platform/blob/master/modules/store/src/action_group_creator_models.ts) is pretty fascinating, if you're a TypeScript enthusiast, I strongly recommend checking it out or listen to. You can also listen to [Brandon Robert's video about the "TypeScript magic" behind the scenes](https://www.youtube.com/watch?v=V6eHIvwDFP4). |
| 117 | + |
| 118 | +::: |
| 119 | + |
| 120 | +## Homework |
| 121 | + |
| 122 | +Yes, you've read it correctly: we have learned how to write some basic code in NgRx, so it is time for some homework! |
| 123 | + |
| 124 | +:::info |
| 125 | + |
| 126 | +We **strongly** recommand you not to move on to the next chapter without adding the homework code as we will be using that code in the next chapters |
| 127 | + |
| 128 | +::: |
| 129 | + |
| 130 | +1. **Create an action for deleting a category** |
| 131 | + It should receive a string with the name of the category, and in the next chapter we will use that code to write the actual logic for deleting the category. |
| 132 | +2. **Create an action for updating a category** |
| 133 | + It must receive a `Category` object (`{name: string}`), and again, we will write the code to update the category in the next chapter |
| 134 | + |
| 135 | +> For this homework, assume categories cannot have duplicate names. We will deal with this problem later. |
| 136 | +
|
| 137 | +:::note |
| 138 | + |
| 139 | +You will find solution code for all the homeworks at the end of the chapters |
| 140 | + |
| 141 | +::: |
| 142 | + |
| 143 | +In this chapter, we learned how to create `Actions`, unique events that specify what should happen to the state. In the next one, we will be writing code that actually does the transformation in the state. |
| 144 | + |
| 145 | +--- |
| 146 | + |
| 147 | +<details> |
| 148 | +<summary>Exercise 1 solution</summary> |
| 149 | + |
| 150 | +**Using `createActionGroup`:** |
| 151 | + |
| 152 | +```ts {5} title="state/actions.ts" |
| 153 | +export const categoryActions = createActionGroup({ |
| 154 | + source: 'Category List', |
| 155 | + events: { |
| 156 | + 'Add Category': props<{ category: Category }>(), |
| 157 | + 'Delete Category': props<{ category: Category }>(), |
| 158 | + } |
| 159 | +}) |
| 160 | +``` |
| 161 | + |
| 162 | +**Using `createAction`:** |
| 163 | + |
| 164 | +```ts {6-9} title="state/actions.ts" |
| 165 | +export const addCategory = createAction( |
| 166 | + "[Category List] Add Category", |
| 167 | + props<{ category: Category }>() |
| 168 | +); |
| 169 | + |
| 170 | +export const deleteCategory = createAction( |
| 171 | + "[Category List] Delete Category", |
| 172 | + props<{name: string}>() |
| 173 | +); |
| 174 | +``` |
| 175 | + |
| 176 | +</details> |
| 177 | + |
| 178 | +<details> |
| 179 | +<summary>Exercise 2 solution</summary> |
| 180 | + |
| 181 | +**Using `createActionGroup`:** |
| 182 | + |
| 183 | +```ts {6} title="state/actions.ts" |
| 184 | +export const categoryActions = createActionGroup({ |
| 185 | + source: 'Category List', |
| 186 | + events: { |
| 187 | + 'Add Category': props<{ category: Category }>(), |
| 188 | + 'Delete Category': props<{ category: Category }>(), |
| 189 | + 'Update Category': props<{ category: Category }>(), |
| 190 | + } |
| 191 | +}) |
| 192 | +``` |
| 193 | + |
| 194 | +**Using `createAction`:** |
| 195 | + |
| 196 | +```ts {11-14} title="state/actions.ts" |
| 197 | +export const addCategory = createAction( |
| 198 | + "[Category List] Add Category", |
| 199 | + props<{ category: Category }>() |
| 200 | +); |
| 201 | + |
| 202 | +export const deleteCategory = createAction( |
| 203 | + "[Category List] Delete Category", |
| 204 | + props<{name: string}>() |
| 205 | +); |
| 206 | + |
| 207 | +export const updateCategory = createAction( |
| 208 | + "[Category List] Update Category", |
| 209 | + props<{ name: string }>() |
| 210 | +); |
| 211 | + |
| 212 | +``` |
| 213 | + |
| 214 | +</details> |
0 commit comments