1# \@Watch Decorator: Getting Notified of State Variable Changes
2
3
4\@Watch is used to listen for state variables. If your application needs watch for value changes of a state variable, you can decorate the variable with \@Watch.
5
6
7> **NOTE**
8>
9> Since API version 9, this decorator is supported in ArkTS widgets.
10>
11> This decorator can be used in atomic services since API version 11.
12
13## Overview
14
15An application can request to be notified whenever the value of the \@Watch decorated variable changes. The \@Watch callback is called when the value change has occurred. \@Watch uses strict equality (===) to determine whether a value is updated in the ArkUI framework. If **false** is returned, the \@Watch callback is triggered.
16
17
18## Decorator Description
19
20| \@Watch Decorator| Description                                      |
21| -------------- | ---------------------------------------- |
22| Decorator parameters         | Mandatory. Constant string, which is quoted. Reference to a (string) => void custom component member function.|
23| Custom component variables that can be decorated   | All decorated state variables. Regular variables cannot be watched.              |
24| Order of decorators        | Place the [\@State](./arkts-state.md), [\@Prop](./arkts-prop.md), or [\@Link](./arkts-link.md) decorator in front of the \@Watch decorator.|
25| Called when| The variable changes and is assigned a value. For details, see [Time for \@Watch to be Called](#time-for-watch-to-be-called).|
26
27## Syntax
28
29| Type                                      | Description                                      |
30| ---------------------------------------- | ---------------------------------------- |
31| (changedPropertyName?&nbsp;:&nbsp;string)&nbsp;=&gt;&nbsp;void | This function is a member function of the custom component. **changedPropertyName** indicates the name of the watched attribute.<br>It is useful when you use the same function as a callback to several watched attributes.<br>It takes the attribute name as a string input parameter and returns nothing.|
32
33
34## Observed Changes and Behavior
35
361. \@Watch callback is triggered when a change of a state variable (including the change of a key in [AppStorage](./arkts-appstorage.md) and [LocalStorage](./arkts-localstorage.md) that are bound in a two-way manner) is observed.
37
382. The \@Watch callback is executed synchronously after the variable change in the custom component.
39
403. If the \@Watch callback mutates other watched variables, their variable @Watch callbacks in the same and other custom components as well as state updates are triggered.
41
424. A \@Watch function is not called upon custom component variable initialization, because initialization is not considered as variable mutation. A \@Watch function is called upon change of the custom component variable.
43
44
45## Restrictions
46
47- Pay attention to the risk of infinite loops. Loops can be caused by the \@Watch callback directly or indirectly mutating the same variable. To avoid loops, do not mutate the \@Watch decorated state variable inside the callback handler.
48
49- Pay attention to performance. The attribute value update function delays component re-render (see the preceding behavior description). The callback should only perform quick computations.
50
51- Calling **async await** from an \@Watch function is not recommended, because asynchronous behavior may cause performance issues of re-rendering.
52
53- The \@Watch parameter is mandatory and must be of the string type. Otherwise, an error will be reported during compilation.
54
55```ts
56// Incorrect format. An error is reported during compilation.
57@State @Watch() num: number = 10;
58@State @Watch(change) num: number = 10;
59
60// Correct format.
61@State @Watch('change') num: number = 10;
62change() {
63  console.log(`xxx`);
64}
65```
66
67- The parameters in \@Watch must be declared method names. Otherwise, an error will be reported during compilation.
68
69```ts
70// Incorrect format. No function with the corresponding name is available, and an error is reported during compilation.
71@State @Watch('change') num: number = 10;
72onChange() {
73  console.log(`xxx`);
74}
75
76// Correct format.
77@State @Watch('change') num: number = 10;
78change() {
79  console.log(`xxx`);
80}
81```
82
83- Common variables cannot be decorated by \@Watch. Otherwise, an error will be reported during compilation.
84
85```ts
86// Incorrect format.
87@Watch('change') num: number = 10;
88change() {
89  console.log(`xxx`);
90}
91
92// Correct format.
93@State @Watch('change') num: number = 10;
94change() {
95  console.log(`xxx`);
96}
97```
98
99
100## Application Scenarios
101
102### \@Watch and Custom Component Update
103
104This example is used to clarify the processing steps of custom component updates and \@Watch. **count** is decorated by \@State in **CountModifier** and \@Prop in **TotalView**.
105
106
107```ts
108@Component
109struct TotalView {
110  @Prop @Watch('onCountUpdated') count: number = 0;
111  @State total: number = 0;
112  // @Watch callback
113  onCountUpdated(propName: string): void {
114    this.total += this.count;
115  }
116
117  build() {
118    Text(`Total: ${this.total}`)
119  }
120}
121
122@Entry
123@Component
124struct CountModifier {
125  @State count: number = 0;
126
127  build() {
128    Column() {
129      Button('add to basket')
130        .onClick(() => {
131          this.count++
132        })
133      TotalView({ count: this.count })
134    }
135  }
136}
137```
138
139The procedure is as follows:
140
1411. The click event **Button.onClick** of the **CountModifier** custom component increases the value of **count**.
142
1432. In response to the change of the @State decorated variable **count**, \@Prop in the child component **TotalView** is updated, and its **\@Watch('onCountUpdated')** callback is invoked, which updates the **total** variable in **TotalView**.
144
1453. The **Text** component in the child component **TotalView** is re-rendered.
146
147
148### Combination of \@Watch and \@Link
149
150This example illustrates how to watch an \@Link decorated variable in a child component.
151
152
153```ts
154class PurchaseItem {
155  static NextId: number = 0;
156  public id: number;
157  public price: number;
158
159  constructor(price: number) {
160    this.id = PurchaseItem.NextId++;
161    this.price = price;
162  }
163}
164
165@Component
166struct BasketViewer {
167  @Link @Watch('onBasketUpdated') shopBasket: PurchaseItem[];
168  @State totalPurchase: number = 0;
169
170  updateTotal(): number {
171    let total = this.shopBasket.reduce((sum, i) => sum + i.price, 0);
172    // A discount is provided when the amount exceeds 100 euros.
173    if (total >= 100) {
174      total = 0.9 * total;
175    }
176    return total;
177  }
178  // @Watch callback
179  onBasketUpdated(propName: string): void {
180    this.totalPurchase = this.updateTotal();
181  }
182
183  build() {
184    Column() {
185      ForEach(this.shopBasket,
186        (item: PurchaseItem) => {
187          Text(`Price: ${item.price.toFixed(2)} €`)
188        },
189        (item: PurchaseItem) => item.id.toString()
190      )
191      Text(`Total: ${this.totalPurchase.toFixed(2)} €`)
192    }
193  }
194}
195
196@Entry
197@Component
198struct BasketModifier {
199  @State shopBasket: PurchaseItem[] = [];
200
201  build() {
202    Column() {
203      Button('Add to basket')
204        .onClick(() => {
205          this.shopBasket.push(new PurchaseItem(Math.round(100 * Math.random())))
206        })
207      BasketViewer({ shopBasket: $shopBasket })
208    }
209  }
210}
211```
212
213The procedure is as follows:
214
2151. **Button.onClick** of the **BasketModifier** component adds an item to **BasketModifier shopBasket**.
216
2172. The value of the \@Link decorated variable **BasketViewer shopBasket** changes.
218
2193. The state management framework calls the \@Watch callback **BasketViewer onBasketUpdated** to update the value of **BasketViewer TotalPurchase**.
220
2214. Because \@Link decorated shopBasket changes (a new item is added), the **ForEach** component executes the item Builder to render and build the new item. Because the @State decorated **totalPurchase** variable changes, the **Text** component is also re-rendered. Re-rendering happens asynchronously.
222
223### Time for \@Watch to be Called
224
225To show the \@Watch callback is called when the state variable changes, this example uses the \@Link and \@ObjectLink decorators in the child component to observe different state objects. You can change the state variable in the parent component and observe the calling sequence of the \@Watch callback to learn the relationship between the time for calling, value assignment, and synchronization.
226
227```ts
228@Observed
229class Task {
230  isFinished: boolean = false;
231
232  constructor(isFinished : boolean) {
233    this.isFinished = isFinished;
234  }
235}
236
237@Entry
238@Component
239struct ParentComponent {
240  @State @Watch('onTaskAChanged') taskA: Task = new Task(false);
241  @State @Watch('onTaskBChanged') taskB: Task = new Task(false);
242
243  onTaskAChanged(changedPropertyName: string): void {
244    console.log(`Property of this parent component task is changed: ${changedPropertyName}`);
245  }
246
247  onTaskBChanged(changedPropertyName: string): void {
248    console.log(`Property of this parent component task is changed: ${changedPropertyName}`);
249  }
250
251  build() {
252    Column() {
253      Text(`Parent component task A state: ${this.taskA.isFinished ? 'Finished' : 'Unfinished'}`)
254      Text(`Parent component task B state: ${this.taskB.isFinished ? 'Finished' : 'Unfinished'}`)
255      ChildComponent({ taskA: this.taskA, taskB: this.taskB })
256      Button('Switch Task State')
257        .onClick(() => {
258          this.taskB = new Task(!this.taskB.isFinished);
259          this.taskA = new Task(!this.taskA.isFinished);
260        })
261    }
262  }
263}
264
265@Component
266struct ChildComponent {
267  @ObjectLink @Watch('onObjectLinkTaskChanged') taskB: Task;
268  @Link @Watch('onLinkTaskChanged') taskA: Task;
269
270  onObjectLinkTaskChanged(changedPropertyName: string): void {
271    console.log(`Property of @ObjectLink associated task of the child component is changed: ${changedPropertyName}`);
272  }
273
274  onLinkTaskChanged(changedPropertyName: string): void {
275    console.log(`Property of @Link associated task of the child component is changed: ${changedPropertyName}`);
276  }
277
278  build() {
279    Column() {
280      Text(`Child component task A state: ${this.taskA.isFinished ? 'Finished' : 'Unfinished'}`)
281      Text(`Child component task B state: ${this.taskB.isFinished ? 'Finished' : 'Unfinished'}`)
282    }
283  }
284}
285```
286
287The procedure is as follows:
288
2891. When you click the button to switch the task state, the parent component updates **taskB** associated with \@ObjectLink and **taskA** associated with \@Link.
290
2912. The following information is displayed in sequence in the log:
292    ```
293    Property of this parent component task is changed: taskB
294    Property of this parent component task is changed: taskA
295    Property of @Link associated task of the child component is changed: taskA
296    Property of @ObjectLink associated task of the child component is changed: taskB
297    ```
298
2993. The log shows that the calling sequence of the parent component is the same as the change sequence, but the calling sequence of \@Link and \@ObjectLink in the child component is different from the variable update sequence in the parent component. This is because the variables of the parent component are updated in real time, but \@Link and \@ObjectLink in the child component obtain the updated data at different time. The \@Link associated state is updated synchronously, therefore, state change immediately calls the \@Watch callback. The update of \@ObjectLink associated state depends on the synchronization of the parent component. The \@Watch callback is called only when the parent component updates and passes the updated variables to the child component. Therefore, the calling sequence is slightly later than that of \@Link.
300
3014. This behavior meets the expectation. The \@Watch callback is invoked based on the actual state variable change time.  Similarly, \@Prop may behave similarly to \@ObjectLink, and the time for its callback to be invoked is slightly later.
302
303### Using changedPropertyName for Different Logic Processing
304
305The following example shows how to use **changedPropertyName** in the \@Watch function for different logic processing.
306
307
308```ts
309@Entry
310@Component
311struct UsePropertyName {
312  @State @Watch('countUpdated') apple: number = 0;
313  @State @Watch('countUpdated') cabbage: number = 0;
314  @State fruit: number = 0;
315  // @Watch callback
316  countUpdated(propName: string): void {
317    if (propName == 'apple') {
318      this.fruit = this.apple;
319    }
320  }
321
322  build() {
323    Column() {
324      Text(`Number of apples: ${this.apple.toString()}`).fontSize(30)
325      Text(`Number of cabbages: ${this.cabbage.toString()}`).fontSize(30)
326      Text(`Total number of fruits: ${this.fruit.toString()}`).fontSize(30)
327      Button('Add apples')
328        .onClick(() => {
329          this.apple++;
330        })
331      Button('Add cabbages')
332        .onClick(() => {
333          this.cabbage++;
334        })
335    }
336  }
337}
338```
339
340The procedure is as follows:
341
3421. Click **Button('Add apples')**, the value of **apple** changes.
343
3442. The state management framework calls the \@Watch function **countUpdated** and the value of state variable **apple** is changed; the **if** logic condition is met, the value of **fruit** changes.
345
3463. **Text**s bound to **apple** and **fruit** are rendered again.
347
3484. Click **Button('Add cabbages')**, the value of **cabbage** changes.
349
3505. The state management framework calls the \@Watch function **countUpdated** and the value of state variable **cabbage** is changed; the **if** logic condition is not met, the value of **fruit** does not change.
351
3526. **Text** bound to **cabbage** is rendered again.
353