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? : string) => 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