1# \@Observed and \@ObjectLink Decorators: Observing Property Changes in Nested Class Objects 2 3 4The decorators including [\@State](./arkts-state.md), [\@Prop](./arkts-prop.md), [\@Link](./arkts-link.md), [\@Provide and \@Consume](./arkts-provide-and-consume.md) can only observe the top-layer changes. However, in actual application development, the application encapsulates its own data model based on the requirements. In this case, for multi-layer nesting, for example, a two-dimensional array, an array item class, or a class inside another class as a property, the property changes at the second layer cannot be observed. This is where the \@Observed and \@ObjectLink decorators come in handy. 5 6\@Observed and \@ObjectLink are used together to observe nested scenarios, aiming at offsetting the limitation that the decorator can observe only one layer. You are advised to have a basic understanding of the observation capability of the decorators before reading this topic. For details, see [\@State](./arkts-state.md). 7 8> **NOTE** 9> 10> These two decorators can be used in ArkTS widgets since API version 9. 11> 12> These two decorators can be used in atomic services since API version 11. 13 14## Overview 15 16\@ObjectLink and \@Observed class decorators are used for two-way data synchronization in scenarios involving nested objects or arrays: 17 18- Use **new** to create a class decorated by \@Observed so that the property changes can be observed. 19 20- The \@ObjectLink decorated state variable in the child component is used to accept the instance of the \@Observed decorated class and establish two-way data binding with the corresponding state variable in the parent component. The instance can be an \@Observed decorated item in the array or an \@Observed decorated property in the class object. 21 22- Using \@Observed alone has no effect in nested classes. To observe changes of class properties, it must be used with a custom component. (For examples, see [Nested Object](#nested-object)). To create a two-way or one-way synchronization, it must be used with \@ObjectLink or with \@Prop. (For details, see [Differences Between \@Prop and \@ObjectLink](#differences-between-prop-and-objectlink).) 23 24 25## Decorator Description 26 27| \@Observed Decorator| Description | 28| -------------- | --------------------------------- | 29| Decorator parameters | None. | 30| Class decorator | Decorates a class. You must use **new** to create a class object before defining the class.| 31 32| \@ObjectLink Decorator| Description | 33| ----------------- | ---------------------------------------- | 34| Decorator parameters | None. | 35| Allowed variable types | \@Observed decorated class instance.<br>\@ObjectLink does not support simple types. To use simple types, you can use [\@Prop](arkts-prop.md).<br>Objects of classes that extend Date, [Array](#two-dimensional-array), [Map](#extended-map-class), and [Set](#extended-set-class) (the latter two are supported since API version 11). For an example, see [Observed Changes](#observed-changes).<br>(Applicable to API version 11 or later) Union type of @Observed decorated classes and **undefined** or **null**, for example, **ClassA \| ClassB**, **ClassA \| undefined**, or **ClassA \| null**. For details, see [Union Type @ObjectLink](#union-type-objectlink).<br>An @ObjectLink decorated variable accepts changes to its properties, but assignment is not allowed. In other words, an @ObjectLink decorated variable is read-only and cannot be changed.| 36| Initial value for the decorated variable | Not allowed. | 37 38Example of a read-only \@ObjectLink decorated variable: 39 40 41```ts 42// Value assignment is allowed for the @ObjectLink decorated variable. 43this.objLink.a= ... 44// Value assignment is not allowed for the @ObjectLink decorated variable. 45this.objLink= ... 46``` 47 48> **NOTE** 49> 50> Value assignment is not allowed for the \@ObjectLink decorated variable. To assign a value, use [@Prop](arkts-prop.md) instead. 51> 52> - \@Prop creates a one-way synchronization from the data source to the decorated variable. It takes a copy of its source to enable changes to remain local. When \@Prop observes a change to its source, the local value of the \@Prop decorated variable is overwritten. 53> 54> - \@ObjectLink creates a two-way synchronization between the data source and the decorated variable. An \@ObjectLink decorated variable can be considered as a pointer to the source object inside the parent component. Do not assign values to \@ObjectLink decorated variables, as doing so will interrupt the synchronization chain. \@ObjectLink decorated variables are initialized through data source (Object) references. Assigning a value to them is equivalent to updating the array items or class properties in the parent component, which is not supported in TypeScript/JavaScript and will result in a runtime error. 55 56 57## Variable Transfer/Access Rules 58 59| \@ObjectLink Transfer/Access| Description | 60| ----------------- | ---------------------------------------- | 61| Initialization from the parent component | Mandatory.<br>To initialize an \@ObjectLink decorated variable, a variable in the parent component must meet all the following conditions:<br>- The variable type is an \@Observed decorated class.<br>- The initialized value must be an array item or a class property.<br>- The class or array of the synchronization source must be decorated by [\@State](./arkts-state.md), [\@Link](./arkts-link.md), [\@Provide](./arkts-provide-and-consume.md), [\@Consume](./arkts-provide-and-consume.md), or \@ObjectLink.<br>For an example where the synchronization source is an array item, see [Object Array](#object-array). For an example of the initialized class, see [Nested Object](#nested-object).| 62| Synchronization with the source | Two-way. | 63| Subnode initialization | Supported; can be used to initialize a regular variable or \@State, \@Link, \@Prop, or \@Provide decorated variable in the child component.| 64 65 66 **Figure 1** Initialization rule 67 68 69 70 71 72## Observed Changes and Behavior 73 74 75### Observed Changes 76 77If the property of an \@Observed decorated class is not of the simple type, such as class, object, or array, it must be decorated by \@Observed. Otherwise, the property changes cannot be observed. 78 79 80```ts 81class Child { 82 public num: number; 83 84 constructor(num: number) { 85 this.num = num; 86 } 87} 88 89@Observed 90class Parent { 91 public child: Child; 92 public count: number; 93 94 constructor(child: Child, count: number) { 95 this.child = child; 96 this.count = count; 97 } 98} 99``` 100 101In the preceding example, **Parent** is decorated by \@Observed, and the value changes of its member variables can be observed. In contrast, **Child** is not decorated by \@Observed, and therefore its property changes cannot be observed. 102 103 104```ts 105@ObjectLink parent: Parent; 106 107// Value changes can be observed. 108this.parent.child = new Child(5); 109this.parent.count = 5; 110 111// Child is not decorated by @Observed, therefore, its property changes cannot be observed. 112this.parent.child.num = 5; 113``` 114 115\@ObjectLink: \@ObjectLink can only accept instances of classes decorated by \@Observed. When possible, design a separate custom component to render each array or object. In this case, an object array or nested object (which is an object whose property is an object) requires two custom components: one for rendering an external array/object, and the other for rendering a class object nested within the array/object. The following can be observed: 116 117- Value changes of the properties that **Object.keys(observedObject)** returns. For details, see [Nested Object](#nested-object). 118 119- Replacement of array items for the data source of an array and changes of class properties for the data source of a class. For details, see [Object Array](#object-array). 120 121For an instance of the class that extends **Date**, the value changes of **Date** properties can be observed. In addition, you can call the following APIs to update **Date** properties: **setFullYear**, **setMonth**, **setDate**, **setHours**, **setMinutes**, **setSeconds**, **setMilliseconds**, **setTime**, **setUTCFullYear**, **setUTCMonth**, **setUTCDate**, **setUTCHours**, **setUTCMinutes**, **setUTCSeconds**, and **setUTCMilliseconds**. 122 123```ts 124@Observed 125class DateClass extends Date { 126 constructor(args: number | string) { 127 super(args); 128 } 129} 130 131@Observed 132class NewDate { 133 public data: DateClass; 134 135 constructor(data: DateClass) { 136 this.data = data; 137 } 138} 139 140@Component 141struct Child { 142 label: string = 'date'; 143 @ObjectLink data: DateClass; 144 145 build() { 146 Column() { 147 Button(`child increase the day by 1`) 148 .onClick(() => { 149 this.data.setDate(this.data.getDate() + 1); 150 }) 151 DatePicker({ 152 start: new Date('1970-1-1'), 153 end: new Date('2100-1-1'), 154 selected: this.data 155 }) 156 } 157 } 158} 159 160@Entry 161@Component 162struct Parent { 163 @State newData: NewDate = new NewDate(new DateClass('2023-1-1')); 164 165 build() { 166 Column() { 167 Child({ label: 'date', data: this.newData.data }) 168 169 Button(`parent update the new date`) 170 .onClick(() => { 171 this.newData.data = new DateClass('2023-07-07'); 172 }) 173 Button(`ViewB: this.newData = new NewDate(new DateClass('2023-08-20'))`) 174 .onClick(() => { 175 this.newData = new NewDate(new DateClass('2023-08-20')); 176 }) 177 } 178 } 179} 180``` 181 182For a class that extends **Map**, the value changes of the **Map** instance can be observed. In addition, you can call the following APIs to update the instance: **set**, **clear**, and **delete**. For details, see [Extended Map Class](#extended-map-class). 183 184For a class that extends **Set**, the value changes of the **Set** instance can be observed. In addition, you can call the following APIs to update the instance: **add**, **clear**, and **delete**. For details, see [Extended Set Class](#extended-set-class). 185 186 187### Framework Behavior 188 1891. Initial rendering: 190 191 a. \@Observed causes all instances of the decorated class to be wrapped with an opaque proxy object, which takes over the **setter** and **getter** methods of the properties of the class. 192 193 b. The \@ObjectLink decorated variable in the child component is initialized from the parent component and accepts the instance of the \@Observed decorated class. The \@ObjectLink decorated wrapped object registers itself with the \@Observed decorated class. 194 1952. Property update: When the property of the \@Observed decorated class is updated, the framework executes **setter** and **getter** methods of the proxy, traverses the \@ObjectLink decorated wrapped objects that depend on it, and notifies the data update. 196 197 198## Constraints 199 2001. Using \@Observed to decorate a class changes the original prototype chain of the class. Using \@Observed and other class decorators to decorate the same class may cause problems. 201 2022. The \@ObjectLink decorator cannot be used in custom components decorated by \@Entry. 203 2043. The variable type decorated by \@ObjectLink must be the class explicitly decorated by @Observed. If no type is specified or the class is not decorated by \@Observed, an error is reported during compilation. 205 206 ```ts 207 @Observed 208 class Info { 209 count: number; 210 211 constructor(count: number) { 212 this.count = count; 213 } 214 } 215 216 class Test { 217 msg: number; 218 219 constructor(msg: number) { 220 this.msg = msg; 221 } 222 } 223 224 // Incorrect format. The count type is not specified, leading to a compilation error. 225 @ObjectLink count; 226 // Incorrect format. Test is not decorated by @Observed, leading to a compilation error. 227 @ObjectLink test: Test; 228 229 // Correct format. 230 @ObjectLink count: Info; 231 ``` 232 2334. Variables decorated by \@ObjectLink cannot be initialized locally. You can only pass in the initial value from the parent component through construction parameters. Otherwise, an error is reported during compilation. 234 235 ```ts 236 @Observed 237 class Info { 238 count: number; 239 240 constructor(count: number) { 241 this.count = count; 242 } 243 } 244 245 // Incorrect format. An error is reported during compilation. 246 @ObjectLink count: Info = new Info(10); 247 248 // Correct format. 249 @ObjectLink count: Info; 250 ``` 251 2525. The variables decorated by \@ObjectLink are read-only and cannot be assigned values. Otherwise, an error "Cannot set property when setter is undefined" is reported during runtime. If you need to replace all variables decorated by \@ObjectLink, you can replace them in the parent component. 253 254 [Incorrect Usage] 255 256 ```ts 257 @Observed 258 class Info { 259 count: number; 260 261 constructor(count: number) { 262 this.count = count; 263 } 264 } 265 266 @Component 267 struct Child { 268 @ObjectLink num: Info; 269 270 build() { 271 Column() { 272 Text(`Value of num: ${this.num.count}`) 273 .onClick(() => { 274 // Incorrect format. The variable decorated by @ObjectLink cannot be assigned a value. 275 this.num = new Info(10); 276 }) 277 } 278 } 279 } 280 281 @Entry 282 @Component 283 struct Parent { 284 @State num: Info = new Info(10); 285 286 build() { 287 Column() { 288 Text(`Value of count: ${this.num.count}`) 289 Child({num: this.num}) 290 } 291 } 292 } 293 ``` 294 295 [Correct Usage] 296 297 ```ts 298 @Observed 299 class Info { 300 count: number; 301 302 constructor(count: number) { 303 this.count = count; 304 } 305 } 306 307 @Component 308 struct Child { 309 @ObjectLink num: Info; 310 311 build() { 312 Column() { 313 Text(`Value of num: ${this.num.count}`) 314 .onClick(() => { 315 // Correct format, which is used to change the member property of the @ObjectLink decorated variables. 316 this.num.count = 20; 317 }) 318 } 319 } 320 } 321 322 @Entry 323 @Component 324 struct Parent { 325 @State num: Info = new Info(10); 326 327 build() { 328 Column() { 329 Text(`Value of count: ${this.num.count}`) 330 Button('click') 331 .onClick(() => { 332 // Replace the variable in the parent component. 333 this.num = new Info(30); 334 }) 335 Child({num: this.num}) 336 } 337 } 338 } 339 ``` 340 341 342## Use Scenarios 343 344### Inheritance Object 345 346```ts 347@Observed 348class Animal { 349 name: string; 350 age: number; 351 352 constructor(name: string, age: number) { 353 this.name = name; 354 this.age = age; 355 } 356} 357 358@Observed 359class Dog extends Animal { 360 kinds: string; 361 362 constructor(name: string, age: number, kinds: string) { 363 super(name, age); 364 this.kinds = kinds; 365 } 366} 367 368@Entry 369@Component 370struct Index { 371 @State dog: Dog = new Dog('Molly', 2, 'Husky'); 372 373 @Styles 374 pressedStyles() { 375 .backgroundColor('#ffd5d5d5') 376 } 377 378 @Styles 379 normalStyles() { 380 .backgroundColor('#ffffff') 381 } 382 383 build() { 384 Column() { 385 Text(`${this.dog.name}`) 386 .width(320) 387 .margin(10) 388 .fontSize(30) 389 .textAlign(TextAlign.Center) 390 .stateStyles({ 391 pressed: this.pressedStyles, 392 normal: this.normalStyles 393 }) 394 .onClick(() => { 395 this.dog.name = 'DouDou'; 396 }) 397 398 Text(`${this.dog.age}`) 399 .width(320) 400 .margin(10) 401 .fontSize(30) 402 .textAlign(TextAlign.Center) 403 .stateStyles({ 404 pressed: this.pressedStyles, 405 normal: this.normalStyles 406 }) 407 .onClick(() => { 408 this.dog.age = 3; 409 }) 410 411 Text(`${this.dog.kinds}`) 412 .width(320) 413 .margin(10) 414 .fontSize(30) 415 .textAlign(TextAlign.Center) 416 .stateStyles({ 417 pressed: this.pressedStyles, 418 normal: this.normalStyles 419 }) 420 .onClick(() => { 421 this.dog.kinds = 'Samoyed'; 422 }) 423 } 424 } 425} 426``` 427 428 429 430In the preceding example, some properties (**name** and **age**) in the **Dog** class are inherited from the **Animal** class. You can directly change **name** and **age** in the **dog** variable decorated by \@State to trigger UI re-rendering. 431 432### Nested Object 433 434```ts 435@Observed 436class Book { 437 name: string; 438 439 constructor(name: string) { 440 this.name = name; 441 } 442} 443 444@Observed 445class Bag { 446 book: Book; 447 448 constructor(book: Book) { 449 this.book = book; 450 } 451} 452 453@Component 454struct BookCard { 455 @ObjectLink book: Book; 456 457 build() { 458 Column() { 459 Text(`BookCard: ${this.book.name}`) // The name change can be observed. 460 .width(320) 461 .margin(10) 462 .textAlign(TextAlign.Center) 463 464 Button('change book.name') 465 .width(320) 466 .margin(10) 467 .onClick(() => { 468 this.book.name = 'C++'; 469 }) 470 } 471 } 472} 473 474@Entry 475@Component 476struct Index { 477 @State bag: Bag = new Bag(new Book('JS')); 478 479 build() { 480 Column() { 481 Text(`Index: ${this.bag.book.name}`) // The name change cannot be observed. 482 .width(320) 483 .margin(10) 484 .textAlign(TextAlign.Center) 485 486 Button('change bag.book.name') 487 .width(320) 488 .margin(10) 489 .onClick(() => { 490 this.bag.book.name = 'TS'; 491 }) 492 493 BookCard({ book: this.bag.book }) 494 } 495 } 496} 497``` 498 499 500 501In the preceding example, the **Text** component in the **Index** component is not re-rendered because the change belongs to the second layer and \@State cannot observe the change at the second layer. However, **Book** is decorated by \@Observed, and the **name** property of **Book** can be observed by \@ObjectLink. Therefore, no matter which button is clicked, the **Text** component in the **BookCard** component is re-rendered. 502 503### Object Array 504 505An object array is a frequently used data structure. The following example shows the usage of array objects. 506 507> **NOTE** 508> 509> **NextID** is used to generate a unique, persistent key for each array item during [ForEach rendering](./arkts-rendering-control-foreach.md) to identify the corresponding component. 510 511```ts 512let NextID: number = 1; 513 514@Observed 515class Info { 516 public id: number; 517 public info: number; 518 519 constructor(info: number) { 520 this.id = NextID++; 521 this.info = info; 522 } 523} 524 525@Component 526struct Child { 527 // The type of the Child's @ObjectLink is Info. 528 @ObjectLink info: Info; 529 label: string = 'ViewChild'; 530 531 build() { 532 Row() { 533 Button(`ViewChild [${this.label}] this.info.info = ${this.info ? this.info.info : "undefined"}`) 534 .width(320) 535 .margin(10) 536 .onClick(() => { 537 this.info.info += 1; 538 }) 539 } 540 } 541} 542 543@Entry 544@Component 545struct Parent { 546 // Info[] decorated by @State in the Parent. 547 @State arrA: Info[] = [new Info(0), new Info(0)]; 548 549 build() { 550 Column() { 551 ForEach(this.arrA, 552 (item: Info) => { 553 Child({ label: `#${item.id}`, info: item }) 554 }, 555 (item: Info): string => item.id.toString() 556 ) 557 // Initialize the @ObjectLink decorated variable using the @State decorated array, whose items are instances of @Observed decorated Info. 558 Child({ label: `ViewChild this.arrA[first]`, info: this.arrA[0] }) 559 Child({ label: `ViewChild this.arrA[last]`, info: this.arrA[this.arrA.length-1] }) 560 561 Button(`ViewParent: reset array`) 562 .width(320) 563 .margin(10) 564 .onClick(() => { 565 this.arrA = [new Info(0), new Info(0)]; 566 }) 567 Button(`ViewParent: push`) 568 .width(320) 569 .margin(10) 570 .onClick(() => { 571 this.arrA.push(new Info(0)); 572 }) 573 Button(`ViewParent: shift`) 574 .width(320) 575 .margin(10) 576 .onClick(() => { 577 if (this.arrA.length > 0) { 578 this.arrA.shift(); 579 } else { 580 console.log("length <= 0"); 581 } 582 }) 583 Button(`ViewParent: item property in middle`) 584 .width(320) 585 .margin(10) 586 .onClick(() => { 587 this.arrA[Math.floor(this.arrA.length / 2)].info = 10; 588 }) 589 Button(`ViewParent: item property in middle`) 590 .width(320) 591 .margin(10) 592 .onClick(() => { 593 this.arrA[Math.floor(this.arrA.length / 2)] = new Info(11); 594 }) 595 } 596 } 597} 598``` 599 600 601 602- **this.arrA[Math.floor(this.arrA.length/2)] = new Info(..)**: The change of this state variable triggers two updates. 603 1. **ForEach**: The value assignment of the array item causes the change of [itemGenerator](../reference/apis-arkui/arkui-ts/ts-rendering-control-foreach.md) of **ForEach**. Therefore, the array item is identified as changed, and the item builder of **ForEach** is executed to create a **Child** component instance. 604 2. **Child({ label: ViewChild this.arrA[last], info: this.arrA[this.arrA.length-1] })**: The preceding update changes the second element in the array. Therefore, the **Child** component instance bound to **this.arrA[1]** is updated. 605 606- **this.arrA.push(new Info(0))**: The change of this state variable triggers two updates with different effects. 607 1. **ForEach**: The newly added **Info** object is unknown to the **ForEach** [itemGenerator](../reference/apis-arkui/arkui-ts/ts-rendering-control-foreach.md). The item builder of **ForEach** will be executed to create a **Child** component instance. 608 2. **Child({ label: ViewChild this.arrA[last], info: this.arrA[this.arrA.length-1] })**: The last item of the array is changed. As a result, the second **Child** component instance is changed. **Child({ label: ViewChild this.arrA[first], info: this.arrA[0] })**: The change to the array does not trigger a change to the array item, so the first **Child** component instance is not re-rendered. 609 610- **this.arrA[Math.floor(this.arrA.length/2)].info**: @State cannot observe changes at the second layer. However, as **Info** is decorated by \@Observed, the change of its properties will be observed by \@ObjectLink. 611 612 613### Two-Dimensional Array 614 615@Observed class decoration is required for a two-dimensional array. You can declare an \@Observed decorated class that extends from **Array**. 616 617 618```ts 619@Observed 620class ObservedArray<T> extends Array<T> { 621} 622``` 623 624Declare a class **ObservedArray\<T\>** inherited from Array and use **new** to create an instance of **ObservedArray\<string\>**, and then the property changes can be observed. 625 626The following example shows how to use \@Observed to observe the changes of a two-dimensional array. 627 628```ts 629@Observed 630class ObservedArray<T> extends Array<T> { 631} 632 633@Component 634struct Item { 635 @ObjectLink itemArr: ObservedArray<string>; 636 637 build() { 638 Row() { 639 ForEach(this.itemArr, (item: string, index: number) => { 640 Text(`${index}: ${item}`) 641 .width(100) 642 .height(100) 643 }, (item: string) => item) 644 } 645 } 646} 647 648@Entry 649@Component 650struct IndexPage { 651 @State arr: Array<ObservedArray<string>> = [new ObservedArray<string>('apple'), new ObservedArray<string>('banana'), new ObservedArray<string>('orange')]; 652 653 build() { 654 Column() { 655 ForEach(this.arr, (itemArr: ObservedArray<string>) => { 656 Item({ itemArr: itemArr }) 657 }) 658 659 Divider() 660 661 Button('push two-dimensional array item') 662 .margin(10) 663 .onClick(() => { 664 this.arr[0].push('strawberry'); 665 }) 666 667 Button('push array item') 668 .margin(10) 669 .onClick(() => { 670 this.arr.push(new ObservedArray<string>('pear')); 671 }) 672 673 Button('change two-dimensional array first item') 674 .margin(10) 675 .onClick(() => { 676 this.arr[0][0] = 'APPLE'; 677 }) 678 679 Button('change array first item') 680 .margin(10) 681 .onClick(() => { 682 this.arr[0] = new ObservedArray<string>('watermelon'); 683 }) 684 } 685 } 686} 687``` 688 689 690 691### Extended Map Class 692 693> **NOTE** 694> 695> Since API version 11, \@ObjectLink supports @Observed decorated classes extending from **Map** and the Map type. 696 697In the following example, the **myMap** variable is of the MyMap\<number, string\> type. When the button is clicked, the value of **myMap** changes, and the UI is re-rendered. 698 699```ts 700@Observed 701class Info { 702 public info: MyMap<number, string>; 703 704 constructor(info: MyMap<number, string>) { 705 this.info = info; 706 } 707} 708 709 710@Observed 711export class MyMap<K, V> extends Map<K, V> { 712 public name: string; 713 714 constructor(name?: string, args?: [K, V][]) { 715 super(args); 716 this.name = name ? name : "My Map"; 717 } 718 719 getName() { 720 return this.name; 721 } 722} 723 724@Entry 725@Component 726struct MapSampleNested { 727 @State message: Info = new Info(new MyMap("myMap", [[0, "a"], [1, "b"], [3, "c"]])); 728 729 build() { 730 Row() { 731 Column() { 732 MapSampleNestedChild({ myMap: this.message.info }) 733 } 734 .width('100%') 735 } 736 .height('100%') 737 } 738} 739 740@Component 741struct MapSampleNestedChild { 742 @ObjectLink myMap: MyMap<number, string>; 743 744 build() { 745 Row() { 746 Column() { 747 ForEach(Array.from(this.myMap.entries()), (item: [number, string]) => { 748 Text(`${item[0]}`).fontSize(30) 749 Text(`${item[1]}`).fontSize(30) 750 Divider().strokeWidth(5) 751 }) 752 753 Button('set new one') 754 .width(200) 755 .margin(10) 756 .onClick(() => { 757 this.myMap.set(4, "d"); 758 }) 759 Button('clear') 760 .width(200) 761 .margin(10) 762 .onClick(() => { 763 this.myMap.clear(); 764 }) 765 Button('replace the first one') 766 .width(200) 767 .margin(10) 768 .onClick(() => { 769 this.myMap.set(0, "aa"); 770 }) 771 Button('delete the first one') 772 .width(200) 773 .margin(10) 774 .onClick(() => { 775 this.myMap.delete(0); 776 }) 777 } 778 .width('100%') 779 } 780 .height('100%') 781 } 782} 783``` 784 785 786 787### Extended Set Class 788 789> **NOTE** 790> 791> Since API version 11, \@ObjectLink supports @Observed decorated classes extending from **Set** and the Set type. 792 793In the following example, the **mySet** variable is of the MySet\<number\> type. When the button is clicked, the value of **mySet** changes, and the UI is re-rendered. 794 795```ts 796@Observed 797class Info { 798 public info: MySet<number>; 799 800 constructor(info: MySet<number>) { 801 this.info = info; 802 } 803} 804 805 806@Observed 807export class MySet<T> extends Set<T> { 808 public name: string; 809 810 constructor(name?: string, args?: T[]) { 811 super(args); 812 this.name = name ? name : "My Set"; 813 } 814 815 getName() { 816 return this.name; 817 } 818} 819 820@Entry 821@Component 822struct SetSampleNested { 823 @State message: Info = new Info(new MySet("Set", [0, 1, 2, 3, 4])); 824 825 build() { 826 Row() { 827 Column() { 828 SetSampleNestedChild({ mySet: this.message.info }) 829 } 830 .width('100%') 831 } 832 .height('100%') 833 } 834} 835 836@Component 837struct SetSampleNestedChild { 838 @ObjectLink mySet: MySet<number>; 839 840 build() { 841 Row() { 842 Column() { 843 ForEach(Array.from(this.mySet.entries()), (item: [number, number]) => { 844 Text(`${item}`).fontSize(30) 845 Divider() 846 }) 847 Button('set new one') 848 .width(200) 849 .margin(10) 850 .onClick(() => { 851 this.mySet.add(5); 852 }) 853 Button('clear') 854 .width(200) 855 .margin(10) 856 .onClick(() => { 857 this.mySet.clear(); 858 }) 859 Button('delete the first one') 860 .width(200) 861 .margin(10) 862 .onClick(() => { 863 this.mySet.delete(0); 864 }) 865 } 866 .width('100%') 867 } 868 .height('100%') 869 } 870} 871``` 872 873 874 875## Union Type @ObjectLink 876 877@ObjectLink supports union types of @Observed decorated classes and **undefined** or **null**. In the following example, the type of **count** is **Source | Data | undefined**. If the property or type of **count** is changed when the button in the **Parent** component is clicked, the change will be synchronized to the **Child** component. 878 879```ts 880@Observed 881class Source { 882 public source: number; 883 884 constructor(source: number) { 885 this.source = source; 886 } 887} 888 889@Observed 890class Data { 891 public data: number; 892 893 constructor(data: number) { 894 this.data = data; 895 } 896} 897 898@Entry 899@Component 900struct Parent { 901 @State count: Source | Data | undefined = new Source(10); 902 903 build() { 904 Column() { 905 Child({ count: this.count }) 906 907 Button('change count property') 908 .margin(10) 909 .onClick(() => { 910 // Determine the count type and update the property. 911 if (this.count instanceof Source) { 912 this.count.source += 1; 913 } else if (this.count instanceof Data) { 914 this.count.data += 1; 915 } else { 916 console.info('count is undefined, cannot change property'); 917 } 918 }) 919 920 Button('change count to Source') 921 .margin(10) 922 .onClick(() => { 923 // Assign the value of an instance of Source. 924 this.count = new Source(100); 925 }) 926 927 Button('change count to Data') 928 .margin(10) 929 .onClick(() => { 930 // Assign the value of an instance of Data. 931 this.count = new Data(100); 932 }) 933 934 Button('change count to undefined') 935 .margin(10) 936 .onClick(() => { 937 // Assign the value undefined. 938 this.count = undefined; 939 }) 940 }.width('100%') 941 } 942} 943 944@Component 945struct Child { 946 @ObjectLink count: Source | Data | undefined; 947 948 build() { 949 Column() { 950 Text(`count is instanceof ${this.count instanceof Source ? 'Source' : 951 this.count instanceof Data ? 'Data' : 'undefined'}`) 952 .fontSize(30) 953 .margin(10) 954 955 Text(`count's property is ${this.count instanceof Source ? this.count.source : this.count?.data}`).fontSize(15) 956 957 }.width('100%') 958 } 959} 960``` 961 962 963 964## FAQs 965 966### Assigning Value to @ObjectLink Decorated Variable in Child Component 967 968It is not allowed to assign a value to an @ObjectLink decorated variable in the child component. 969 970[Incorrect Usage] 971 972```ts 973@Observed 974class Info { 975 public info: number = 0; 976 977 constructor(info: number) { 978 this.info = info; 979 } 980} 981 982@Component 983struct ObjectLinkChild { 984 @ObjectLink testNum: Info; 985 986 build() { 987 Text(`ObjectLinkChild testNum ${this.testNum.info}`) 988 .onClick(() => { 989 // The @ObjectLink decorated variable cannot be assigned a value here. 990 this.testNum = new Info(47); 991 }) 992 } 993} 994 995@Entry 996@Component 997struct Parent { 998 @State testNum: Info[] = [new Info(1)]; 999 1000 build() { 1001 Column() { 1002 Text(`Parent testNum ${this.testNum[0].info}`) 1003 .onClick(() => { 1004 this.testNum[0].info += 1; 1005 }) 1006 1007 ObjectLinkChild({ testNum: this.testNum[0] }) 1008 } 1009 } 1010} 1011``` 1012 1013In this example, an attempt is made to assign a value to the @ObjectLink decorated variable by clicking **ObjectLinkChild**. 1014 1015``` 1016this.testNum = new Info(47); 1017``` 1018 1019This is not allowed. For @ObjectLink that implements two-way data synchronization, assigning a value is equivalent to updating the array item or class property in the parent component, which is not supported in TypeScript/JavaScript and will result in a runtime error. 1020 1021[Correct Usage] 1022 1023```ts 1024@Observed 1025class Info { 1026 public info: number = 0; 1027 1028 constructor(info: number) { 1029 this.info = info; 1030 } 1031} 1032 1033@Component 1034struct ObjectLinkChild { 1035 @ObjectLink testNum: Info; 1036 1037 build() { 1038 Text(`ObjectLinkChild testNum ${this.testNum.info}`) 1039 .onClick(() => { 1040 // You can assign values to the properties of the ObjectLink decorated object. 1041 this.testNum.info = 47; 1042 }) 1043 } 1044} 1045 1046@Entry 1047@Component 1048struct Parent { 1049 @State testNum: Info[] = [new Info(1)]; 1050 1051 build() { 1052 Column() { 1053 Text(`Parent testNum ${this.testNum[0].info}`) 1054 .onClick(() => { 1055 this.testNum[0].info += 1; 1056 }) 1057 1058 ObjectLinkChild({ testNum: this.testNum[0] }) 1059 } 1060 } 1061} 1062``` 1063 1064### UI Not Updated on property Changes in Simple Nested Objects 1065 1066If you find your application UI not updating after a property in a nested object is changed, you may want to check the decorators in use. 1067 1068Each decorator has its scope of observable changes, and only those observed changes can cause the UI to update. The \@Observed decorator can observe the property changes of nested objects, while other decorators can observe only the changes at the first layer. 1069 1070[Incorrect Usage] 1071 1072In the following example, some UI components are not updated. 1073 1074 1075```ts 1076class Parent { 1077 parentId: number; 1078 1079 constructor(parentId: number) { 1080 this.parentId = parentId; 1081 } 1082 1083 getParentId(): number { 1084 return this.parentId; 1085 } 1086 1087 setParentId(parentId: number): void { 1088 this.parentId = parentId; 1089 } 1090} 1091 1092class Child { 1093 childId: number; 1094 1095 constructor(childId: number) { 1096 this.childId = childId; 1097 } 1098 1099 getChildId(): number { 1100 return this.childId; 1101 } 1102 1103 setChildId(childId: number): void { 1104 this.childId = childId; 1105 } 1106} 1107 1108class Cousin extends Parent { 1109 cousinId: number = 47; 1110 child: Child; 1111 1112 constructor(parentId: number, cousinId: number, childId: number) { 1113 super(parentId); 1114 this.cousinId = cousinId; 1115 this.child = new Child(childId); 1116 } 1117 1118 getCousinId(): number { 1119 return this.cousinId; 1120 } 1121 1122 setCousinId(cousinId: number): void { 1123 this.cousinId = cousinId; 1124 } 1125 1126 getChild(): number { 1127 return this.child.getChildId(); 1128 } 1129 1130 setChild(childId: number): void { 1131 return this.child.setChildId(childId); 1132 } 1133} 1134 1135@Entry 1136@Component 1137struct MyView { 1138 @State cousin: Cousin = new Cousin(10, 20, 30); 1139 1140 build() { 1141 Column({ space: 10 }) { 1142 Text(`parentId: ${this.cousin.parentId}`) 1143 Button("Change Parent.parent") 1144 .onClick(() => { 1145 this.cousin.parentId += 1; 1146 }) 1147 1148 Text(`cousinId: ${this.cousin.cousinId}`) 1149 Button("Change Cousin.cousinId") 1150 .onClick(() => { 1151 this.cousin.cousinId += 1; 1152 }) 1153 1154 Text(`childId: ${this.cousin.child.childId}`) 1155 Button("Change Cousin.Child.childId") 1156 .onClick(() => { 1157 // The Text component is not updated when clicked. 1158 this.cousin.child.childId += 1; 1159 }) 1160 } 1161 } 1162} 1163``` 1164 1165- The UI is not re-rendered when the last **Text('child: ${this.cousin.child.childId}')** is clicked. This is because, \@State **cousin: Cousin** can only observe the property change of **this.cousin**, such as **this.cousin.parentId**, **this.cousin.cousinId**, and **this.cousin.child**, but cannot observe the in-depth property, that is, **this.cousin.child.childId** (**childId** is the property of the **Child** object embedded in **cousin**). 1166 1167- To observe the properties of nested object **Child**, you need to make the following changes: 1168 - Construct a child component for separate rendering of the **Child** instance. This child component can use \@ObjectLink **child : Child** or \@Prop **child : Child**. \@ObjectLink is generally used, unless local changes to the **Child** object are required. 1169 - The nested **Child** object must be decorated by \@Observed. When a **Child** object is created in **Cousin** (**Cousin(10, 20, 30)** in this example), it is wrapped in the ES6 proxy. When the **Child** property changes to **this.cousin.child.childId += 1**, the \@ObjectLink decorated variable is notified of the change. 1170 1171[Correct Usage] 1172 1173The following example uses \@Observed/\@ObjectLink to observe property changes for nested objects. 1174 1175 1176```ts 1177class Parent { 1178 parentId: number; 1179 1180 constructor(parentId: number) { 1181 this.parentId = parentId; 1182 } 1183 1184 getParentId(): number { 1185 return this.parentId; 1186 } 1187 1188 setParentId(parentId: number): void { 1189 this.parentId = parentId; 1190 } 1191} 1192 1193@Observed 1194class Child { 1195 childId: number; 1196 1197 constructor(childId: number) { 1198 this.childId = childId; 1199 } 1200 1201 getChildId(): number { 1202 return this.childId; 1203 } 1204 1205 setChildId(childId: number): void { 1206 this.childId = childId; 1207 } 1208} 1209 1210class Cousin extends Parent { 1211 cousinId: number = 47; 1212 child: Child; 1213 1214 constructor(parentId: number, cousinId: number, childId: number) { 1215 super(parentId); 1216 this.cousinId = cousinId; 1217 this.child = new Child(childId); 1218 } 1219 1220 getCousinId(): number { 1221 return this.cousinId; 1222 } 1223 1224 setCousinId(cousinId: number): void { 1225 this.cousinId = cousinId; 1226 } 1227 1228 getChild(): number { 1229 return this.child.getChildId(); 1230 } 1231 1232 setChild(childId: number): void { 1233 return this.child.setChildId(childId); 1234 } 1235} 1236 1237@Component 1238struct ViewChild { 1239 @ObjectLink child: Child; 1240 1241 build() { 1242 Column({ space: 10 }) { 1243 Text(`childId: ${this.child.getChildId()}`) 1244 Button("Change childId") 1245 .onClick(() => { 1246 this.child.setChildId(this.child.getChildId() + 1); 1247 }) 1248 } 1249 } 1250} 1251 1252@Entry 1253@Component 1254struct MyView { 1255 @State cousin: Cousin = new Cousin(10, 20, 30); 1256 1257 build() { 1258 Column({ space: 10 }) { 1259 Text(`parentId: ${this.cousin.parentId}`) 1260 Button("Change Parent.parentId") 1261 .onClick(() => { 1262 this.cousin.parentId += 1; 1263 }) 1264 1265 Text(`cousinId: ${this.cousin.cousinId}`) 1266 Button("Change Cousin.cousinId") 1267 .onClick(() => { 1268 this.cousin.cousinId += 1; 1269 }) 1270 1271 ViewChild({ child: this.cousin.child }) // Alternative format of Text(`childId: ${this.cousin.child.childId}`). 1272 Button("Change Cousin.Child.childId") 1273 .onClick(() => { 1274 this.cousin.child.childId += 1; 1275 }) 1276 } 1277 } 1278} 1279``` 1280 1281### UI Not Updated on property Changes in Complex Nested Objects 1282 1283[Incorrect Usage] 1284 1285The following example creates a child component with an \@ObjectLink decorated variable to render **ParentCounter** with nested properties. Specifically, **SubCounter** nested in **ParentCounter** is decorated with \@Observed. 1286 1287 1288```ts 1289let nextId = 1; 1290@Observed 1291class SubCounter { 1292 counter: number; 1293 constructor(c: number) { 1294 this.counter = c; 1295 } 1296} 1297@Observed 1298class ParentCounter { 1299 id: number; 1300 counter: number; 1301 subCounter: SubCounter; 1302 incrCounter() { 1303 this.counter++; 1304 } 1305 incrSubCounter(c: number) { 1306 this.subCounter.counter += c; 1307 } 1308 setSubCounter(c: number): void { 1309 this.subCounter.counter = c; 1310 } 1311 constructor(c: number) { 1312 this.id = nextId++; 1313 this.counter = c; 1314 this.subCounter = new SubCounter(c); 1315 } 1316} 1317@Component 1318struct CounterComp { 1319 @ObjectLink value: ParentCounter; 1320 build() { 1321 Column({ space: 10 }) { 1322 Text(`${this.value.counter}`) 1323 .fontSize(25) 1324 .onClick(() => { 1325 this.value.incrCounter(); 1326 }) 1327 Text(`${this.value.subCounter.counter}`) 1328 .onClick(() => { 1329 this.value.incrSubCounter(1); 1330 }) 1331 Divider().height(2) 1332 } 1333 } 1334} 1335@Entry 1336@Component 1337struct ParentComp { 1338 @State counter: ParentCounter[] = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)]; 1339 build() { 1340 Row() { 1341 Column() { 1342 CounterComp({ value: this.counter[0] }) 1343 CounterComp({ value: this.counter[1] }) 1344 CounterComp({ value: this.counter[2] }) 1345 Divider().height(5) 1346 ForEach(this.counter, 1347 (item: ParentCounter) => { 1348 CounterComp({ value: item }) 1349 }, 1350 (item: ParentCounter) => item.id.toString() 1351 ) 1352 Divider().height(5) 1353 // First click event 1354 Text('Parent: incr counter[0].counter') 1355 .fontSize(20).height(50) 1356 .onClick(() => { 1357 this.counter[0].incrCounter(); 1358 // The value increases by 10 each time the event is triggered. 1359 this.counter[0].incrSubCounter(10); 1360 }) 1361 // Second click event 1362 Text('Parent: set.counter to 10') 1363 .fontSize(20).height(50) 1364 .onClick(() => { 1365 // The value cannot be set to 10, and the UI is not updated. 1366 this.counter[0].setSubCounter(10); 1367 }) 1368 Text('Parent: reset entire counter') 1369 .fontSize(20).height(50) 1370 .onClick(() => { 1371 this.counter = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)]; 1372 }) 1373 } 1374 } 1375 } 1376} 1377``` 1378 1379For the **onClick** event of **Text('Parent: incr counter[0].counter')**, **this.counter[0].incrSubCounter(10)** calls the **incrSubCounter** method to increase the **counter** value of **SubCounter** by 10. The UI is updated to reflect the change. 1380 1381However, when **this.counter[0].setSubCounter(10)** is called in **onClick** of **Text('Parent: set.counter to 10')**, the **counter** value of **SubCounter** cannot be reset to **10**. 1382 1383**incrSubCounter** and **setSubCounter** are functions of the same **SubCounter**. The UI can be correctly updated when **incrSubCounter** is called for the first click event. However, the UI is not updated when **setSubCounter** is called for the second click event. Actually neither **incrSubCounter** nor **setSubCounter** can trigger an update of **Text('${this.value.subCounter.counter}')**. This is because \@ObjectLink **value: ParentCounter** can only observe the properties of **ParentCounter**. **this.value.subCounter.counter** is a property of **SubCounter** and therefore cannot be observed. 1384 1385However, when **this.counter[0].incrCounter()** is called for the first click event, it marks \@ObjectLink **value: ParentCounter** in the **CounterComp** component as changed. In this case, an update of **Text('${this.value.subCounter.counter}')** is triggered. If **this.counter[0].incrCounter()** is deleted from the first click event, the UI cannot be updated. 1386 1387[Correct Usage] 1388 1389To solve the preceding problem, you can use the following method to directly observe the properties in **SubCounter** so that the **this.counter[0].setSubCounter(10)** API works: 1390 1391 1392```ts 1393CounterComp({ value: this.counter[0] }); // ParentComp passes ParentCounter to CounterComp. 1394@ObjectLink value: ParentCounter; // @ObjectLink receives ParentCounter. 1395 1396CounterChild({ subValue: this.value.subCounter }); // CounterComp passes SubCounter to CounterChild 1397@ObjectLink subValue: SubCounter; // @ObjectLink receives SubCounter. 1398``` 1399 1400This approach enables \@ObjectLink to serve as a proxy for the properties of the **ParentCounter** and **SubCounter** classes. In this way, the property changes of the two classes can be observed and trigger UI update. Even if **this.counter[0].incrCounter()** is deleted, the UI can be updated correctly. 1401 1402This approach can be used to implement "two-layer" observation, that is, observation of external objects and internal nested objects. However, it is only applicable to the \@ObjectLink decorator, but not to \@Prop (\@Prop passes objects through deep copy). For details, see [Differences Between \@Prop and \@ObjectLink](#differences-between-prop-and-objectlink). 1403 1404 1405```ts 1406let nextId = 1; 1407 1408@Observed 1409class SubCounter { 1410 counter: number; 1411 1412 constructor(c: number) { 1413 this.counter = c; 1414 } 1415} 1416 1417@Observed 1418class ParentCounter { 1419 id: number; 1420 counter: number; 1421 subCounter: SubCounter; 1422 1423 incrCounter() { 1424 this.counter++; 1425 } 1426 1427 incrSubCounter(c: number) { 1428 this.subCounter.counter += c; 1429 } 1430 1431 setSubCounter(c: number): void { 1432 this.subCounter.counter = c; 1433 } 1434 1435 constructor(c: number) { 1436 this.id = nextId++; 1437 this.counter = c; 1438 this.subCounter = new SubCounter(c); 1439 } 1440} 1441 1442@Component 1443struct CounterComp { 1444 @ObjectLink value: ParentCounter; 1445 1446 build() { 1447 Column({ space: 10 }) { 1448 Text(`${this.value.counter}`) 1449 .fontSize(25) 1450 .onClick(() => { 1451 this.value.incrCounter(); 1452 }) 1453 CounterChild({ subValue: this.value.subCounter }) 1454 Divider().height(2) 1455 } 1456 } 1457} 1458 1459@Component 1460struct CounterChild { 1461 @ObjectLink subValue: SubCounter; 1462 1463 build() { 1464 Text(`${this.subValue.counter}`) 1465 .onClick(() => { 1466 this.subValue.counter += 1; 1467 }) 1468 } 1469} 1470 1471@Entry 1472@Component 1473struct ParentComp { 1474 @State counter: ParentCounter[] = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)]; 1475 1476 build() { 1477 Row() { 1478 Column() { 1479 CounterComp({ value: this.counter[0] }) 1480 CounterComp({ value: this.counter[1] }) 1481 CounterComp({ value: this.counter[2] }) 1482 Divider().height(5) 1483 ForEach(this.counter, 1484 (item: ParentCounter) => { 1485 CounterComp({ value: item }) 1486 }, 1487 (item: ParentCounter) => item.id.toString() 1488 ) 1489 Divider().height(5) 1490 Text('Parent: reset entire counter') 1491 .fontSize(20).height(50) 1492 .onClick(() => { 1493 this.counter = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)]; 1494 }) 1495 Text('Parent: incr counter[0].counter') 1496 .fontSize(20).height(50) 1497 .onClick(() => { 1498 this.counter[0].incrCounter(); 1499 this.counter[0].incrSubCounter(10); 1500 }) 1501 Text('Parent: set.counter to 10') 1502 .fontSize(20).height(50) 1503 .onClick(() => { 1504 this.counter[0].setSubCounter(10); 1505 }) 1506 } 1507 } 1508 } 1509} 1510``` 1511 1512### Differences Between \@Prop and \@ObjectLink 1513 1514In the following example, the \@ObjectLink decorated variable is a reference to the data source. That is, **this.value.subCounter** and **this.subValue** are different references to the same object. Therefore, when the click handler of **CounterComp** is clicked, both **this.value.subCounter.counter** and **this.subValue.counter** change, and the corresponding component **Text(this.subValue.counter: ${this.subValue.counter})** is re-rendered. 1515 1516 1517```ts 1518let nextId = 1; 1519 1520@Observed 1521class SubCounter { 1522 counter: number; 1523 1524 constructor(c: number) { 1525 this.counter = c; 1526 } 1527} 1528 1529@Observed 1530class ParentCounter { 1531 id: number; 1532 counter: number; 1533 subCounter: SubCounter; 1534 1535 incrCounter() { 1536 this.counter++; 1537 } 1538 1539 incrSubCounter(c: number) { 1540 this.subCounter.counter += c; 1541 } 1542 1543 setSubCounter(c: number): void { 1544 this.subCounter.counter = c; 1545 } 1546 1547 constructor(c: number) { 1548 this.id = nextId++; 1549 this.counter = c; 1550 this.subCounter = new SubCounter(c); 1551 } 1552} 1553 1554@Component 1555struct CounterComp { 1556 @ObjectLink value: ParentCounter; 1557 1558 build() { 1559 Column({ space: 10 }) { 1560 CountChild({ subValue: this.value.subCounter }) 1561 Text(`this.value.counter: increase 7 `) 1562 .fontSize(30) 1563 .onClick(() => { 1564 // Text(`this.subValue.counter: ${this.subValue.counter}`) is re-rendered after clicking. 1565 this.value.incrSubCounter(7); 1566 }) 1567 Divider().height(2) 1568 } 1569 } 1570} 1571 1572@Component 1573struct CountChild { 1574 @ObjectLink subValue: SubCounter; 1575 1576 build() { 1577 Text(`this.subValue.counter: ${this.subValue.counter}`) 1578 .fontSize(30) 1579 } 1580} 1581 1582@Entry 1583@Component 1584struct ParentComp { 1585 @State counter: ParentCounter[] = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)]; 1586 1587 build() { 1588 Row() { 1589 Column() { 1590 CounterComp({ value: this.counter[0] }) 1591 CounterComp({ value: this.counter[1] }) 1592 CounterComp({ value: this.counter[2] }) 1593 Divider().height(5) 1594 ForEach(this.counter, 1595 (item: ParentCounter) => { 1596 CounterComp({ value: item }) 1597 }, 1598 (item: ParentCounter) => item.id.toString() 1599 ) 1600 Divider().height(5) 1601 Text('Parent: reset entire counter') 1602 .fontSize(20).height(50) 1603 .onClick(() => { 1604 this.counter = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)]; 1605 }) 1606 Text('Parent: incr counter[0].counter') 1607 .fontSize(20).height(50) 1608 .onClick(() => { 1609 this.counter[0].incrCounter(); 1610 this.counter[0].incrSubCounter(10); 1611 }) 1612 Text('Parent: set.counter to 10') 1613 .fontSize(20).height(50) 1614 .onClick(() => { 1615 this.counter[0].setSubCounter(10); 1616 }) 1617 } 1618 } 1619 } 1620} 1621``` 1622 1623The following figure shows how \@ObjectLink works. 1624 1625 1626 1627[Incorrect Usage] 1628 1629\@Prop is used instead of \@ObjectLink. Click **Text(this.subValue.counter: ${this.subValue.counter})**, and the UI is re-rendered properly. However, when you click **Text(this.value.counter: increase 7)**, \@Prop makes a local copy of the variable, and the first **Text** of **CounterComp** is not re-rendered. 1630 1631 **this.value.subCounter** and **this.subValue** are not the same object. Therefore, the change of **this.value.subCounter** does not change the copy object of **this.subValue**, and **Text(this.subValue.counter: ${this.subValue.counter})** is not re-rendered. 1632 1633```ts 1634@Component 1635struct CounterComp { 1636 @Prop value: ParentCounter = new ParentCounter(0); 1637 @Prop subValue: SubCounter = new SubCounter(0); 1638 build() { 1639 Column({ space: 10 }) { 1640 Text(`this.subValue.counter: ${this.subValue.counter}`) 1641 .fontSize(20) 1642 .onClick(() => { 1643 this.subValue.counter += 7; 1644 }) 1645 Text(`this.value.counter: increase 7 `) 1646 .fontSize(20) 1647 .onClick(() => { 1648 this.value.incrSubCounter(7); 1649 }) 1650 Divider().height(2) 1651 } 1652 } 1653} 1654``` 1655 1656The following figure shows how \@Prop works. 1657 1658 1659 1660[Correct Usage] 1661 1662Make only one copy of \@Prop value: ParentCounter from **ParentComp** to **CounterComp**. Do not make another copy of **SubCounter**. 1663 1664- Use only one \@Prop **counter: Counter** in the **CounterComp** component. 1665 1666- Add another child component **SubCounterComp** that contains \@ObjectLink **subCounter: SubCounter**. This \@ObjectLink ensures that changes to the **SubCounter** object properties are observed and the UI is updated properly. 1667 1668- \@ObjectLink **subCounter: SubCounter** shares the same **SubCounter** object with **this.counter.subCounter** of \@Prop **counter: Counter** in **CounterComp**. 1669 1670 1671 1672```ts 1673let nextId = 1; 1674 1675@Observed 1676class SubCounter { 1677 counter: number; 1678 constructor(c: number) { 1679 this.counter = c; 1680 } 1681} 1682 1683@Observed 1684class ParentCounter { 1685 id: number; 1686 counter: number; 1687 subCounter: SubCounter; 1688 incrCounter() { 1689 this.counter++; 1690 } 1691 incrSubCounter(c: number) { 1692 this.subCounter.counter += c; 1693 } 1694 setSubCounter(c: number): void { 1695 this.subCounter.counter = c; 1696 } 1697 constructor(c: number) { 1698 this.id = nextId++; 1699 this.counter = c; 1700 this.subCounter = new SubCounter(c); 1701 } 1702} 1703 1704@Component 1705struct SubCounterComp { 1706 @ObjectLink subValue: SubCounter; 1707 build() { 1708 Text(`SubCounterComp: this.subValue.counter: ${this.subValue.counter}`) 1709 .onClick(() => { 1710 this.subValue.counter = 7; 1711 }) 1712 } 1713} 1714@Component 1715struct CounterComp { 1716 @Prop value: ParentCounter; 1717 build() { 1718 Column({ space: 10 }) { 1719 Text(`this.value.incrCounter(): this.value.counter: ${this.value.counter}`) 1720 .fontSize(20) 1721 .onClick(() => { 1722 this.value.incrCounter(); 1723 }) 1724 SubCounterComp({ subValue: this.value.subCounter }) 1725 Text(`this.value.incrSubCounter()`) 1726 .onClick(() => { 1727 this.value.incrSubCounter(77); 1728 }) 1729 Divider().height(2) 1730 } 1731 } 1732} 1733@Entry 1734@Component 1735struct ParentComp { 1736 @State counter: ParentCounter[] = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)]; 1737 build() { 1738 Row() { 1739 Column() { 1740 CounterComp({ value: this.counter[0] }) 1741 CounterComp({ value: this.counter[1] }) 1742 CounterComp({ value: this.counter[2] }) 1743 Divider().height(5) 1744 ForEach(this.counter, 1745 (item: ParentCounter) => { 1746 CounterComp({ value: item }) 1747 }, 1748 (item: ParentCounter) => item.id.toString() 1749 ) 1750 Divider().height(5) 1751 Text('Parent: reset entire counter') 1752 .fontSize(20).height(50) 1753 .onClick(() => { 1754 this.counter = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)]; 1755 }) 1756 Text('Parent: incr counter[0].counter') 1757 .fontSize(20).height(50) 1758 .onClick(() => { 1759 this.counter[0].incrCounter(); 1760 this.counter[0].incrSubCounter(10); 1761 }) 1762 Text('Parent: set.counter to 10') 1763 .fontSize(20).height(50) 1764 .onClick(() => { 1765 this.counter[0].setSubCounter(10); 1766 }) 1767 } 1768 } 1769 } 1770} 1771``` 1772 1773 1774The following figure shows the copy relationship. 1775 1776 1777 1778 1779### Member Variable Changes in the @Observed Decorated Class Constructor Not Taking Effect 1780 1781In state management, @Observed decorated classes are wrapped with a proxy. When a member variable of a class is changed in a component, the proxy intercepts the change. When the value in the data source is changed, the proxy notifies the bound component of the change. In this way, the change can be observed and trigger UI re-rendering. 1782 1783If the value change of a member variable occurs in the class constructor, the change does not pass through the proxy (because the change occurs in the data source). Therefore, even if the change is successful with a timer in the class constructor, the UI cannot be re-rendered. 1784 1785[Incorrect Usage] 1786 1787```ts 1788@Observed 1789class RenderClass { 1790 waitToRender: boolean = false; 1791 1792 constructor() { 1793 setTimeout(() => { 1794 this.waitToRender = true; 1795 console.log("Change the value of waitToRender to" + this.waitToRender); 1796 }, 1000) 1797 } 1798} 1799 1800@Entry 1801@Component 1802struct Index { 1803 @State @Watch('renderClassChange') renderClass: RenderClass = new RenderClass(); 1804 @State textColor: Color = Color.Black; 1805 1806 renderClassChange() { 1807 console.log("The value of renderClass is changed to" + this.renderClass.waitToRender); 1808 } 1809 1810 build() { 1811 Row() { 1812 Column() { 1813 Text("The value of renderClass is" + this.renderClass.waitToRender) 1814 .fontSize(20) 1815 .fontColor(this.textColor) 1816 Button("Show") 1817 .onClick(() => { 1818 // It is not recommended to use other state variables to forcibly re-render the UI. This example is used to check whether the value of waitToRender is updated. 1819 this.textColor = Color.Red; 1820 }) 1821 } 1822 .width('100%') 1823 } 1824 .height('100%') 1825 } 1826} 1827``` 1828 1829In the preceding example, a timer is used in the constructor of **RenderClass**. Though the value of **waitToRender** changes 1 second later, the UI is not re-rendered. After the button is clicked to forcibly refresh the **Text** component, you can see that the value of **waitToRender** is changed to **true**. 1830 1831[Correct Usage] 1832 1833```ts 1834@Observed 1835class RenderClass { 1836 waitToRender: boolean = false; 1837 1838 constructor() { 1839 } 1840} 1841 1842@Entry 1843@Component 1844struct Index { 1845 @State @Watch('renderClassChange') renderClass: RenderClass = new RenderClass(); 1846 1847 renderClassChange() { 1848 console.log("The value of renderClass is changed to" + this.renderClass.waitToRender); 1849 } 1850 1851 onPageShow() { 1852 setTimeout(() => { 1853 this.renderClass.waitToRender = true; 1854 console.log("Change the value of renderClass to" + this.renderClass.waitToRender); 1855 }, 1000) 1856 } 1857 1858 build() { 1859 Row() { 1860 Column() { 1861 Text("The value of renderClass is" + this.renderClass.waitToRender) 1862 .fontSize(20) 1863 } 1864 .width('100%') 1865 } 1866 .height('100%') 1867 } 1868} 1869``` 1870 1871In the preceding example, the timer is moved to the component. In this case, the page displays "The value of renderClass is changed to false". When the timer is triggered, the value of renderClass is changed, triggering the [@Watch](./arkts-watch.md) callback. As a result, page content changes to "The value of renderClass is true" and the log is displayed as "Change the value of renderClass to true". 1872 1873In sum, it is recommended that you change the class members decorated by @Observed in components to implement UI re-rendering. 1874 1875### \@ObjectLink Data Source Update Timing 1876 1877```ts 1878@Observed 1879class Person { 1880 name: string = ''; 1881 age: number = 0; 1882 1883 constructor(name: string, age: number) { 1884 this.name = name; 1885 this.age = age; 1886 } 1887} 1888 1889@Observed 1890class Info { 1891 person: Person; 1892 1893 constructor(person: Person) { 1894 this.person = person; 1895 } 1896} 1897 1898@Entry 1899@Component 1900struct Parent { 1901 @State @Watch('onChange01') info: Info = new Info(new Person('Bob', 10)); 1902 1903 onChange01() { 1904 console.log(':::onChange01:' + this.info.person.name); // 2 1905 } 1906 1907 build() { 1908 Column() { 1909 Text(this.info.person.name).height(40) 1910 Child({ 1911 per: this.info.person, clickEvent: () => { 1912 console.log(':::clickEvent before', this.info.person.name); // 1 1913 this.info.person = new Person('Jack', 12); 1914 console.log(':::clickEvent after', this.info.person.name); // 3 1915 } 1916 }) 1917 } 1918 } 1919} 1920 1921@Component 1922struct Child { 1923 @ObjectLink @Watch('onChange02') per: Person; 1924 clickEvent?: () => void; 1925 1926 onChange02() { 1927 console.log(':::onChange02:' + this.per.name); // 5 1928 } 1929 1930 build() { 1931 Column() { 1932 Button(this.per.name) 1933 .height(40) 1934 .onClick(() => { 1935 this.onClickType(); 1936 }) 1937 } 1938 } 1939 1940 private onClickType() { 1941 if (this.clickEvent) { 1942 this.clickEvent(); 1943 } 1944 console.log(':::-------- this.per.name in Child is still:' + this.per.name); // 4 1945 } 1946} 1947``` 1948 1949The data source update of \@ObjectLink depends on its parent component. When the data source changes of the parent component trigger a re-rendering on the parent component, the data source of the child component \@ObjectLink is reset. This process does not occur immediately after the data source of the parent component changes. Instead, it occurs when the parent component is re-rendered. In the preceding example, **Parent** contains **Child** and passes the arrow function to **Child**. When the child component is clicked, the log printing sequence is from 1 to 5. When the log is printed to log 4, the click event process ends. In this case, only **Child** is marked as the node that needs to be updated by the parent component, therefore, the value of **this.per.name** in log 4 is still **Bob**. The data source of **Child** is updated only when the parent component is re-rendered. 1950 1951When the \@Watch function of **@ObjectLink @Watch('onChange02') per: Person** is executed, the data source of \@ObjectLink has been updated by the parent component. In this case, the value printed in log 5 is **Jack**. 1952 1953The meaning of the log is as follows: 1954- Log 1: Before a value is assigned to **Parent @State @Watch('onChange01') info: Info = new Info(new Person('Bob', 10))**. 1955 1956- Log 2: Assign a value to **Parent @State @Watch('onChange01') info: Info = new Info(new Person('Bob', 10))** and execute its \@Watch function synchronously. 1957 1958- Log 3: A value is assigned to **Parent @State @Watch('onChange01') info: Info = new Info(new Person('Bob', 10))**. 1959 1960- Log 4: After **clickEvent** in the **onClickType** method is executed, **Child** is marked as the node that needs to be updated by the parent component, and the latest value is not updated to **Child @ObjectLink @Watch('onChange02') per: Person**. Therefore, the value of **this.per.name** in log 4 is still **Bob**. 1961 1962- Log 5: The next VSync triggers **Child** re-rendering. **@ObjectLink @Watch('onChange02') per: Person** is re-rendered and its @Watch method is triggered. In this case, the new value of the **@ObjectLink @Watch('onChange02') per: Person** is **Jack**. 1963 1964The parent-child synchronization principle of \@Prop is the same as that of \@ObjectLink. 1965 1966When **this.info.person.name** is changed in **clickEvent**, this change takes effect immediately. In this case, the value of log 4 is **Jack**. 1967 1968```ts 1969Child({ 1970 per: this.info.person, clickEvent: () => { 1971 console.log(':::clickEvent before', this.info.person.name); // 1 1972 this.info.person.name = 'Jack'; 1973 console.log(':::clickEvent after', this.info.person.name); // 3 1974 } 1975}) 1976``` 1977 1978The **Text** component in **Parent** is not re-rendered because **this.info.person.name** is a value with two-layer nesting. 1979 1980### Using the a.b(this.object) Format Fails to Trigger UI Re-render 1981 1982In the **build** method, when the variable decorated by @Observed and @ObjectLink is of the object type and is called using the **a.b(this.object)** format, the original object of **this.object** is passed in the b method. If the property of **this.object** is changed, the UI cannot be re-rendered. In the following example, the UI re-render is not triggered when **this.weather.temperature** in the component is changed by using a static method or using **this** to call the internal method of the component. 1983 1984[Incorrect Usage] 1985 1986```ts 1987@Observed 1988class Weather { 1989 temperature:number; 1990 1991 constructor(temperature:number) { 1992 this.temperature = temperature; 1993 } 1994 1995 static increaseTemperature(weather:Weather) { 1996 weather.temperature++; 1997 } 1998} 1999 2000class Day { 2001 weather:Weather; 2002 week:string; 2003 constructor(weather:Weather, week:string) { 2004 this.weather = weather; 2005 this.week = week; 2006 } 2007} 2008 2009@Entry 2010@Component 2011struct Parent { 2012 @State day1: Day = new Day(new Weather(15), 'Monday'); 2013 2014 build() { 2015 Column({ space:10 }) { 2016 Child({ weather: this.day1.weather}) 2017 } 2018 .height('100%') 2019 .width('100%') 2020 } 2021} 2022 2023@Component 2024struct Child { 2025 @ObjectLink weather: Weather; 2026 2027 reduceTemperature (weather:Weather) { 2028 weather.temperature--; 2029 } 2030 2031 build() { 2032 Column({ space:10 }) { 2033 Text(`The temperature of day1 is ${this.weather.temperature} degrees.`) 2034 .fontSize(20) 2035 Button('increaseTemperature') 2036 .onClick(()=>{ 2037 // The UI cannot be re-rendered using a static method. 2038 Weather.increaseTemperature(this.weather); 2039 }) 2040 Button('reduceTemperature') 2041 .onClick(()=>{ 2042 // The UI cannot be re-rendered using this. 2043 this.reduceTemperature(this.weather); 2044 }) 2045 } 2046 .height('100%') 2047 .width('100%') 2048 } 2049} 2050``` 2051 2052You can add a proxy for **this.weather** to re-render the UI by assigning a value to the variable and then calling the variable. 2053 2054[Correct Usage] 2055 2056```ts 2057@Observed 2058class Weather { 2059 temperature:number; 2060 2061 constructor(temperature:number) { 2062 this.temperature = temperature; 2063 } 2064 2065 static increaseTemperature(weather:Weather) { 2066 weather.temperature++; 2067 } 2068} 2069 2070class Day { 2071 weather:Weather; 2072 week:string; 2073 constructor(weather:Weather, week:string) { 2074 this.weather = weather; 2075 this.week = week; 2076 } 2077} 2078 2079@Entry 2080@Component 2081struct Parent { 2082 @State day1: Day = new Day(new Weather(15), 'Monday'); 2083 2084 build() { 2085 Column({ space:10 }) { 2086 Child({ weather: this.day1.weather}) 2087 } 2088 .height('100%') 2089 .width('100%') 2090 } 2091} 2092 2093@Component 2094struct Child { 2095 @ObjectLink weather: Weather; 2096 2097 reduceTemperature (weather:Weather) { 2098 weather.temperature--; 2099 } 2100 2101 build() { 2102 Column({ space:10 }) { 2103 Text(`The temperature of day1 is ${this.weather.temperature} degrees.`) 2104 .fontSize(20) 2105 Button('increaseTemperature') 2106 .onClick(()=>{ 2107 // Add a proxy by assigning a value. 2108 let weather1 = this.weather; 2109 Weather.increaseTemperature(weather1); 2110 }) 2111 Button('reduceTemperature') 2112 .onClick(()=>{ 2113 // Add a proxy by assigning a value. 2114 let weather2 = this.weather; 2115 this.reduceTemperature(weather2); 2116 }) 2117 } 2118 .height('100%') 2119 .width('100%') 2120 } 2121} 2122``` 2123 2124<!--no_check-->