• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
7In addition, \@Watch can only listen for changes that can be observed.
8
9Before reading this topic, you are advised to read [\@State](./arkts-state.md) to have a understanding of the basic observation capabilities of state management.
10
11> **NOTE**
12>
13> Since API version 9, this decorator is supported in ArkTS widgets.
14>
15> This decorator can be used in atomic services since API version 11.
16
17## Overview
18
19An 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 return, the \@Monitor decorated callback is triggered.
20
21
22## Decorator Description
23
24| \@Watch Decorator| Description                                      |
25| -------------- | ---------------------------------------- |
26| Decorator parameters         | Mandatory. Constant string, which is quoted. Reference to a (string) => void custom component member function.|
27| Custom component variables that can be decorated   | All decorated state variables. Regular variables cannot be watched.              |
28| Order of decorators        | The order of decorators does not affect the actual functions. You can determine it as required. It is recommended that the [\@State](./arkts-state.md), [\@Prop](./arkts-prop.md), and [\@Link](./arkts-link.md) decorators be placed before the \@Watch decorator, to keep the overall style consistent.|
29| 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).|
30
31## Syntax
32
33| Type                                      | Description                                      |
34| ---------------------------------------- | ---------------------------------------- |
35| (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.|
36
37
38## Observed Changes and Behavior
39
401. \@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.
41
422. The \@Watch callback is executed synchronously after the variable change in the custom component.
43
443. 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.
45
464. 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.
47
48
49## Restrictions
50
51- 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.
52
53- 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.
54
55- Calling **async await** from an \@Watch function is not recommended, because asynchronous behavior may cause performance issues of re-rendering.
56
57- The \@Watch parameter is mandatory and must be of the string type. Otherwise, an error will be reported during compilation.
58
59```ts
60// Incorrect format. An error is reported during compilation.
61@State @Watch() num: number = 10;
62@State @Watch(change) num: number = 10;
63
64// Correct format.
65@State @Watch('change') num: number = 10;
66change() {
67  console.log(`xxx`);
68}
69```
70
71- The parameters in \@Watch must be declared method names. Otherwise, an error will be reported during compilation.
72
73```ts
74// Incorrect format. No function with the corresponding name is available, and an error is reported during compilation.
75@State @Watch('change') num: number = 10;
76onChange() {
77  console.log(`xxx`);
78}
79
80// Correct format.
81@State @Watch('change') num: number = 10;
82change() {
83  console.log(`xxx`);
84}
85```
86
87- Common variables cannot be decorated by \@Watch. Otherwise, an error will be reported during compilation.
88
89```ts
90// Incorrect format.
91@Watch('change') num: number = 10;
92change() {
93  console.log(`xxx`);
94}
95
96// Correct format.
97@State @Watch('change') num: number = 10;
98change() {
99  console.log(`xxx`);
100}
101```
102
103
104## Use Scenarios
105
106### \@Watch and Custom Component Update
107
108This 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**.
109
110
111```ts
112@Component
113struct TotalView {
114  @Prop @Watch('onCountUpdated') count: number = 0;
115  @State total: number = 0;
116  // @Watch callback
117  onCountUpdated(propName: string): void {
118    this.total += this.count;
119  }
120
121  build() {
122    Text(`Total: ${this.total}`)
123  }
124}
125
126@Entry
127@Component
128struct CountModifier {
129  @State count: number = 0;
130
131  build() {
132    Column() {
133      Button('add to basket')
134        .onClick(() => {
135          this.count++
136        })
137      TotalView({ count: this.count })
138    }
139  }
140}
141```
142
143The procedure is as follows:
144
1451. The click event **Button.onClick** of the **CountModifier** custom component increases the value of **count**.
146
1472. 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**.
148
1493. The **Text** component in the child component **TotalView** is re-rendered.
150
151
152### Combination of \@Watch and \@Link
153
154This example illustrates how to watch an \@Link decorated variable in a child component.
155
156
157```ts
158class PurchaseItem {
159  static NextId: number = 0;
160  public id: number;
161  public price: number;
162
163  constructor(price: number) {
164    this.id = PurchaseItem.NextId++;
165    this.price = price;
166  }
167}
168
169@Component
170struct BasketViewer {
171  @Link @Watch('onBasketUpdated') shopBasket: PurchaseItem[];
172  @State totalPurchase: number = 0;
173
174  updateTotal(): number {
175    let total = this.shopBasket.reduce((sum, i) => sum + i.price, 0);
176    // A discount is provided when the amount exceeds 100 euros.
177    if (total >= 100) {
178      total = 0.9 * total;
179    }
180    return total;
181  }
182  // @Watch callback
183  onBasketUpdated(propName: string): void {
184    this.totalPurchase = this.updateTotal();
185  }
186
187  build() {
188    Column() {
189      ForEach(this.shopBasket,
190        (item: PurchaseItem) => {
191          Text(`Price: ${item.price.toFixed(2)} €`)
192        },
193        (item: PurchaseItem) => item.id.toString()
194      )
195      Text(`Total: ${this.totalPurchase.toFixed(2)} €`)
196    }
197  }
198}
199
200@Entry
201@Component
202struct BasketModifier {
203  @State shopBasket: PurchaseItem[] = [];
204
205  build() {
206    Column() {
207      Button('Add to basket')
208        .onClick(() => {
209          this.shopBasket.push(new PurchaseItem(Math.round(100 * Math.random())))
210        })
211      BasketViewer({ shopBasket: $shopBasket })
212    }
213  }
214}
215```
216
217The procedure is as follows:
218
2191. **Button.onClick** of the **BasketModifier** component adds an item to **BasketModifier shopBasket**.
220
2212. The value of the \@Link decorated variable **BasketViewer shopBasket** changes.
222
2233. The state management framework calls the \@Watch callback **BasketViewer onBasketUpdated** to update the value of **BasketViewer TotalPurchase**.
224
2254. 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.
226
227### Time for \@Watch to be Called
228
229To 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.
230
231```ts
232@Observed
233class Task {
234  isFinished: boolean = false;
235
236  constructor(isFinished : boolean) {
237    this.isFinished = isFinished;
238  }
239}
240
241@Entry
242@Component
243struct ParentComponent {
244  @State @Watch('onTaskAChanged') taskA: Task = new Task(false);
245  @State @Watch('onTaskBChanged') taskB: Task = new Task(false);
246
247  onTaskAChanged(changedPropertyName: string): void {
248    console.log(`Property of this parent component task is changed: ${changedPropertyName}`);
249  }
250
251  onTaskBChanged(changedPropertyName: string): void {
252    console.log(`Property of this parent component task is changed: ${changedPropertyName}`);
253  }
254
255  build() {
256    Column() {
257      Text(`Parent component task A state: ${this.taskA.isFinished ? 'Finished' : 'Unfinished'}`)
258      Text(`Parent component task B state: ${this.taskB.isFinished ? 'Finished' : 'Unfinished'}`)
259      ChildComponent({ taskA: this.taskA, taskB: this.taskB })
260      Button('Switch Task State')
261        .onClick(() => {
262          this.taskB = new Task(!this.taskB.isFinished);
263          this.taskA = new Task(!this.taskA.isFinished);
264        })
265    }
266  }
267}
268
269@Component
270struct ChildComponent {
271  @ObjectLink @Watch('onObjectLinkTaskChanged') taskB: Task;
272  @Link @Watch('onLinkTaskChanged') taskA: Task;
273
274  onObjectLinkTaskChanged(changedPropertyName: string): void {
275    console.log(`Property of @ObjectLink associated task of the child component is changed: ${changedPropertyName}`);
276  }
277
278  onLinkTaskChanged(changedPropertyName: string): void {
279    console.log(`Property of @Link associated task of the child component is changed: ${changedPropertyName}`);
280  }
281
282  build() {
283    Column() {
284      Text(`Child component task A state: ${this.taskA.isFinished ? 'Finished' : 'Unfinished'}`)
285      Text(`Child component task B state: ${this.taskB.isFinished ? 'Finished' : 'Unfinished'}`)
286    }
287  }
288}
289```
290
291The procedure is as follows:
292
2931. When you click the button to switch the task state, the parent component updates **taskB** associated with \@ObjectLink and **taskA** associated with \@Link.
294
2952. The following information is displayed in sequence in the log:
296    ```
297    Property of this parent component task is changed: taskB
298    Property of this parent component task is changed: taskA
299    Property of @Link associated task of the child component is changed: taskA
300    Property of @ObjectLink associated task of the child component is changed: taskB
301    ```
302
3033. 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.
304
3054. 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.
306
307### Using changedPropertyName for Different Logic Processing
308
309The following example shows how to use **changedPropertyName** in the \@Watch function for different logic processing.
310
311
312```ts
313@Entry
314@Component
315struct UsePropertyName {
316  @State @Watch('countUpdated') apple: number = 0;
317  @State @Watch('countUpdated') cabbage: number = 0;
318  @State fruit: number = 0;
319  // @Watch callback
320  countUpdated(propName: string): void {
321    if (propName == 'apple') {
322      this.fruit = this.apple;
323    }
324  }
325
326  build() {
327    Column() {
328      Text(`Number of apples: ${this.apple.toString()}`).fontSize(30)
329      Text(`Number of cabbages: ${this.cabbage.toString()}`).fontSize(30)
330      Text(`Total number of fruits: ${this.fruit.toString()}`).fontSize(30)
331      Button('Add apples')
332        .onClick(() => {
333          this.apple++;
334        })
335      Button('Add cabbages')
336        .onClick(() => {
337          this.cabbage++;
338        })
339    }
340  }
341}
342```
343
344The procedure is as follows:
345
3461. Click **Button('Add apples')**, the value of **apple** changes.
347
3482. 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.
349
3503. **Text**s bound to **apple** and **fruit** are rendered again.
351
3524. Click **Button('Add cabbages')**, the value of **cabbage** changes.
353
3545. 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.
355
3566. **Text** bound to **cabbage** is rendered again.
357