1# \@ObservedV2 and \@Trace Decorators: Observing Class Property Changes
2
3To allow the state management framework to observe properties in class objects, you can use the \@ObservedV2 decorator and \@Trace decorator to decorate classes and properties in classes.
4
5>**NOTE**
6>
7>The \@ObservedV2 and \@Trace decorators are supported since API version 12.
8>
9
10## Overview
11
12The \@ObservedV2 and \@Trace decorators are used to decorate classes and properties in classes so that changes to the classes and properties can be observed.
13
14- \@ObservedV2 and \@Trace must come in pairs. Using either of them alone does not work.
15- If a property decorated by \@Trace changes, only the component bound to the property is instructed to re-render.
16- In a nested class, changes to its property trigger UI re-renders only when the property is decorated by \@Trace and the class is decorated by \@ObservedV2.
17- In an inherited class, changes to a property of the parent or child class trigger UI re-renders only when the property is decorated by \@Trace and the owning class is decorated by \@ObservedV2.
18- Attributes that are not decorated by \@Trace cannot detect changes nor trigger UI re-renders.
19- Instances of \@ObservedV2 decorated classes cannot be serialized using **JSON.stringify**.
20
21## Limitations of State Management V1 on Observability of Properties in Nested Class Objects
22
23With state management V1, properties of nested class objects are not directly observable.
24
25```ts
26@Observed
27class Father {
28  son: Son;
29
30  constructor(name: string, age: number) {
31    this.son = new Son(name, age);
32  }
33}
34@Observed
35class Son {
36  name: string;
37  age: number;
38
39  constructor(name: string, age: number) {
40    this.name = name;
41    this.age = age;
42  }
43}
44@Entry
45@Component
46struct Index {
47  @State father: Father = new Father("John", 8);
48
49  build() {
50    Row() {
51      Column() {
52        Text(`name: ${this.father.son.name} age: ${this.father.son.age}`)
53          .fontSize(50)
54          .fontWeight(FontWeight.Bold)
55          .onClick(() => {
56            this.father.son.age++;
57          })
58      }
59      .width('100%')
60    }
61    .height('100%')
62  }
63}
64```
65
66In the preceding example, clicking the **Text** component increases the value of **age**, but does not trigger UI re-renders. The reason is that, the **age** property is in a nested class and not observable to the current state management framework. To resolve this issue, state management V1 uses [\@ObjectLink](arkts-observed-and-objectlink.md) with custom components.
67
68```ts
69@Observed
70class Father {
71  son: Son;
72
73  constructor(name: string, age: number) {
74    this.son = new Son(name, age);
75  }
76}
77@Observed
78class Son {
79  name: string;
80  age: number;
81
82  constructor(name: string, age: number) {
83    this.name = name;
84    this.age = age;
85  }
86}
87@Component
88struct Child {
89  @ObjectLink son: Son;
90
91  build() {
92    Row() {
93      Column() {
94        Text(`name: ${this.son.name} age: ${this.son.age}`)
95          .fontSize(50)
96          .fontWeight(FontWeight.Bold)
97          .onClick(() => {
98            this.son.age++;
99          })
100      }
101      .width('100%')
102    }
103    .height('100%')
104  }
105}
106@Entry
107@Component
108struct Index {
109  @State father: Father = new Father("John", 8);
110
111  build() {
112    Column() {
113      Child({son: this.father.son})
114    }
115  }
116}
117```
118
119Yet, this approach has its drawbacks: If the nesting level is deep, the code becomes unnecessarily complicated and the usability poor. This is where the class decorator \@ObservedV2 and member property decorator \@Trace come into the picture.
120
121## Decorator Description
122
123| \@ObservedV2 Decorator| Description                                                 |
124| ------------------ | ----------------------------------------------------- |
125| Decorator parameters        | None.                                                   |
126| Class decorator          | Decorates a class. You must use **new** to create a class object before defining the class.||
127
128| \@Trace member property decorator| Description                                                        |
129| --------------------- | ------------------------------------------------------------ |
130| Decorator parameters           | None.                                                          |
131| Allowed variable types         | Member properties in classes in any of the following types: number, string, boolean, class, Array, Date, Map, Set|
132
133## Observed Changes
134
135In classes decorated by \@ObservedV2, properties decorated by \@Trace are observable. This means that, any of the following changes can be observed and will trigger UI re-renders of bound components:
136
137- Changes to properties decorated by \@Trace in nested classes decorated by \@ObservedV2
138
139```ts
140@ObservedV2
141class Son {
142  @Trace age: number = 100;
143}
144class Father {
145  son: Son = new Son();
146}
147@Entry
148@ComponentV2
149struct Index {
150  father: Father = new Father();
151
152  build() {
153    Column() {
154      // If age is changed, the Text component is re-rendered.
155      Text(`${this.father.son.age}`)
156        .onClick(() => {
157          this.father.son.age++;
158        })
159    }
160  }
161}
162
163```
164
165- Changes to properties decorated by \@Trace in inherited classes decorated by \@ObservedV2
166
167```ts
168@ObservedV2
169class Father {
170  @Trace name: string = "Tom";
171}
172class Son extends Father {
173}
174@Entry
175@ComponentV2
176struct Index {
177  son: Son = new Son();
178
179  build() {
180    Column() {
181      // If name is changed, the Text component is re-rendered.
182      Text(`${this.son.name}`)
183        .onClick(() => {
184          this.son.name = "Jack";
185        })
186    }
187  }
188}
189```
190
191- Changes to static properties decorated by \@Trace in classes decorated by \@ObservedV2
192
193```ts
194@ObservedV2
195class Manager {
196  @Trace static count: number = 1;
197}
198@Entry
199@ComponentV2
200struct Index {
201  build() {
202    Column() {
203      // If count is changed, the Text component is re-rendered.
204      Text(`${Manager.count}`)
205        .onClick(() => {
206          Manager.count++;
207        })
208    }
209  }
210}
211```
212
213- Changes caused by the APIs listed below to properties of built-in types decorated by \@Trace
214
215  | Type | APIs that can observe changes                                             |
216  | ----- | ------------------------------------------------------------ |
217  | Array | push, pop, shift, unshift, splice, copyWithin, fill, reverse, sort|
218  | Date  | setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds |
219  | Map   | set, clear, delete                                           |
220  | Set   | add, clear, delete                                           |
221
222## Constraints
223
224Note the following constraints when using the \@ObservedV2 and \@Trace decorators:
225
226- The member property that is not decorated by \@Trace cannot trigger UI re-renders.
227
228```ts
229@ObservedV2
230class Person {
231  id: number = 0;
232  @Trace age: number = 8;
233}
234@Entry
235@ComponentV2
236struct Index {
237  person: Person = new Person();
238
239  build() {
240    Column() {
241      // age is decorated by @Trace and can trigger re-renders when used in the UI.
242      Text(`${this.person.age}`)
243        .onClick(() => {
244          this.person.age++; // The click event can trigger a UI re-render.
245        })
246      // id is not decorated by @Trace and cannot trigger re-renders when used in the UI.
247      Text(`${this.person.id}`) // UI is not re-rendered when id changes.
248        .onClick(() => {
249          this.person.id++; // The click event cannot trigger a UI re-render.
250        })
251    }
252  }
253}
254```
255
256- \@ObservedV2 can decorate only classes.
257
258```ts
259@ObservedV2 // Incorrect usage. An error is reported during compilation.
260struct Index {
261  build() {
262  }
263}
264```
265
266- \@Trace cannot be used in classes that are not decorated by \@ObservedV2.
267
268```ts
269class User {
270  id: number = 0;
271  @Trace name: string = "Tom"; // Incorrect usage. An error is reported at compile time.
272}
273```
274
275- \@Trace is a decorator for properties in classes and cannot be used in a struct.
276
277```ts
278@ComponentV2
279struct Comp {
280  @Trace message: string = "Hello World"; // Incorrect usage. An error is reported at compile time.
281
282  build() {
283  }
284}
285```
286
287- \@ObservedV2 and \@Trace cannot be used together with [\@Observed](arkts-observed-and-objectlink.md) and [\@Track](arkts-track.md).
288
289```ts
290@Observed
291class User {
292  @Trace name: string = "Tom"; // Incorrect usage. An error is reported at compile time.
293}
294
295@ObservedV2
296class Person {
297  @Track name: string = "Jack"; // Incorrect usage. An error is reported at compile time.
298}
299```
300
301- Classes decorated by @ObservedV2 and @Trace cannot be used together with [\@State](arkts-state.md) or other decorators of V1. Otherwise, an error is reported at compile time.
302
303```ts
304// @State is used as an example.
305@ObservedV2
306class Job {
307  @Trace jobName: string = "Teacher";
308}
309@ObservedV2
310class Info {
311  @Trace name: string = "Tom";
312  @Trace age: number = 25;
313  job: Job = new Job();
314}
315@Entry
316@Component
317struct Index {
318  @State info: Info = new Info(); // As @State is not allowed here, an error is reported at compile time.
319
320  build() {
321    Column() {
322      Text(`name: ${this.info.name}`)
323      Text(`age: ${this.info.age}`)
324      Text(`jobName: ${this.info.job.jobName}`)
325      Button("change age")
326        .onClick(() => {
327          this.info.age++;
328        })
329      Button("Change job")
330        .onClick(() => {
331          this.info.job.jobName = "Doctor";
332        })
333    }
334  }
335}
336```
337
338- Classes extended from \@ObservedV2 cannot be used together with [\@State](arkts-state.md) or other decorators of V1. Otherwise, an error is reported during running.
339
340```ts
341// @State is used as an example.
342@ObservedV2
343class Job {
344  @Trace jobName: string = "Teacher";
345}
346@ObservedV2
347class Info {
348  @Trace name: string = "Tom";
349  @Trace age: number = 25;
350  job: Job = new Job();
351}
352class Message extends Info {
353    constructor() {
354        super();
355    }
356}
357@Entry
358@Component
359struct Index {
360  @State message: Message = new Message(); // As @State is not allowed here, an error is reported during running.
361
362  build() {
363    Column() {
364      Text(`name: ${this.message.name}`)
365      Text(`age: ${this.message.age}`)
366      Text(`jobName: ${this.message.job.jobName}`)
367      Button("change age")
368        .onClick(() => {
369          this.message.age++;
370        })
371      Button("Change job")
372        .onClick(() => {
373          this.message.job.jobName = "Doctor";
374        })
375    }
376  }
377}
378```
379
380- Instances of \@ObservedV2 decorated classes cannot be serialized using **JSON.stringify**.
381
382## Use Scenarios
383
384### Nested Class
385
386In the following example, **Pencil** is the innermost class in the **Son** class. As **Pencil** is decorated by \@ObservedV2 and its **length** property is decorated by \@Trace, changes to **length** can be observed.
387
388The example demonstrates how \@Trace is stacked up against [\@Track](arkts-track.md) and [\@State](arkts-state.md) under the existing state management framework: The @Track decorator offers property-level update capability for classes, but not deep observability; \@State can only observe the changes of the object itself and changes at the first layer; in multi-layer nesting scenarios, you must encapsulate custom components and use [\@Observed](arkts-observed-and-objectlink.md) and [\@ObjectLink](arkts-observed-and-objectlink.md) to observe the changes.
389
390* Click **Button("change length")**, in which **length** is a property decorated by \@Trace. The change of **length** can trigger the re-render of the associated UI component, that is, **UINode (1)**, and output the log "id: 1 renderTimes: x" whose **x** increases according to the number of clicks.
391* Because **son** on the custom component **page** is a regular variable, no change is observed for clicks on **Button("assign Son")**.
392* Clicks on **Button("assign Son")** and **Button("change length")** do not trigger UI re-renders. The reason is that, the change to **son** is not updated to the bound component.
393
394```ts
395@ObservedV2
396class Pencil {
397  @Trace length: number = 21; // If length changes, the bound component is re-rendered.
398}
399class Bag {
400  width: number = 50;
401  height: number = 60;
402  pencil: Pencil = new Pencil();
403}
404class Son {
405  age: number = 5;
406  school: string = "some";
407  bag: Bag = new Bag();
408}
409
410@Entry
411@ComponentV2
412struct Page {
413  son: Son = new Son();
414  renderTimes: number = 0;
415  isRender(id: number): number {
416    console.info(`id: ${id} renderTimes: ${this.renderTimes}`);
417    this.renderTimes++;
418    return 40;
419  }
420
421  build() {
422    Column() {
423      Text('pencil length'+ this.son.bag.pencil.length)
424        .fontSize(this.isRender(1))   // UINode (1)
425      Button("change length")
426        .onClick(() => {
427          // The value of length is changed upon a click, which triggers a re-render of UINode (1).
428          this.son.bag.pencil.length += 100;
429        })
430      Button("assign Son")
431        .onClick(() => {
432          // Changes to the regular variable son do not trigger UI re-renders of UINode (1).
433          this.son = new Son();
434        })
435    }
436  }
437}
438```
439
440
441### Inheritance
442
443Properties in base or derived classes are observable only when decorated by \@Trace.
444In the following example, classes **GrandFather**, **Father**, **Uncle**, **Son**, and **Cousin** are declared. The following figure shows the inheritance relationship.
445
446![arkts-old-state-management](figures/arkts-new-observed-and-track-extend-sample.png)
447
448
449Create instances of the **Son** and **Cousin** classes. Clicks on **Button('change Son age')** and **Button('change Cousin age')** can trigger UI re-renders.
450
451```ts
452@ObservedV2
453class GrandFather {
454  @Trace age: number = 0;
455
456  constructor(age: number) {
457    this.age = age;
458  }
459}
460class Father extends GrandFather{
461  constructor(father: number) {
462    super(father);
463  }
464}
465class Uncle extends GrandFather {
466  constructor(uncle: number) {
467    super(uncle);
468  }
469}
470class Son extends Father {
471  constructor(son: number) {
472    super(son);
473  }
474}
475class Cousin extends Uncle {
476  constructor(cousin: number) {
477    super(cousin);
478  }
479}
480@Entry
481@ComponentV2
482struct Index {
483  son: Son = new Son(0);
484  cousin: Cousin = new Cousin(0);
485  renderTimes: number = 0;
486
487  isRender(id: number): number {
488    console.info(`id: ${id} renderTimes: ${this.renderTimes}`);
489    this.renderTimes++;
490    return 40;
491  }
492
493  build() {
494    Row() {
495      Column() {
496        Text(`Son ${this.son.age}`)
497          .fontSize(this.isRender(1))
498          .fontWeight(FontWeight.Bold)
499        Text(`Cousin ${this.cousin.age}`)
500          .fontSize(this.isRender(2))
501          .fontWeight(FontWeight.Bold)
502        Button('change Son age')
503          .onClick(() => {
504            this.son.age++;
505          })
506        Button('change Cousin age')
507          .onClick(() => {
508            this.cousin.age++;
509          })
510      }
511      .width('100%')
512    }
513    .height('100%')
514  }
515}
516```
517
518### Decorating an Array of a Built-in Type with \@Trace
519
520With an array of a built-in type decorated by \@Trace, changes caused by supported APIs can be observed. For details about the supported APIs, see [Observed Changes](#observed-changes).
521In the following example, the **numberArr** property in the \@ObservedV2 decorated **Arr** class is an \@Trace decorated array. If an array API is used to operate **numberArr**, the change caused can be observed. Perform length checks on arrays to prevent out-of-bounds access.
522
523```ts
524let nextId: number = 0;
525
526@ObservedV2
527class Arr {
528  id: number = 0;
529  @Trace numberArr: number[] = [];
530
531  constructor() {
532    this.id = nextId++;
533    this.numberArr = [0, 1, 2];
534  }
535}
536
537@Entry
538@ComponentV2
539struct Index {
540  arr: Arr = new Arr();
541
542  build() {
543    Column() {
544      Text(`length: ${this.arr.numberArr.length}`)
545        .fontSize(40)
546      Divider()
547      if (this.arr.numberArr.length >= 3) {
548        Text(`${this.arr.numberArr[0]}`)
549          .fontSize(40)
550          .onClick(() => {
551            this.arr.numberArr[0]++;
552          })
553        Text(`${this.arr.numberArr[1]}`)
554          .fontSize(40)
555          .onClick(() => {
556            this.arr.numberArr[1]++;
557          })
558        Text(`${this.arr.numberArr[2]}`)
559          .fontSize(40)
560          .onClick(() => {
561            this.arr.numberArr[2]++;
562          })
563      }
564
565      Divider()
566
567      ForEach(this.arr.numberArr, (item: number, index: number) => {
568        Text(`${index} ${item}`)
569          .fontSize(40)
570      })
571
572      Button('push')
573        .onClick(() => {
574          this.arr.numberArr.push(50);
575        })
576
577      Button('pop')
578        .onClick(() => {
579          this.arr.numberArr.pop();
580        })
581
582      Button('shift')
583        .onClick(() => {
584          this.arr.numberArr.shift();
585        })
586
587      Button('splice')
588        .onClick(() => {
589          this.arr.numberArr.splice(1, 0, 60);
590        })
591
592
593      Button('unshift')
594        .onClick(() => {
595          this.arr.numberArr.unshift(100);
596        })
597
598      Button('copywithin')
599        .onClick(() => {
600          this.arr.numberArr.copyWithin(0, 1, 2);
601        })
602
603      Button('fill')
604        .onClick(() => {
605          this.arr.numberArr.fill(0, 2, 4);
606        })
607
608      Button('reverse')
609        .onClick(() => {
610          this.arr.numberArr.reverse();
611        })
612
613      Button('sort')
614        .onClick(() => {
615          this.arr.numberArr.sort();
616        })
617    }
618  }
619}
620```
621
622### Decorating an Object Array with \@Trace
623
624* In the following example, the **personList** object array and the **age** property in the **Person** class are decorated by \@Trace. As such, changes to **personList** and **age** can be observed.
625* Clicking the **Text** component changes the value of **age** and thereby triggers a UI re-render of the **Text** component
626
627```ts
628let nextId: number = 0;
629
630@ObservedV2
631class Person {
632  @Trace age: number = 0;
633
634  constructor(age: number) {
635    this.age = age;
636  }
637}
638
639@ObservedV2
640class Info {
641  id: number = 0;
642  @Trace personList: Person[] = [];
643
644  constructor() {
645    this.id = nextId++;
646    this.personList = [new Person(0), new Person(1), new Person(2)];
647  }
648}
649
650@Entry
651@ComponentV2
652struct Index {
653  info: Info = new Info();
654
655  build() {
656    Column() {
657      Text(`length: ${this.info.personList.length}`)
658        .fontSize(40)
659      Divider()
660      if (this.info.personList.length >= 3) {
661        Text(`${this.info.personList[0].age}`)
662          .fontSize(40)
663          .onClick(() => {
664            this.info.personList[0].age++;
665          })
666
667        Text(`${this.info.personList[1].age}`)
668          .fontSize(40)
669          .onClick(() => {
670            this.info.personList[1].age++;
671          })
672
673        Text(`${this.info.personList[2].age}`)
674          .fontSize(40)
675          .onClick(() => {
676            this.info.personList[2].age++;
677          })
678      }
679
680      Divider()
681
682      ForEach(this.info.personList, (item: Person, index: number) => {
683        Text(`${index} ${item.age}`)
684          .fontSize(40)
685      })
686    }
687  }
688}
689
690```
691
692### Decorating a Property of the Map Type with \@Trace
693
694* With a property of the Map type decorated by \@Trace, changes caused by supported APIs, such as **set**, **clear**, and **delete**, can be observed.
695* In the following example, the **Info** class is decorated by \@ObservedV2 and its **memberMap** property is decorated by \@Trace; as such, changes to the **memberMap** property caused by clicking **Button('init map')** can be observed.
696
697```ts
698@ObservedV2
699class Info {
700  @Trace memberMap: Map<number, string> = new Map([[0, "a"], [1, "b"], [3, "c"]]);
701}
702
703@Entry
704@ComponentV2
705struct MapSample {
706  info: Info = new Info();
707
708  build() {
709    Row() {
710      Column() {
711        ForEach(Array.from(this.info.memberMap.entries()), (item: [number, string]) => {
712          Text(`${item[0]}`)
713            .fontSize(30)
714          Text(`${item[1]}`)
715            .fontSize(30)
716          Divider()
717        })
718        Button('init map')
719          .onClick(() => {
720            this.info.memberMap = new Map([[0, "a"], [1, "b"], [3, "c"]]);
721          })
722        Button('set new one')
723          .onClick(() => {
724            this.info.memberMap.set(4, "d");
725          })
726        Button('clear')
727          .onClick(() => {
728            this.info.memberMap.clear();
729          })
730        Button('set the key: 0')
731          .onClick(() => {
732            this.info.memberMap.set(0, "aa");
733          })
734        Button('delete the first one')
735          .onClick(() => {
736            this.info.memberMap.delete(0);
737          })
738      }
739      .width('100%')
740    }
741    .height('100%')
742  }
743}
744```
745
746### Decorating a Property of the Set Type with \@Trace
747
748* With a property of the Set type decorated by \@Trace, changes caused by supported APIs, such as **add**, **clear**, and **delete**, can be observed.
749* In the following example, the **Info** class is decorated by \@ObservedV2 and its **memberSet** property is decorated by \@Trace; as such, changes to the **memberSet** property caused by clicking **Button('init set')** can be observed.
750
751```ts
752@ObservedV2
753class Info {
754  @Trace memberSet: Set<number> = new Set([0, 1, 2, 3, 4]);
755}
756
757@Entry
758@ComponentV2
759struct SetSample {
760  info: Info = new Info();
761
762  build() {
763    Row() {
764      Column() {
765        ForEach(Array.from(this.info.memberSet.entries()), (item: [number, string]) => {
766          Text(`${item[0]}`)
767            .fontSize(30)
768          Divider()
769        })
770        Button('init set')
771          .onClick(() => {
772            this.info.memberSet = new Set([0, 1, 2, 3, 4]);
773          })
774        Button('set new one')
775          .onClick(() => {
776            this.info.memberSet.add(5);
777          })
778        Button('clear')
779          .onClick(() => {
780            this.info.memberSet.clear();
781          })
782        Button('delete the first one')
783          .onClick(() => {
784            this.info.memberSet.delete(0);
785          })
786      }
787      .width('100%')
788    }
789    .height('100%')
790  }
791}
792```
793
794
795### Decorating a Property of the Date Type with \@Trace
796
797* With a property of the Date type decorated by \@Trace, changes caused by the following APIs can be observed: setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds.
798* In the following example, the **Info** class is decorated by \@ObservedV2 and its **selectedDate** property is decorated by \@Trace; as such, changes to the **selectedDate** property caused by clicking **Button('set selectedDate to 2023-07-08')** can be observed.
799
800```ts
801@ObservedV2
802class Info {
803  @Trace selectedDate: Date = new Date('2021-08-08')
804}
805
806@Entry
807@ComponentV2
808struct DateSample {
809  info: Info = new Info()
810
811  build() {
812    Column() {
813      Button('set selectedDate to 2023-07-08')
814        .margin(10)
815        .onClick(() => {
816          this.info.selectedDate = new Date('2023-07-08');
817        })
818      Button('increase the year by 1')
819        .margin(10)
820        .onClick(() => {
821          this.info.selectedDate.setFullYear(this.info.selectedDate.getFullYear() + 1);
822        })
823      Button('increase the month by 1')
824        .margin(10)
825        .onClick(() => {
826          this.info.selectedDate.setMonth(this.info.selectedDate.getMonth() + 1);
827        })
828      Button('increase the day by 1')
829        .margin(10)
830        .onClick(() => {
831          this.info.selectedDate.setDate(this.info.selectedDate.getDate() + 1);
832        })
833      DatePicker({
834        start: new Date('1970-1-1'),
835        end: new Date('2100-1-1'),
836        selected: this.info.selectedDate
837      })
838    }.width('100%')
839  }
840}
841```
842