# \@ObservedV2 and \@Trace Decorators: Observing Class Property Changes To 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. >**NOTE** > >The \@ObservedV2 and \@Trace decorators are supported since API version 12. > ## Overview The \@ObservedV2 and \@Trace decorators are used to decorate classes and properties in classes so that changes to the classes and properties can be observed. - \@ObservedV2 and \@Trace must come in pairs. Using either of them alone does not work. - If a property decorated by \@Trace changes, only the component bound to the property is instructed to re-render. - 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. - 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. - Attributes that are not decorated by \@Trace cannot detect changes nor trigger UI re-renders. - Instances of \@ObservedV2 decorated classes cannot be serialized using **JSON.stringify**. ## Limitations of State Management V1 on Observability of Properties in Nested Class Objects With state management V1, properties of nested class objects are not directly observable. ```ts @Observed class Father { son: Son; constructor(name: string, age: number) { this.son = new Son(name, age); } } @Observed class Son { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } } @Entry @Component struct Index { @State father: Father = new Father("John", 8); build() { Row() { Column() { Text(`name: ${this.father.son.name} age: ${this.father.son.age}`) .fontSize(50) .fontWeight(FontWeight.Bold) .onClick(() => { this.father.son.age++; }) } .width('100%') } .height('100%') } } ``` In 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. ```ts @Observed class Father { son: Son; constructor(name: string, age: number) { this.son = new Son(name, age); } } @Observed class Son { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } } @Component struct Child { @ObjectLink son: Son; build() { Row() { Column() { Text(`name: ${this.son.name} age: ${this.son.age}`) .fontSize(50) .fontWeight(FontWeight.Bold) .onClick(() => { this.son.age++; }) } .width('100%') } .height('100%') } } @Entry @Component struct Index { @State father: Father = new Father("John", 8); build() { Column() { Child({son: this.father.son}) } } } ``` Yet, 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. ## Decorator Description | \@ObservedV2 Decorator| Description | | ------------------ | ----------------------------------------------------- | | Decorator parameters | None. | | Class decorator | Decorates a class. You must use **new** to create a class object before defining the class.|| | \@Trace member property decorator| Description | | --------------------- | ------------------------------------------------------------ | | Decorator parameters | None. | | Allowed variable types | Member properties in classes in any of the following types: number, string, boolean, class, Array, Date, Map, Set| ## Observed Changes In 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: - Changes to properties decorated by \@Trace in nested classes decorated by \@ObservedV2 ```ts @ObservedV2 class Son { @Trace age: number = 100; } class Father { son: Son = new Son(); } @Entry @ComponentV2 struct Index { father: Father = new Father(); build() { Column() { // If age is changed, the Text component is re-rendered. Text(`${this.father.son.age}`) .onClick(() => { this.father.son.age++; }) } } } ``` - Changes to properties decorated by \@Trace in inherited classes decorated by \@ObservedV2 ```ts @ObservedV2 class Father { @Trace name: string = "Tom"; } class Son extends Father { } @Entry @ComponentV2 struct Index { son: Son = new Son(); build() { Column() { // If name is changed, the Text component is re-rendered. Text(`${this.son.name}`) .onClick(() => { this.son.name = "Jack"; }) } } } ``` - Changes to static properties decorated by \@Trace in classes decorated by \@ObservedV2 ```ts @ObservedV2 class Manager { @Trace static count: number = 1; } @Entry @ComponentV2 struct Index { build() { Column() { // If count is changed, the Text component is re-rendered. Text(`${Manager.count}`) .onClick(() => { Manager.count++; }) } } } ``` - Changes caused by the APIs listed below to properties of built-in types decorated by \@Trace | Type | APIs that can observe changes | | ----- | ------------------------------------------------------------ | | Array | push, pop, shift, unshift, splice, copyWithin, fill, reverse, sort| | Date | setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds | | Map | set, clear, delete | | Set | add, clear, delete | ## Constraints Note the following constraints when using the \@ObservedV2 and \@Trace decorators: - The member property that is not decorated by \@Trace cannot trigger UI re-renders. ```ts @ObservedV2 class Person { id: number = 0; @Trace age: number = 8; } @Entry @ComponentV2 struct Index { person: Person = new Person(); build() { Column() { // age is decorated by @Trace and can trigger re-renders when used in the UI. Text(`${this.person.age}`) .onClick(() => { this.person.age++; // The click event can trigger a UI re-render. }) // id is not decorated by @Trace and cannot trigger re-renders when used in the UI. Text(`${this.person.id}`) // UI is not re-rendered when id changes. .onClick(() => { this.person.id++; // The click event cannot trigger a UI re-render. }) } } } ``` - \@ObservedV2 can decorate only classes. ```ts @ObservedV2 // Incorrect usage. An error is reported during compilation. struct Index { build() { } } ``` - \@Trace cannot be used in classes that are not decorated by \@ObservedV2. ```ts class User { id: number = 0; @Trace name: string = "Tom"; // Incorrect usage. An error is reported at compile time. } ``` - \@Trace is a decorator for properties in classes and cannot be used in a struct. ```ts @ComponentV2 struct Comp { @Trace message: string = "Hello World"; // Incorrect usage. An error is reported at compile time. build() { } } ``` - \@ObservedV2 and \@Trace cannot be used together with [\@Observed](arkts-observed-and-objectlink.md) and [\@Track](arkts-track.md). ```ts @Observed class User { @Trace name: string = "Tom"; // Incorrect usage. An error is reported at compile time. } @ObservedV2 class Person { @Track name: string = "Jack"; // Incorrect usage. An error is reported at compile time. } ``` - 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. ```ts // @State is used as an example. @ObservedV2 class Job { @Trace jobName: string = "Teacher"; } @ObservedV2 class Info { @Trace name: string = "Tom"; @Trace age: number = 25; job: Job = new Job(); } @Entry @Component struct Index { @State info: Info = new Info(); // As @State is not allowed here, an error is reported at compile time. build() { Column() { Text(`name: ${this.info.name}`) Text(`age: ${this.info.age}`) Text(`jobName: ${this.info.job.jobName}`) Button("change age") .onClick(() => { this.info.age++; }) Button("Change job") .onClick(() => { this.info.job.jobName = "Doctor"; }) } } } ``` - 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. ```ts // @State is used as an example. @ObservedV2 class Job { @Trace jobName: string = "Teacher"; } @ObservedV2 class Info { @Trace name: string = "Tom"; @Trace age: number = 25; job: Job = new Job(); } class Message extends Info { constructor() { super(); } } @Entry @Component struct Index { @State message: Message = new Message(); // As @State is not allowed here, an error is reported during running. build() { Column() { Text(`name: ${this.message.name}`) Text(`age: ${this.message.age}`) Text(`jobName: ${this.message.job.jobName}`) Button("change age") .onClick(() => { this.message.age++; }) Button("Change job") .onClick(() => { this.message.job.jobName = "Doctor"; }) } } } ``` - Instances of \@ObservedV2 decorated classes cannot be serialized using **JSON.stringify**. ## Use Scenarios ### Nested Class In 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. The 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. * 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. * Because **son** on the custom component **page** is a regular variable, no change is observed for clicks on **Button("assign Son")**. * 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. ```ts @ObservedV2 class Pencil { @Trace length: number = 21; // If length changes, the bound component is re-rendered. } class Bag { width: number = 50; height: number = 60; pencil: Pencil = new Pencil(); } class Son { age: number = 5; school: string = "some"; bag: Bag = new Bag(); } @Entry @ComponentV2 struct Page { son: Son = new Son(); renderTimes: number = 0; isRender(id: number): number { console.info(`id: ${id} renderTimes: ${this.renderTimes}`); this.renderTimes++; return 40; } build() { Column() { Text('pencil length'+ this.son.bag.pencil.length) .fontSize(this.isRender(1)) // UINode (1) Button("change length") .onClick(() => { // The value of length is changed upon a click, which triggers a re-render of UINode (1). this.son.bag.pencil.length += 100; }) Button("assign Son") .onClick(() => { // Changes to the regular variable son do not trigger UI re-renders of UINode (1). this.son = new Son(); }) } } } ``` ### Inheritance Properties in base or derived classes are observable only when decorated by \@Trace. In the following example, classes **GrandFather**, **Father**, **Uncle**, **Son**, and **Cousin** are declared. The following figure shows the inheritance relationship.  Create instances of the **Son** and **Cousin** classes. Clicks on **Button('change Son age')** and **Button('change Cousin age')** can trigger UI re-renders. ```ts @ObservedV2 class GrandFather { @Trace age: number = 0; constructor(age: number) { this.age = age; } } class Father extends GrandFather{ constructor(father: number) { super(father); } } class Uncle extends GrandFather { constructor(uncle: number) { super(uncle); } } class Son extends Father { constructor(son: number) { super(son); } } class Cousin extends Uncle { constructor(cousin: number) { super(cousin); } } @Entry @ComponentV2 struct Index { son: Son = new Son(0); cousin: Cousin = new Cousin(0); renderTimes: number = 0; isRender(id: number): number { console.info(`id: ${id} renderTimes: ${this.renderTimes}`); this.renderTimes++; return 40; } build() { Row() { Column() { Text(`Son ${this.son.age}`) .fontSize(this.isRender(1)) .fontWeight(FontWeight.Bold) Text(`Cousin ${this.cousin.age}`) .fontSize(this.isRender(2)) .fontWeight(FontWeight.Bold) Button('change Son age') .onClick(() => { this.son.age++; }) Button('change Cousin age') .onClick(() => { this.cousin.age++; }) } .width('100%') } .height('100%') } } ``` ### Decorating an Array of a Built-in Type with \@Trace With 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). In 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. ```ts let nextId: number = 0; @ObservedV2 class Arr { id: number = 0; @Trace numberArr: number[] = []; constructor() { this.id = nextId++; this.numberArr = [0, 1, 2]; } } @Entry @ComponentV2 struct Index { arr: Arr = new Arr(); build() { Column() { Text(`length: ${this.arr.numberArr.length}`) .fontSize(40) Divider() if (this.arr.numberArr.length >= 3) { Text(`${this.arr.numberArr[0]}`) .fontSize(40) .onClick(() => { this.arr.numberArr[0]++; }) Text(`${this.arr.numberArr[1]}`) .fontSize(40) .onClick(() => { this.arr.numberArr[1]++; }) Text(`${this.arr.numberArr[2]}`) .fontSize(40) .onClick(() => { this.arr.numberArr[2]++; }) } Divider() ForEach(this.arr.numberArr, (item: number, index: number) => { Text(`${index} ${item}`) .fontSize(40) }) Button('push') .onClick(() => { this.arr.numberArr.push(50); }) Button('pop') .onClick(() => { this.arr.numberArr.pop(); }) Button('shift') .onClick(() => { this.arr.numberArr.shift(); }) Button('splice') .onClick(() => { this.arr.numberArr.splice(1, 0, 60); }) Button('unshift') .onClick(() => { this.arr.numberArr.unshift(100); }) Button('copywithin') .onClick(() => { this.arr.numberArr.copyWithin(0, 1, 2); }) Button('fill') .onClick(() => { this.arr.numberArr.fill(0, 2, 4); }) Button('reverse') .onClick(() => { this.arr.numberArr.reverse(); }) Button('sort') .onClick(() => { this.arr.numberArr.sort(); }) } } } ``` ### Decorating an Object Array with \@Trace * 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. * Clicking the **Text** component changes the value of **age** and thereby triggers a UI re-render of the **Text** component ```ts let nextId: number = 0; @ObservedV2 class Person { @Trace age: number = 0; constructor(age: number) { this.age = age; } } @ObservedV2 class Info { id: number = 0; @Trace personList: Person[] = []; constructor() { this.id = nextId++; this.personList = [new Person(0), new Person(1), new Person(2)]; } } @Entry @ComponentV2 struct Index { info: Info = new Info(); build() { Column() { Text(`length: ${this.info.personList.length}`) .fontSize(40) Divider() if (this.info.personList.length >= 3) { Text(`${this.info.personList[0].age}`) .fontSize(40) .onClick(() => { this.info.personList[0].age++; }) Text(`${this.info.personList[1].age}`) .fontSize(40) .onClick(() => { this.info.personList[1].age++; }) Text(`${this.info.personList[2].age}`) .fontSize(40) .onClick(() => { this.info.personList[2].age++; }) } Divider() ForEach(this.info.personList, (item: Person, index: number) => { Text(`${index} ${item.age}`) .fontSize(40) }) } } } ``` ### Decorating a Property of the Map Type with \@Trace * With a property of the Map type decorated by \@Trace, changes caused by supported APIs, such as **set**, **clear**, and **delete**, can be observed. * 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. ```ts @ObservedV2 class Info { @Trace memberMap: Map<number, string> = new Map([[0, "a"], [1, "b"], [3, "c"]]); } @Entry @ComponentV2 struct MapSample { info: Info = new Info(); build() { Row() { Column() { ForEach(Array.from(this.info.memberMap.entries()), (item: [number, string]) => { Text(`${item[0]}`) .fontSize(30) Text(`${item[1]}`) .fontSize(30) Divider() }) Button('init map') .onClick(() => { this.info.memberMap = new Map([[0, "a"], [1, "b"], [3, "c"]]); }) Button('set new one') .onClick(() => { this.info.memberMap.set(4, "d"); }) Button('clear') .onClick(() => { this.info.memberMap.clear(); }) Button('set the key: 0') .onClick(() => { this.info.memberMap.set(0, "aa"); }) Button('delete the first one') .onClick(() => { this.info.memberMap.delete(0); }) } .width('100%') } .height('100%') } } ``` ### Decorating a Property of the Set Type with \@Trace * With a property of the Set type decorated by \@Trace, changes caused by supported APIs, such as **add**, **clear**, and **delete**, can be observed. * 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. ```ts @ObservedV2 class Info { @Trace memberSet: Set<number> = new Set([0, 1, 2, 3, 4]); } @Entry @ComponentV2 struct SetSample { info: Info = new Info(); build() { Row() { Column() { ForEach(Array.from(this.info.memberSet.entries()), (item: [number, string]) => { Text(`${item[0]}`) .fontSize(30) Divider() }) Button('init set') .onClick(() => { this.info.memberSet = new Set([0, 1, 2, 3, 4]); }) Button('set new one') .onClick(() => { this.info.memberSet.add(5); }) Button('clear') .onClick(() => { this.info.memberSet.clear(); }) Button('delete the first one') .onClick(() => { this.info.memberSet.delete(0); }) } .width('100%') } .height('100%') } } ``` ### Decorating a Property of the Date Type with \@Trace * 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. * 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. ```ts @ObservedV2 class Info { @Trace selectedDate: Date = new Date('2021-08-08') } @Entry @ComponentV2 struct DateSample { info: Info = new Info() build() { Column() { Button('set selectedDate to 2023-07-08') .margin(10) .onClick(() => { this.info.selectedDate = new Date('2023-07-08'); }) Button('increase the year by 1') .margin(10) .onClick(() => { this.info.selectedDate.setFullYear(this.info.selectedDate.getFullYear() + 1); }) Button('increase the month by 1') .margin(10) .onClick(() => { this.info.selectedDate.setMonth(this.info.selectedDate.getMonth() + 1); }) Button('increase the day by 1') .margin(10) .onClick(() => { this.info.selectedDate.setDate(this.info.selectedDate.getDate() + 1); }) DatePicker({ start: new Date('1970-1-1'), end: new Date('2100-1-1'), selected: this.info.selectedDate }) }.width('100%') } } ```