• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# \@Monitor Decorator: Listening for Value Changes of the State Variables
2
3You can use \@Monitor, a method decorator in state management V2, to enhance the capability of the state management framework to listen for the state variable changes
4
5
6\@Monitor provides the capability of listening for state variables of V2. Before reading this topic, you are advised to read [\@ComponentV2](./arkts-new-componentV2.md), [\@ObservedV2 and \@Trace](./arkts-new-observedV2-and-trace.md), and [\@Local](./arkts-new-local.md).
7
8>**NOTE**
9>
10>The \@Monitor decorator is supported since API version 12.
11>
12
13## Overview
14
15To listen for value changes of the state variables in a lower level, you can use the \@Monitor decorator:
16
17- The \@Monitor decorator can be used in custom components decorated by \@ComponentV2. But it cannot listen for the changes of the state variables that are not decorated by these decorators: [\@Local](arkts-new-local.md), [\@Param](arkts-new-param.md), [\@Provider](arkts-new-Provider-and-Consumer.md), [\@Consumer](arkts-new-Provider-and-Consumer.md), and [\@Computed](arkts-new-Computed.md).
18
19- The \@Monitor decorator can be used in a class together with [\@ObservedV2 and \@Trace](arkts-new-observedV2-and-trace.md) decorators. But it cannot be used in a class that is not decorated by \@ObservedV2. \@Monitor cannot listen for the properties that are not decorated by \@Trace.
20- When the listened property changes, the callback defined by \@Monitor will be called. Strict equality (===) is used to determine whether a property is changed. If **false** is return, the \@Monitor decorated callback is triggered. When a property is changed for multiple times in an event, the initial value will be compared with the final value to determine whether the property is changed.
21- A single \@Monitor decorator can listen for the changes of multiple properties at the same time. When these properties change together in an event, the \@Monitor callback method is triggered only once.
22- The \@Monitor decorator has lower-level listening capability and can listen for changes of specified items in nested classes, multi-dimensional arrays, and object arrays. The observation requires that \@ObservedV2 decorate the nested class and \@Trace decorate the member properties in an object array.
23- In the inheritance scenario, you can define \@Monitor for the same property in the parent and child components for listening. When the property changes, the \@Monitor callback defined in the parent and child components is called.
24- Similar to the [\@Watch](arkts-watch.md) decorator, you should define the callback functions by yourselves. The difference is that the \@Watch decorator uses the function name as a parameter, while the \@Monitor directly decorates the callback function. For details about the comparison between \@Monitor and \@Watch, see [Comparing \@Monitor with \@Watch](#comparing-monitor-with-watch).
25
26## Limitations of the \@Watch decorator in State Management V1
27
28This V1 version cannot listen for the changes of an object, a single property in an array, or array items. It also cannot obtain the value before change.
29
30```ts
31@Observed
32class Info {
33  name: string = "Tom";
34  age: number = 25;
35}
36@Entry
37@Component
38struct Index {
39  @State @Watch('onInfoChange') info: Info = new Info();
40  @State @Watch('onNumArrChange') numArr: number[] = [1,2,3,4,5];
41
42  onInfoChange() {
43    console.log(`info after change name: ${this.info.name}, age: ${this.info.age} `);
44  }
45  onNumArrChange() {
46    console.log(`numArr after change ${JSON.stringify(this.numArr)}`);
47  }
48  build() {
49    Row() {
50      Column() {
51        Button("change info name")
52          .onClick(() => {
53            this.info.name = "Jack";
54          })
55        Button("change info age")
56          .onClick(() => {
57            this.info.age = 30;
58          })
59        Button("change numArr[2]")
60          .onClick(() => {
61            this.numArr[2] = 5;
62          })
63        Button("change numArr[3]")
64          .onClick(() => {
65            this.numArr[3] = 6;
66          })
67      }
68      .width('100%')
69    }
70    .height('100%')
71  }
72}
73```
74
75In the preceding code, when you click **change info name** to change the **name** property in **info**, or click **change info age** to change **age**, the **info** registered \@Watch callback is triggered. When you click **change numArr[2]** to change the third element in **numArr**, or click **change numArr[3]** to change the fourth element, the **numArr** registered \@Watch callback is triggered. In these two callbacks, the value before data change cannot be obtained. This makes it inconvenient for you to listen for the variable changes because you cannot find out which property or element is changed to trigger \@Watch event in a more complex scenario. Therefore, the \@Monitor decorator comes into the picture to listen for the changes of an object, a single property in an array, or an array item and obtain the value before change.
76
77## Decorator Description
78
79| \@Monitor Property Decorator| Description                                                        |
80| ------------------- | ------------------------------------------------------------ |
81| Decorator parameter         | Object property name of the string type. This decorator can listen for multiple object properties at the same time. Each property is separated by commas (,), for example, @Monitor ("prop1", "prop2"). In addition, properties such as an element in a multi-dimensional array, a property in a nested object, and a property in an object array can be listened in a lower level. For details, see [Listened Changes](#listened-changes).|
82| Decorated object           | \@Monitor decorated member method. This callback is triggered when the listened property changes. The variable of [IMonitor type](#imonitor-type) will be set as a parameter to provide related information before and after change.|
83
84## API Description
85
86### IMonitor Type
87
88Variables of the IMonitor type are used as parameters for \@Monitor to decorate a method.
89
90| Property      | Type           | Parameter         | Return Value            | Description                                                        |
91| ---------- | --------------- | ------------- | ------------------ | ------------------------------------------------------------ |
92| dirty      | Array\<string\> | None.           | None.                | Saves the changed property name.                                      |
93| value\<T\> | function        | path?: string | IMonitorValue\<T\> | Obtains the change information of a specified property (**path**). If **path** is not specified, @Monitor will return the first changed property information in the listening sequence.|
94
95### IMonitorValue\<T\> Type
96
97Saves the information about property changes, including the property name, original value, and new value.
98
99| Property  | Type  | Description                      |
100| ------ | ------ | -------------------------- |
101| before | T      | Listens for the value before the property change.    |
102| now    | T      | Listens for the current value after the property changes.|
103| path   | string | Listened property name.            |
104
105## Listened Changes
106
107### Using \@Monitor in Custom Components Decorated by \@ComponentV2
108
109When the state variables listened by \@Monitor change, the callback is triggered.
110
111- Variables listened by \@Monitor need to be decorated by \@Local, \@Param, \@Provider, \@Consumer, and \@Computed. Otherwise, they cannot be listened when they change. \@Monitor can listen for multiple state variables at the same time. Names of these variables are separated by commas (,).
112
113  ```ts
114  @Entry
115  @ComponentV2
116  struct Index {
117    @Local message: string = "Hello World";
118    @Local name: string = "Tom";
119    @Local age: number = 24;
120    @Monitor("message", "name")
121    onStrChange(monitor: IMonitor) {
122      monitor.dirty.forEach((path: string) => {
123        console.log(`${path} changed from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`)
124      })
125    }
126    build() {
127      Column() {
128        Button("change string")
129          .onClick(() => {
130            this.message += "!";
131            this.name = "Jack";
132        })
133      }
134    }
135  }
136  ```
137
138- When the state variable listened by \@Monitor is a class object, only the overall object changes can be listened. To listen for the changes of a class property, this property should be decorated by \@Trace.
139
140  ```ts
141  class Info {
142    name: string;
143    age: number;
144    constructor(name: string, age: number) {
145      this.name = name;
146      this.age = age;
147    }
148  }
149  @Entry
150  @ComponentV2
151  struct Index {
152    @Local info: Info = new Info("Tom", 25);
153    @Monitor("info")
154    infoChange(monitor: IMonitor) {
155      console.log(`info change`);
156    }
157    @Monitor("info.name")
158    infoPropertyChange(monitor: IMonitor) {
159      console.log(`info name change`);
160    }
161    build() {
162      Column() {
163        Text(`name: ${this.info.name}, age: ${this.info.age}`)
164        Button("change info")
165          .onClick(() => {
166            this.info = new Info ("Lucy", 18); // Can listen for the change.
167          })
168        Button("change info.name")
169          .onClick(() => {
170            this.info.name = "Jack"; // Cannot listen for the change.
171          })
172      }
173    }
174  }
175  ```
176
177### Using \@Monitor in Classes Decorated by \@ObservedV2
178
179When the properties listened by \@Monitor change, the callback is triggered.
180
181- The object property listened by \@Monitor should be decorated by \@Trace. Otherwise, the property cannot be listened. \@Monitor can listen for multiple properties at the same time. These properties are separated by commas (,).
182
183```ts
184@ObservedV2
185class Info {
186  @Trace name: string = "Tom";
187  @Trace region: string = "North";
188  @Trace job: string = "Teacher";
189  age: number = 25;
190  // name is decorated by @Trace. Can listen for the change.
191  @Monitor("name")
192  onNameChange(monitor: IMonitor) {
193    console.log(`name change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
194  }
195  // age is not decorated by @Trace. Cannot listen for the change.
196  @Monitor("age")
197  onAgeChange(monitor: IMonitor) {
198    console.log(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
199  }
200  // region and job are decorated by @Trace. Can listen for the change.
201  @Monitor("region", "job")
202  onChange(monitor: IMonitor) {
203    monitor.dirty.forEach((path: string) => {
204      console.log(`${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
205    })
206  }
207}
208@Entry
209@ComponentV2
210struct Index {
211  info: Info = new Info();
212  build() {
213    Column() {
214      Button("change name")
215        .onClick(() => {
216          this.info.name = "Jack"; // Can trigger the onNameChange method.
217        })
218      Button("change age")
219        .onClick(() => {
220          this.info.age = 26; // Cannot trigger the onAgeChange method.
221        })
222      Button("change region")
223        .onClick(() => {
224          this.info.region = "South"; // Can trigger the onChange method.
225        })
226      Button("change job")
227        .onClick(() => {
228          this.info.job = "Driver"; // Can trigger the onChange method.
229        })
230    }
231  }
232}
233```
234
235- \@Monitor can listen for the changes of lower-level properties which should be decorated by @Trace.
236
237```ts
238@ObservedV2
239class Inner {
240  @Trace num: number = 0;
241}
242@ObservedV2
243class Outer {
244  inner: Inner = new Inner();
245  @Monitor("inner.num")
246  onChange(monitor: IMonitor) {
247    console.log(`inner.num change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
248  }
249}
250@Entry
251@ComponentV2
252struct Index {
253  outer: Outer = new Outer();
254  build() {
255    Column() {
256      Button("change name")
257        .onClick(() => {
258          this.outer.inner.num = 100; // Can trigger the onChange method.
259        })
260    }
261  }
262}
263```
264
265- In the inheritance class scenario, you can listen for the same property for multiple times in the inheritance chain.
266
267```ts
268@ObservedV2
269class Base {
270  @Trace name: string;
271  // Listen for the name property of the base class.
272  @Monitor("name")
273  onBaseNameChange(monitor: IMonitor) {
274    console.log(`Base Class name change`);
275  }
276  constructor(name: string) {
277    this.name = name;
278  }
279}
280@ObservedV2
281class Derived extends Base {
282  // Listen for the name property of the inheritance class.
283  @Monitor("name")
284  onDerivedNameChange(monitor: IMonitor) {
285    console.log(`Derived Class name change`);
286  }
287  constructor(name: string) {
288    super(name);
289  }
290}
291@Entry
292@ComponentV2
293struct Index {
294  derived: Derived = new Derived("AAA");
295  build() {
296    Column() {
297      Button("change name")
298        .onClick(() => {
299          this.derived.name = "BBB"; // Can trigger the onBaseNameChange and onDerivedNameChange methods in sequence.
300        })
301    }
302  }
303}
304```
305
306### General Listening Capability
307
308\@Monitor also has some general listening capabilities.
309
310- \@Monitor can listen for items in arrays, including multi-dimensional arrays and object arrays. \@Monitor cannot listen for changes caused by calling APIs of built-in types (Array, Map, Date, and Set). When \@Monitor listens for the entire array, only the value changes to the entire array can be observed. But you can listen for the length change of the array to determine whether the array is inserted or deleted. Currently, only periods (.) can be used to listen for lower-level properties and array items.
311
312```ts
313@ObservedV2
314class Info {
315  @Trace name: string;
316  @Trace age: number;
317
318  constructor(name: string, age: number) {
319    this.name = name;
320    this.age = age;
321  }
322}
323@ObservedV2
324class ArrMonitor {
325  @Trace dimensionTwo: number[][] = [[1,1,1],[2,2,2],[3,3,3]];
326  @Trace dimensionThree: number[][][] = [[[1],[2],[3]],[[4],[5],[6]],[[7],[8],[9]]];
327  @Trace infoArr: Info[] = [new Info("Jack", 24), new Info("Lucy", 18)];
328  // dimensionTwo is a two-dimensional simple array and is decorated by @Trace. Can observe the element changes.
329  @Monitor("dimensionTwo.0.0", "dimensionTwo.1.1")
330  onDimensionTwoChange(monitor: IMonitor) {
331    monitor.dirty.forEach((path: string) => {
332      console.log(`dimensionTwo path: ${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
333    })
334  }
335  // dimensionThree is a three-dimensional simple array and is decorated by @Trace. Can observe the element changes.
336  @Monitor("dimensionThree.0.0.0", "dimensionThree.1.1.0")
337  onDimensionThreeChange(monitor: IMonitor) {
338    monitor.dirty.forEach((path: string) => {
339      console.log(`dimensionThree path: ${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
340    })
341  }
342  // name and age properties of the info class are decorated by @Trace. Can listen for the change.
343  @Monitor("infoArr.0.name", "infoArr.1.age")
344  onInfoArrPropertyChange(monitor: IMonitor) {
345    monitor.dirty.forEach((path: string) => {
346      console.log(`infoArr path:${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
347    })
348  }
349  // infoArr is decorated by @Trace. Can listen for the value changes.
350  @Monitor("infoArr")
351  onInfoArrChange(monitor: IMonitor) {
352    console.log(`infoArr whole change`);
353  }
354  // Can listen for the length change of the infoArr.
355  @Monitor("infoArr.length")
356  onInfoArrLengthChange(monitor: IMonitor) {
357    console.log(`infoArr length change`);
358  }
359}
360@Entry
361@ComponentV2
362struct Index {
363  arrMonitor: ArrMonitor = new ArrMonitor();
364  build() {
365    Column() {
366      Button("Change dimensionTwo")
367        .onClick(() => {
368          // Can trigger the onDimensionTwoChange method.
369          this.arrMonitor.dimensionTwo[0][0]++;
370          this.arrMonitor.dimensionTwo[1][1]++;
371        })
372      Button("Change dimensionThree")
373        .onClick(() => {
374          // Can trigger the onDimensionThreeChange method.
375          this.arrMonitor.dimensionThree[0][0][0]++;
376          this.arrMonitor.dimensionThree[1][1][0]++;
377        })
378      Button("Change info property")
379        .onClick(() => {
380          // Can trigger the onInfoArrPropertyChange method.
381          this.arrMonitor.infoArr[0].name = "Tom";
382          this.arrMonitor.infoArr[1].age = 19;
383        })
384      Button("Change whole infoArr")
385        .onClick(() => {
386          // Can trigger the onInfoArrChange, onInfoArrPropertyChange, and onInfoArrLengthChange methods.
387          this.arrMonitor.infoArr = [new Info("Cindy", 8)];
388        })
389      Button("Push new info to infoArr")
390        .onClick(() => {
391          // Can trigger the onInfoArrPropertyChange and onInfoArrLengthChange methods.
392          this.arrMonitor.infoArr.push(new Info("David", 50));
393        })
394    }
395  }
396}
397```
398
399- When the entire object changes but the listened property remains unchanged, the \@Monitor callback is not triggered.
400
401The following code represents the behavior in the comment when you execute the instructions in the sequence of Step 1, Step 2 and Step 3.
402
403If you only execute the instruction of Step 2 or Step 3 to change the values of **name** or **age**, the **onNameChange** and **onAgeChange** methods are triggered.
404
405```ts
406@ObservedV2
407class Info {
408  @Trace person: Person;
409  @Monitor("person.name")
410  onNameChange(monitor: IMonitor) {
411    console.log(`name change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
412  }
413  @Monitor("person.age")
414  onAgeChange(monitor: IMonitor) {
415    console.log(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
416  }
417  constructor(name: string, age: number) {
418    this.person = new Person(name, age);
419  }
420}
421@ObservedV2
422class Person {
423  @Trace name: string;
424  @Trace age: number;
425  constructor(name: string, age: number) {
426    this.name = name;
427    this.age = age;
428  }
429}
430@Entry
431@ComponentV2
432struct Index {
433  info: Info = new Info("Tom", 25);
434  build() {
435    Column() {
436      Button("Step 1, only change name")
437        .onClick(() => {
438          this.info.person = new Person("Jack", 25);  // Can trigger the onNameChange method, but not the onAgeChange method.
439        })
440      Button("Step 2, only change age")
441        .onClick(() => {
442          this.info.person = new Person("Jack", 18);  // Can trigger the onAgeChange method, but not the onNameChange method.
443        })
444      Button("Step 3, change name and age")
445        .onClick(() => {
446          this.info.person = new Person("Lucy", 19);  // Can trigger the onNameChange and onAgeChange methods.
447        })
448    }
449  }
450}
451```
452
453- If the property listened by \@Monitor is changed for multiple times in an event, the last change is used.
454
455```ts
456@ObservedV2
457class Frequence {
458  @Trace count: number = 0;
459  @Monitor("count")
460  onCountChange(monitor: IMonitor) {
461    console.log(`count change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
462  }
463}
464@Entry
465@ComponentV2
466struct Index {
467  frequence: Frequence = new Frequence();
468  build() {
469    Column() {
470      Button("change count to 1000")
471        .onClick(() => {
472          for (let i = 1; i <= 1000; i++) {
473            this.frequence.count = i;
474          }
475        })
476      Button("change count to 0 then to 1000")
477        .onClick(() => {
478          for (let i = 999; i >= 0; i--) {
479            this.frequence.count = i;
480          }
481          this.frequence.count = 1000; // Cannot trigger the onCountChange method at last.
482        })
483    }
484  }
485}
486```
487
488After you click **change count to 1000**, the **onCountChange** method is triggered and the **count change from 0 to 1000** log is output. After you click **change count to 0 then to 1000**, the **onCountChange** method is not triggered because the value of **count** property remains 1000 before and after the event.
489
490## Constraints
491
492Pay attention to the following constraints when using \@Monitor:
493
494- Do not listen for the same property for multiple times in a class. When a property in a class is listened for multiple times, only the last listening method takes effect.
495
496```ts
497@ObservedV2
498class Info {
499  @Trace name: string = "Tom";
500  @Monitor("name")
501  onNameChange(monitor: IMonitor) {
502    console.log(`onNameChange`);
503  }
504  @Monitor("name")
505  onNameChangeDuplicate(monitor: IMonitor) {
506    console.log(`onNameChangeDuplicate`);
507  }
508}
509@Entry
510@ComponentV2
511struct Index {
512  info: Info = new Info();
513  build() {
514    Column() {
515      Button("change name")
516        .onClick(() => {
517          this.info.name = "Jack"; // Only the onNameChangeDuplicate method is triggered.
518        })
519    }
520  }
521}
522```
523
524- The \@Monitor parameter must be a string that listens for the property name. Only string literals, **const** constants, and **enum** enumerated values can be used as parameters. If a variable is used as a parameter, only the property corresponding to the variable value during \@Monitor initialization is listened. When a variable is changed, \@Monitor cannot change the listened property in real time. That is, the target property listened by \@Monitor is determined during initialization and cannot be dynamically changed. Do not use variables as \@Monitor parameters for initialization.
525
526```ts
527const t2: string = "t2"; // Constant
528enum ENUM {
529  T3 = "t3" // Enum
530};
531let t4: string = "t4"; // Variable
532@ObservedV2
533class Info {
534  @Trace t1: number = 0;
535  @Trace t2: number = 0;
536  @Trace t3: number = 0;
537  @Trace t4: number = 0;
538  @Trace t5: number = 0;
539  @Monitor("t1") // String literal
540  onT1Change(monitor: IMonitor) {
541    console.log(`t1 change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
542  }
543  @Monitor(t2)
544  onT2Change(monitor: IMonitor) {
545    console.log(`t2 change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
546  }
547  @Monitor(ENUM.T3)
548  onT3Change(monitor: IMonitor) {
549    console.log(`t3 change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
550  }
551  @Monitor(t4)
552  onT4Change(monitor: IMonitor) {
553    console.log(`t4 change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
554  }
555}
556@Entry
557@ComponentV2
558struct Index {
559  info: Info = new Info();
560  build() {
561    Column() {
562      Button("Change t1")
563        .onClick(() => {
564          this.info.t1++; // Can trigger the onT1Change method.
565        })
566      Button("Change t2")
567        .onClick(() => {
568          this.info.t2++; // Can trigger the onT2Change method.
569        })
570      Button("Change t3")
571        .onClick(() => {
572          this.info.t3++; // Can trigger the onT3Change method.
573        })
574      Button("Change t4")
575        .onClick(() => {
576          this.info.t4++; // Can trigger the onT4Change method.
577        })
578      Button("Change var t4 to t5")
579        .onClick(() => {
580          t4 = "t5"; // Change the variable value to "t5".
581        })
582      Button("Change t5")
583        .onClick(() => {
584          this.info.t5++; // The onT4Change still listens for t4. Cannot trigger the method.
585        })
586      Button("Change t4 again")
587        .onClick(() => {
588          this.info.t4++; // Can trigger the onT4Change method.
589        })
590    }
591  }
592}
593```
594
595- Changing the listened property in \@Monitor again may cause infinite loops, which is not recommended.
596
597```ts
598@ObservedV2
599class Info {
600  @Trace count: number = 0;
601  @Monitor("count")
602  onCountChange(monitor: IMonitor) {
603    this.count++; // Avoid using this method because it may cause infinite loops.
604  }
605}
606```
607
608## Comparing \@Monitor with \@Watch
609
610The following table compares the usage and functions of \@Monitor and \@Watch.
611
612|                    | \@Watch                                 | \@Monitor                                                    |
613| ------------------ | --------------------------------------- | ------------------------------------------------------------ |
614| Parameter              | Callback method name.                             | Listened state variable name and property name.                                      |
615| Number of listened targets        | A single state variable.                   | Multiple state variables.                                      |
616| Listening capability          | Listen for the top-level state variables.          | Listen for the lower-level state variables.                                |
617| Obtain the value before change| No.                     | Yes.                                            |
618| Listening Condition          | The listened object is a state variable.                     | The listened object is a state variable or a class member property decorated by \@Trace.               |
619| Constraints          | Used only in \@Component decorated custom components.| Used in \@ComponentV2 decorated custom components and \@ObservedV2 decorated classes.|
620
621## Use Scenarios
622
623### Listening for Lower-level Property Changes
624
625\@Monitor can listen for the lower-level property changes and classify them based on the values before and after the changes.
626
627In the following example, the change of property **value** is listened and the display style of the **Text** component is changed based on the change amplitude.
628
629```ts
630@ObservedV2
631class Info {
632  @Trace value: number = 50;
633}
634@ObservedV2
635class UIStyle {
636  info: Info = new Info();
637  @Trace color: Color = Color.Black;
638  @Trace fontSize: number = 45;
639  @Monitor("info.value")
640  onValueChange(monitor: IMonitor) {
641    let lastValue: number = monitor.value()?.before as number;
642    let curValue: number = monitor.value()?.now as number;
643    if (lastValue != 0) {
644      let diffPercent: number = (curValue - lastValue) / lastValue;
645      if (diffPercent > 0.1) {
646        this.color = Color.Red;
647        this.fontSize = 50;
648      } else if (diffPercent < -0.1) {
649        this.color = Color.Green;
650        this.fontSize = 40;
651      } else {
652        this.color = Color.Black;
653        this.fontSize = 45;
654      }
655    }
656  }
657}
658@Entry
659@ComponentV2
660struct Index {
661  textStyle: UIStyle = new UIStyle();
662  build() {
663    Column() {
664      Text(`Important Value: ${this.textStyle.info.value}`)
665        .fontColor(this.textStyle.color)
666        .fontSize(this.textStyle.fontSize)
667      Button("change!")
668        .onClick(() => {
669          this.textStyle.info.value = Math.floor(Math.random() * 100) + 1;
670        })
671    }
672  }
673}
674```
675
676## FAQs
677
678### Effective and Expiration Time of Variable Listening by the \@Monitor in the Custom Component
679
680When \@Monitor is defined in a custom component decorated by \@ComponentV2, \@Monitor takes effect after the state variable is initialized and becomes invalid when the component is destroyed.
681
682```ts
683@ObservedV2
684class Info {
685  @Trace message: string = "not initialized";
686
687  constructor() {
688    console.log("in constructor message change to initialized");
689    this.message = "initialized";
690  }
691}
692@ComponentV2
693struct Child {
694  @Param info: Info = new Info();
695  @Monitor("info.message")
696  onMessageChange(monitor: IMonitor) {
697    console.log(`Child message change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
698  }
699  aboutToAppear(): void {
700    this.info.message = "Child aboutToAppear";
701  }
702  aboutToDisappear(): void {
703    console.log("Child aboutToDisappear");
704    this.info.message = "Child aboutToDisappear";
705  }
706  build() {
707    Column() {
708      Text("Child")
709      Button("change message in Child")
710        .onClick(() => {
711          this.info.message = "Child click to change Message";
712        })
713    }
714    .borderColor(Color.Red)
715    .borderWidth(2)
716
717  }
718}
719@Entry
720@ComponentV2
721struct Index {
722  @Local info: Info = new Info();
723  @Local flag: boolean = false;
724  @Monitor("info.message")
725  onMessageChange(monitor: IMonitor) {
726    console.log(`Index message change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
727  }
728
729  build() {
730    Column() {
731      Button("show/hide Child")
732        .onClick(() => {
733          this.flag = !this.flag
734        })
735      Button("change message in Index")
736        .onClick(() => {
737          this.info.message = "Index click to change Message";
738        })
739      if (this.flag) {
740        Child({ info: this.info })
741      }
742    }
743  }
744}
745```
746
747In the preceding example, you can create and destroy a **Child** component to observe the effective and expiration time of the \@Monitor defined in the custom component. You are advised to follow the steps below:
748
749- When the **Index** component creates an instance of the **Info** class, the log outputs the message: **in constructor message change to initialized**. At this time, the \@Monitor of the **Index** component has not been initialized successfully, so \@Monitor cannot listen for the message change.
750- After the **Index** component is created and the page is loaded, click **change message in Index** button. \@Monitor now can listen for the change and the log outputs the message "Index message change from initialized to Index click to change Message".
751- Click the **show/hide Child** button to create a **Child** component. After this component initializes the \@Param decorated variables and \@Monitor, call the **aboutToAppear** callback of the **Child** component to change the message. In this case, the \@Monitor of the **Index** and **Child** components can listen for the change, and the logs outputs the messages "Index message change from Index click to change Message to Child aboutToAppear" and "Child message change from Index click to change Message to Child aboutToAppear."
752- Click **change message in Child** button to change the message. In this case, the \@Monitor of the **Index** and **Child** components can listen for the change, and the log outputs the messages "Index message change from Child aboutToAppear to Child click to change Message" and "Child message change from Child aboutToAppear to Child click to change Message."
753- Click the **show/hide Child** button to destroy the **Child** component and call the **aboutToDisappear** callback to change the message. In this case, the \@Monitor of the **Index** and **Child** components can listen for the change, and the log outputs the messages "Child aboutToDisappear, Index message change from Child click to change Message to Child aboutToDisappear", and "Child message change from Child click to change Message to Child aboutToDisappear."
754- Click **change message in Index** button to change the message. In this case, the **Child** component is destroyed, and the \@Monitor is deregistered. Only the \@Monitor of the **Index** component can listen for the changes and the log outputs the message "Index message change from Child aboutToDisappear to Index click to change Message."
755
756The preceding steps indicate that the \@Monitor defined in the **Child** component takes effect when the **Child** component is created and initialized, and becomes invalid when the **Child** component is destroyed.
757
758### Effective and Expiration Time of Variable Listening by the \@Monitor in the Class
759
760When \@Monitor is defined in a class decorated by \@ObservedV2, \@Monitor takes effect after the class is created and becomes invalid when the class is destroyed.
761
762```ts
763@ObservedV2
764class Info {
765  @Trace message: string = "not initialized";
766
767  constructor() {
768    this.message = "initialized";
769  }
770  @Monitor("message")
771  onMessageChange(monitor: IMonitor) {
772    console.log(`message change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
773  }
774}
775
776@Entry
777@ComponentV2
778struct Index {
779  info: Info = new Info();
780
781  aboutToAppear(): void {
782    this.info.message = "Index aboutToAppear";
783  }
784
785  build() {
786    Column() {
787      Button("change message")
788        .onClick(() => {
789          this.info.message = "Index click to change message";
790        })
791    }
792  }
793}
794```
795
796In the preceding example, \@Monitor takes effect after the **info** class is created, which is later than the **constructor** of the class and earlier than the **aboutToAppear** of the custom component. After the page is loaded, click **change message** button to modify the message variable. The log outputs the messages as below:
797
798```ts
799message change from initialized to Index aboutToAppear
800message change from Index aboutToAppear to Index click to change message
801```
802
803\@Monitor defined in a class becomes invalid when the class is destroyed. However, the garbage collection mechanism determines whether a class is actually destroyed and released. Even if the custom component is destroyed, the class is not destroyed accordingly. As a result, the \@Monitor defined in the class still listens for changes.
804
805```ts
806@ObservedV2
807class InfoWrapper {
808  info?: Info;
809  constructor(info: Info) {
810    this.info = info;
811  }
812  @Monitor("info.age")
813  onInfoAgeChange(monitor: IMonitor) {
814    console.log(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`)
815  }
816}
817@ObservedV2
818class Info {
819  @Trace age: number;
820  constructor(age: number) {
821    this.age = age;
822  }
823}
824@ComponentV2
825struct Child {
826  @Param @Require infoWrapper: InfoWrapper;
827  aboutToDisappear(): void {
828    console.log("Child aboutToDisappear", this.infoWrapper.info?.age)
829  }
830  build() {
831    Column() {
832      Text(`${this.infoWrapper.info?.age}`)
833    }
834  }
835}
836@Entry
837@ComponentV2
838struct Index {
839  dataArray: Info[] = [];
840  @Local showFlag: boolean = true;
841  aboutToAppear(): void {
842    for (let i = 0; i < 5; i++) {
843      this.dataArray.push(new Info(i));
844    }
845  }
846  build() {
847    Column() {
848      Button("change showFlag")
849        .onClick(() => {
850          this.showFlag = !this.showFlag;
851        })
852      Button("change number")
853        .onClick(() => {
854          console.log("click to change age")
855          this.dataArray.forEach((info: Info) => {
856            info.age += 100;
857          })
858        })
859      if (this.showFlag) {
860        Column() {
861          Text("Childs")
862          ForEach(this.dataArray, (info: Info) => {
863            Child({ infoWrapper: new InfoWrapper(info) })
864          })
865        }
866        .borderColor(Color.Red)
867        .borderWidth(2)
868      }
869    }
870  }
871}
872```
873
874In the preceding example, when you click **change showFlag** to switch the condition of the **if** component, the **Child** component is destroyed. But when you click **change number** to change the value of **age**, the \@Monitor callback defined in **InfoWrapper** is still triggered. This is because the custom component **Child** has executed **aboutToDisappear**, but its member variable **infoWrapper** is not destroyed immediately. When the variable changes, the **onInfoAgeChange** method defined in **infoWrapper** can still be called, therefore, the \@Monitor callback is still triggered.
875
876The result is unstable when you use the garbage collection mechanism to cancel the listening of \@Monitor. You can use either of the following methods to manage the expiration time of the \@Monitor:
877
8781. Define \@Monitor in the custom component. When a custom component is destroyed, the state management framework cancels the listening of \@Monitor. Therefore, after the custom component calls **aboutToDisappear**, the \@Monitor callback will not be triggered even though the data of the custom component may not be released.
879
880```ts
881@ObservedV2
882class InfoWrapper {
883  info?: Info;
884  constructor(info: Info) {
885    this.info = info;
886  }
887}
888@ObservedV2
889class Info {
890  @Trace age: number;
891  constructor(age: number) {
892    this.age = age;
893  }
894}
895@ComponentV2
896struct Child {
897  @Param @Require infoWrapper: InfoWrapper;
898  @Monitor("infoWrapper.info.age")
899  onInfoAgeChange(monitor: IMonitor) {
900    console.log(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`)
901  }
902  aboutToDisappear(): void {
903    console.log("Child aboutToDisappear", this.infoWrapper.info?.age)
904  }
905  build() {
906    Column() {
907      Text(`${this.infoWrapper.info?.age}`)
908    }
909  }
910}
911@Entry
912@ComponentV2
913struct Index {
914  dataArray: Info[] = [];
915  @Local showFlag: boolean = true;
916  aboutToAppear(): void {
917    for (let i = 0; i < 5; i++) {
918      this.dataArray.push(new Info(i));
919    }
920  }
921  build() {
922    Column() {
923      Button("change showFlag")
924        .onClick(() => {
925          this.showFlag = !this.showFlag;
926        })
927      Button("change number")
928        .onClick(() => {
929          console.log("click to change age")
930          this.dataArray.forEach((info: Info) => {
931            info.age += 100;
932          })
933        })
934      if (this.showFlag) {
935        Column() {
936          Text("Childs")
937          ForEach(this.dataArray, (info: Info) => {
938            Child({ infoWrapper: new InfoWrapper(info) })
939          })
940        }
941        .borderColor(Color.Red)
942        .borderWidth(2)
943      }
944    }
945  }
946}
947```
948
9492. Set the listened object to empty. When the custom component is about to be destroyed, the \@Monitor listened object is set empty. In this way, the \@Monitor cannot listen for the changes of the original object, so that the listening is cancelled.
950
951```ts
952@ObservedV2
953class InfoWrapper {
954  info?: Info;
955  constructor(info: Info) {
956    this.info = info;
957  }
958  @Monitor("info.age")
959  onInfoAgeChange(monitor: IMonitor) {
960    console.log(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`)
961  }
962}
963@ObservedV2
964class Info {
965  @Trace age: number;
966  constructor(age: number) {
967    this.age = age;
968  }
969}
970@ComponentV2
971struct Child {
972  @Param @Require infoWrapper: InfoWrapper;
973  aboutToDisappear(): void {
974    console.log("Child aboutToDisappear", this.infoWrapper.info?.age)
975    this.infoWrapper.info = undefined; // Disable the InfoWrapper from listening for info.age.
976  }
977  build() {
978    Column() {
979      Text(`${this.infoWrapper.info?.age}`)
980    }
981  }
982}
983@Entry
984@ComponentV2
985struct Index {
986  dataArray: Info[] = [];
987  @Local showFlag: boolean = true;
988  aboutToAppear(): void {
989    for (let i = 0; i < 5; i++) {
990      this.dataArray.push(new Info(i));
991    }
992  }
993  build() {
994    Column() {
995      Button("change showFlag")
996        .onClick(() => {
997          this.showFlag = !this.showFlag;
998        })
999      Button("change number")
1000        .onClick(() => {
1001          console.log("click to change age")
1002          this.dataArray.forEach((info: Info) => {
1003            info.age += 100;
1004          })
1005        })
1006      if (this.showFlag) {
1007        Column() {
1008          Text("Childs")
1009          ForEach(this.dataArray, (info: Info) => {
1010            Child({ infoWrapper: new InfoWrapper(info) })
1011          })
1012        }
1013        .borderColor(Color.Red)
1014        .borderWidth(2)
1015      }
1016    }
1017  }
1018}
1019```
1020
1021### Passing Correct Input Parameters to \@Monitor
1022
1023\@Monitor cannot verify input parameters during compilation. Currently, the following statements do not meet the listening condition, but \@Monitor is still triggered. Therefore, you should correctly pass the input parameter and do not pass non-state variables. Otherwise, function exceptions or unexpected behavior may occur.
1024
1025[Negative example 1]
1026
1027```ts
1028@ObservedV2
1029class Info {
1030  name: string = "John";
1031  @Trace age: number = 24;
1032  @Monitor("age", "name") // Listen for the state variable "age" and non-state variable "name" at the same time.
1033  onPropertyChange(monitor: IMonitor) {
1034    monitor.dirty.forEach((path: string) => {
1035      console.log(`property path:${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
1036    })
1037  }
1038}
1039@Entry
1040@ComponentV2
1041struct Index {
1042  info: Info = new Info();
1043  build() {
1044    Column() {
1045      Button("change age&name")
1046        .onClick(() => {
1047          this.info.age = 25; // Change the state variable "age" and non-state variable "name" at the same time.
1048          this.info.name = "Johny";
1049        })
1050    }
1051  }
1052}
1053```
1054
1055In the preceding code, when state variable **age** and non-state variable **name** are changed at the same time, the following log is generated:
1056
1057```
1058property path:age change from 24 to 25
1059property path:name change from John to Johny
1060```
1061
1062Actually, the **name** attribute is not an observable variable and should not be added to the input parameters of \@Monitor. You are advised to remove the listening from the **name** attribute or use \@Trace to decorate the **name** attribute as a state variable.
1063
1064[Correct example 1]
1065
1066```ts
1067@ObservedV2
1068class Info {
1069  name: string = "John";
1070  @Trace age: number = 24;
1071  @Monitor("age") // Only listen for the state variable "age".
1072  onPropertyChange(monitor: IMonitor) {
1073    monitor.dirty.forEach((path: string) => {
1074      console.log(`property path:${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
1075    })
1076  }
1077}
1078@Entry
1079@ComponentV2
1080struct Index {
1081  info: Info = new Info();
1082  build() {
1083    Column() {
1084      Button("change age&name")
1085        .onClick(() => {
1086          this.info.age = 25; // The state variable "age" is changed.
1087          this.info.name = "Johny";
1088        })
1089    }
1090  }
1091}
1092```
1093
1094[Negative example 2]
1095
1096```ts
1097@ObservedV2
1098class Info {
1099  name: string = "John";
1100  @Trace age: number = 24;
1101  get myAge() {
1102    return this.age; // age is a state variable.
1103  }
1104  @Monitor("myAge") // Listen for non-@Computed decorated getter accessor.
1105  onPropertyChange() {
1106    console.log("age changed");
1107  }
1108}
1109@Entry
1110@ComponentV2
1111struct Index {
1112  info: Info = new Info();
1113  build() {
1114    Column() {
1115      Button("change age")
1116        .onClick(() => {
1117          this.info.age = 25; // The state variable "age" is changed.
1118        })
1119    }
1120  }
1121}
1122```
1123
1124In the preceding code, the input parameter of \@Monitor is the name of a **getter** accessor. This accessor is not decorated by \@Computed and is not a variable that can be listened for. However, the state variable is used for computation. After the state variable changes, **myAge** is changed, invoking the \@Monitor callback. You are advised to add an \@Computed decorator to **myAge** or directly listen for the state variable itself when the **getter** accessor returns the state variable.
1125
1126[Positive example 2]
1127
1128Change **myAge** to a state variable:
1129
1130```ts
1131@ObservedV2
1132class Info {
1133  name: string = "John";
1134  @Trace age: number = 24;
1135  @Computed // Add @Computed to myAge as a state variable.
1136  get myAge() {
1137    return this.age;
1138  }
1139  @Monitor("myAge") // Listen for @Computed decorated getter accessor.
1140  onPropertyChange() {
1141    console.log("age changed");
1142  }
1143}
1144@Entry
1145@ComponentV2
1146struct Index {
1147  info: Info = new Info();
1148  build() {
1149    Column() {
1150      Button("change age")
1151        .onClick(() => {
1152          this.info.age = 25; // The state variable "age" is changed.
1153        })
1154    }
1155  }
1156}
1157```
1158
1159Alternatively, listen to the state variable itself.
1160
1161```ts
1162@ObservedV2
1163class Info {
1164  name: string = "John";
1165  @Trace age: number = 24;
1166  @Monitor("age") // Only listen for the state variable "age".
1167  onPropertyChange() {
1168    console.log("age changed");
1169  }
1170}
1171@Entry
1172@ComponentV2
1173struct Index {
1174  info: Info = new Info();
1175  build() {
1176    Column() {
1177      Button("change age")
1178        .onClick(() => {
1179          this.info.age = 25; // The state variable "age" is changed.
1180        })
1181    }
1182  }
1183}
1184```
1185