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. The type must be specified.<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 an explicit class decorated by @Observed. If the type is not 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, **Bag** is decorated by \@Observed, and the **name** property of **Bag** 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 constructor(args: T[]) { 622 super(...args); 623 } 624} 625``` 626 627Declare 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. 628 629The following example shows how to use \@Observed to observe the changes of a two-dimensional array. 630 631```ts 632@Observed 633class ObservedArray<T> extends Array<T> { 634 constructor(args: T[]) { 635 super(...args); 636 } 637} 638 639@Component 640struct Item { 641 @ObjectLink itemArr: ObservedArray<string>; 642 643 build() { 644 Row() { 645 ForEach(this.itemArr, (item: string, index: number) => { 646 Text(`${index}: ${item}`) 647 .width(100) 648 .height(100) 649 }, (item: string) => item) 650 } 651 } 652} 653 654@Entry 655@Component 656struct IndexPage { 657 @State arr: Array<ObservedArray<string>> = [new ObservedArray<string>(['apple']), new ObservedArray<string>(['banana']), new ObservedArray<string>(['orange'])]; 658 659 build() { 660 Column() { 661 ForEach(this.arr, (itemArr: ObservedArray<string>) => { 662 Item({ itemArr: itemArr }) 663 }) 664 665 Divider() 666 667 Button('push two-dimensional array item') 668 .margin(10) 669 .onClick(() => { 670 this.arr[0].push('strawberry'); 671 }) 672 673 Button('push array item') 674 .margin(10) 675 .onClick(() => { 676 this.arr.push(new ObservedArray<string>(['pear'])); 677 }) 678 679 Button('change two-dimensional array first item') 680 .margin(10) 681 .onClick(() => { 682 this.arr[0][0] = 'APPLE'; 683 }) 684 685 Button('change array first item') 686 .margin(10) 687 .onClick(() => { 688 this.arr[0] = new ObservedArray<string>(['watermelon']); 689 }) 690 } 691 } 692} 693``` 694 695 696 697### Extended Map Class 698 699> **NOTE** 700> 701> Since API version 11, \@ObjectLink supports @Observed decorated classes extending from **Map** and the Map type. 702 703In 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. 704 705```ts 706@Observed 707class Info { 708 public info: MyMap<number, string>; 709 710 constructor(info: MyMap<number, string>) { 711 this.info = info; 712 } 713} 714 715 716@Observed 717export class MyMap<K, V> extends Map<K, V> { 718 public name: string; 719 720 constructor(name?: string, args?: [K, V][]) { 721 super(args); 722 this.name = name ? name : "My Map"; 723 } 724 725 getName() { 726 return this.name; 727 } 728} 729 730@Entry 731@Component 732struct MapSampleNested { 733 @State message: Info = new Info(new MyMap("myMap", [[0, "a"], [1, "b"], [3, "c"]])); 734 735 build() { 736 Row() { 737 Column() { 738 MapSampleNestedChild({ myMap: this.message.info }) 739 } 740 .width('100%') 741 } 742 .height('100%') 743 } 744} 745 746@Component 747struct MapSampleNestedChild { 748 @ObjectLink myMap: MyMap<number, string>; 749 750 build() { 751 Row() { 752 Column() { 753 ForEach(Array.from(this.myMap.entries()), (item: [number, string]) => { 754 Text(`${item[0]}`).fontSize(30) 755 Text(`${item[1]}`).fontSize(30) 756 Divider().strokeWidth(5) 757 }) 758 759 Button('set new one') 760 .width(200) 761 .margin(10) 762 .onClick(() => { 763 this.myMap.set(4, "d"); 764 }) 765 Button('clear') 766 .width(200) 767 .margin(10) 768 .onClick(() => { 769 this.myMap.clear(); 770 }) 771 Button('replace the first one') 772 .width(200) 773 .margin(10) 774 .onClick(() => { 775 this.myMap.set(0, "aa"); 776 }) 777 Button('delete the first one') 778 .width(200) 779 .margin(10) 780 .onClick(() => { 781 this.myMap.delete(0); 782 }) 783 } 784 .width('100%') 785 } 786 .height('100%') 787 } 788} 789``` 790 791 792 793### Extended Set Class 794 795> **NOTE** 796> 797> Since API version 11, \@ObjectLink supports @Observed decorated classes extending from **Set** and the Set type. 798 799In 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. 800 801```ts 802@Observed 803class Info { 804 public info: MySet<number>; 805 806 constructor(info: MySet<number>) { 807 this.info = info; 808 } 809} 810 811 812@Observed 813export class MySet<T> extends Set<T> { 814 public name: string; 815 816 constructor(name?: string, args?: T[]) { 817 super(args); 818 this.name = name ? name : "My Set"; 819 } 820 821 getName() { 822 return this.name; 823 } 824} 825 826@Entry 827@Component 828struct SetSampleNested { 829 @State message: Info = new Info(new MySet("Set", [0, 1, 2, 3, 4])); 830 831 build() { 832 Row() { 833 Column() { 834 SetSampleNestedChild({ mySet: this.message.info }) 835 } 836 .width('100%') 837 } 838 .height('100%') 839 } 840} 841 842@Component 843struct SetSampleNestedChild { 844 @ObjectLink mySet: MySet<number>; 845 846 build() { 847 Row() { 848 Column() { 849 ForEach(Array.from(this.mySet.entries()), (item: [number, number]) => { 850 Text(`${item}`).fontSize(30) 851 Divider() 852 }) 853 Button('set new one') 854 .width(200) 855 .margin(10) 856 .onClick(() => { 857 this.mySet.add(5); 858 }) 859 Button('clear') 860 .width(200) 861 .margin(10) 862 .onClick(() => { 863 this.mySet.clear(); 864 }) 865 Button('delete the first one') 866 .width(200) 867 .margin(10) 868 .onClick(() => { 869 this.mySet.delete(0); 870 }) 871 } 872 .width('100%') 873 } 874 .height('100%') 875 } 876} 877``` 878 879 880 881## Union Type @ObjectLink 882 883@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. 884 885```ts 886@Observed 887class Source { 888 public source: number; 889 890 constructor(source: number) { 891 this.source = source; 892 } 893} 894 895@Observed 896class Data { 897 public data: number; 898 899 constructor(data: number) { 900 this.data = data; 901 } 902} 903 904@Entry 905@Component 906struct Parent { 907 @State count: Source | Data | undefined = new Source(10); 908 909 build() { 910 Column() { 911 Child({ count: this.count }) 912 913 Button('change count property') 914 .margin(10) 915 .onClick(() => { 916 // Determine the count type and update the property. 917 if (this.count instanceof Source) { 918 this.count.source += 1; 919 } else if (this.count instanceof Data) { 920 this.count.data += 1; 921 } else { 922 console.info('count is undefined, cannot change property'); 923 } 924 }) 925 926 Button('change count to Source') 927 .margin(10) 928 .onClick(() => { 929 // Assign the value of an instance of Source. 930 this.count = new Source(100); 931 }) 932 933 Button('change count to Data') 934 .margin(10) 935 .onClick(() => { 936 // Assign the value of an instance of Data. 937 this.count = new Data(100); 938 }) 939 940 Button('change count to undefined') 941 .margin(10) 942 .onClick(() => { 943 // Assign the value undefined. 944 this.count = undefined; 945 }) 946 }.width('100%') 947 } 948} 949 950@Component 951struct Child { 952 @ObjectLink count: Source | Data | undefined; 953 954 build() { 955 Column() { 956 Text(`count is instanceof ${this.count instanceof Source ? 'Source' : 957 this.count instanceof Data ? 'Data' : 'undefined'}`) 958 .fontSize(30) 959 .margin(10) 960 961 Text(`count's property is ${this.count instanceof Source ? this.count.source : this.count?.data}`).fontSize(15) 962 963 }.width('100%') 964 } 965} 966``` 967 968 969 970## FAQs 971 972### Assigning Value to @ObjectLink Decorated Variable in Child Component 973 974It is not allowed to assign a value to an @ObjectLink decorated variable in the child component. 975 976[Incorrect Usage] 977 978```ts 979@Observed 980class Info { 981 public info: number = 0; 982 983 constructor(info: number) { 984 this.info = info; 985 } 986} 987 988@Component 989struct ObjectLinkChild { 990 @ObjectLink testNum: Info; 991 992 build() { 993 Text(`ObjectLinkChild testNum ${this.testNum.info}`) 994 .onClick(() => { 995 // The @ObjectLink decorated variable cannot be assigned a value here. 996 this.testNum = new Info(47); 997 }) 998 } 999} 1000 1001@Entry 1002@Component 1003struct Parent { 1004 @State testNum: Info[] = [new Info(1)]; 1005 1006 build() { 1007 Column() { 1008 Text(`Parent testNum ${this.testNum[0].info}`) 1009 .onClick(() => { 1010 this.testNum[0].info += 1; 1011 }) 1012 1013 ObjectLinkChild({ testNum: this.testNum[0] }) 1014 } 1015 } 1016} 1017``` 1018 1019In this example, an attempt is made to assign a value to the @ObjectLink decorated variable by clicking **ObjectLinkChild**. 1020 1021``` 1022this.testNum = new Info(47); 1023``` 1024 1025This 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. 1026 1027[Correct Usage] 1028 1029```ts 1030@Observed 1031class Info { 1032 public info: number = 0; 1033 1034 constructor(info: number) { 1035 this.info = info; 1036 } 1037} 1038 1039@Component 1040struct ObjectLinkChild { 1041 @ObjectLink testNum: Info; 1042 1043 build() { 1044 Text(`ObjectLinkChild testNum ${this.testNum.info}`) 1045 .onClick(() => { 1046 // You can assign values to the properties of the ObjectLink decorated object. 1047 this.testNum.info = 47; 1048 }) 1049 } 1050} 1051 1052@Entry 1053@Component 1054struct Parent { 1055 @State testNum: Info[] = [new Info(1)]; 1056 1057 build() { 1058 Column() { 1059 Text(`Parent testNum ${this.testNum[0].info}`) 1060 .onClick(() => { 1061 this.testNum[0].info += 1; 1062 }) 1063 1064 ObjectLinkChild({ testNum: this.testNum[0] }) 1065 } 1066 } 1067} 1068``` 1069 1070### UI Not Updated on property Changes in Simple Nested Objects 1071 1072If 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. 1073 1074Each 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. 1075 1076[Incorrect Usage] 1077 1078In the following example, some UI components are not updated. 1079 1080 1081```ts 1082class Parent { 1083 parentId: number; 1084 1085 constructor(parentId: number) { 1086 this.parentId = parentId; 1087 } 1088 1089 getParentId(): number { 1090 return this.parentId; 1091 } 1092 1093 setParentId(parentId: number): void { 1094 this.parentId = parentId; 1095 } 1096} 1097 1098class Child { 1099 childId: number; 1100 1101 constructor(childId: number) { 1102 this.childId = childId; 1103 } 1104 1105 getChildId(): number { 1106 return this.childId; 1107 } 1108 1109 setChildId(childId: number): void { 1110 this.childId = childId; 1111 } 1112} 1113 1114class Cousin extends Parent { 1115 cousinId: number = 47; 1116 child: Child; 1117 1118 constructor(parentId: number, cousinId: number, childId: number) { 1119 super(parentId); 1120 this.cousinId = cousinId; 1121 this.child = new Child(childId); 1122 } 1123 1124 getCousinId(): number { 1125 return this.cousinId; 1126 } 1127 1128 setCousinId(cousinId: number): void { 1129 this.cousinId = cousinId; 1130 } 1131 1132 getChild(): number { 1133 return this.child.getChildId(); 1134 } 1135 1136 setChild(childId: number): void { 1137 return this.child.setChildId(childId); 1138 } 1139} 1140 1141@Entry 1142@Component 1143struct MyView { 1144 @State cousin: Cousin = new Cousin(10, 20, 30); 1145 1146 build() { 1147 Column({ space: 10 }) { 1148 Text(`parentId: ${this.cousin.parentId}`) 1149 Button("Change Parent.parent") 1150 .onClick(() => { 1151 this.cousin.parentId += 1; 1152 }) 1153 1154 Text(`cousinId: ${this.cousin.cousinId}`) 1155 Button("Change Cousin.cousinId") 1156 .onClick(() => { 1157 this.cousin.cousinId += 1; 1158 }) 1159 1160 Text(`childId: ${this.cousin.child.childId}`) 1161 Button("Change Cousin.Child.childId") 1162 .onClick(() => { 1163 // The Text component is not updated when clicked. 1164 this.cousin.child.childId += 1; 1165 }) 1166 } 1167 } 1168} 1169``` 1170 1171- 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**). 1172 1173- To observe the properties of nested object **Child**, you need to make the following changes: 1174 - 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. 1175 - 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. 1176 1177[Correct Usage] 1178 1179The following example uses \@Observed/\@ObjectLink to observe property changes for nested objects. 1180 1181 1182```ts 1183class Parent { 1184 parentId: number; 1185 1186 constructor(parentId: number) { 1187 this.parentId = parentId; 1188 } 1189 1190 getParentId(): number { 1191 return this.parentId; 1192 } 1193 1194 setParentId(parentId: number): void { 1195 this.parentId = parentId; 1196 } 1197} 1198 1199@Observed 1200class Child { 1201 childId: number; 1202 1203 constructor(childId: number) { 1204 this.childId = childId; 1205 } 1206 1207 getChildId(): number { 1208 return this.childId; 1209 } 1210 1211 setChildId(childId: number): void { 1212 this.childId = childId; 1213 } 1214} 1215 1216class Cousin extends Parent { 1217 cousinId: number = 47; 1218 child: Child; 1219 1220 constructor(parentId: number, cousinId: number, childId: number) { 1221 super(parentId); 1222 this.cousinId = cousinId; 1223 this.child = new Child(childId); 1224 } 1225 1226 getCousinId(): number { 1227 return this.cousinId; 1228 } 1229 1230 setCousinId(cousinId: number): void { 1231 this.cousinId = cousinId; 1232 } 1233 1234 getChild(): number { 1235 return this.child.getChildId(); 1236 } 1237 1238 setChild(childId: number): void { 1239 return this.child.setChildId(childId); 1240 } 1241} 1242 1243@Component 1244struct ViewChild { 1245 @ObjectLink child: Child; 1246 1247 build() { 1248 Column({ space: 10 }) { 1249 Text(`childId: ${this.child.getChildId()}`) 1250 Button("Change childId") 1251 .onClick(() => { 1252 this.child.setChildId(this.child.getChildId() + 1); 1253 }) 1254 } 1255 } 1256} 1257 1258@Entry 1259@Component 1260struct MyView { 1261 @State cousin: Cousin = new Cousin(10, 20, 30); 1262 1263 build() { 1264 Column({ space: 10 }) { 1265 Text(`parentId: ${this.cousin.parentId}`) 1266 Button("Change Parent.parentId") 1267 .onClick(() => { 1268 this.cousin.parentId += 1; 1269 }) 1270 1271 Text(`cousinId: ${this.cousin.cousinId}`) 1272 Button("Change Cousin.cousinId") 1273 .onClick(() => { 1274 this.cousin.cousinId += 1; 1275 }) 1276 1277 ViewChild({ child: this.cousin.child }) // Alternative format of Text(`childId: ${this.cousin.child.childId}`). 1278 Button("Change Cousin.Child.childId") 1279 .onClick(() => { 1280 this.cousin.child.childId += 1; 1281 }) 1282 } 1283 } 1284} 1285``` 1286 1287### UI Not Updated on property Changes in Complex Nested Objects 1288 1289[Incorrect Usage] 1290 1291The 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. 1292 1293 1294```ts 1295let nextId = 1; 1296@Observed 1297class SubCounter { 1298 counter: number; 1299 constructor(c: number) { 1300 this.counter = c; 1301 } 1302} 1303@Observed 1304class ParentCounter { 1305 id: number; 1306 counter: number; 1307 subCounter: SubCounter; 1308 incrCounter() { 1309 this.counter++; 1310 } 1311 incrSubCounter(c: number) { 1312 this.subCounter.counter += c; 1313 } 1314 setSubCounter(c: number): void { 1315 this.subCounter.counter = c; 1316 } 1317 constructor(c: number) { 1318 this.id = nextId++; 1319 this.counter = c; 1320 this.subCounter = new SubCounter(c); 1321 } 1322} 1323@Component 1324struct CounterComp { 1325 @ObjectLink value: ParentCounter; 1326 build() { 1327 Column({ space: 10 }) { 1328 Text(`${this.value.counter}`) 1329 .fontSize(25) 1330 .onClick(() => { 1331 this.value.incrCounter(); 1332 }) 1333 Text(`${this.value.subCounter.counter}`) 1334 .onClick(() => { 1335 this.value.incrSubCounter(1); 1336 }) 1337 Divider().height(2) 1338 } 1339 } 1340} 1341@Entry 1342@Component 1343struct ParentComp { 1344 @State counter: ParentCounter[] = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)]; 1345 build() { 1346 Row() { 1347 Column() { 1348 CounterComp({ value: this.counter[0] }) 1349 CounterComp({ value: this.counter[1] }) 1350 CounterComp({ value: this.counter[2] }) 1351 Divider().height(5) 1352 ForEach(this.counter, 1353 (item: ParentCounter) => { 1354 CounterComp({ value: item }) 1355 }, 1356 (item: ParentCounter) => item.id.toString() 1357 ) 1358 Divider().height(5) 1359 // First click event 1360 Text('Parent: incr counter[0].counter') 1361 .fontSize(20).height(50) 1362 .onClick(() => { 1363 this.counter[0].incrCounter(); 1364 // The value increases by 10 each time the event is triggered. 1365 this.counter[0].incrSubCounter(10); 1366 }) 1367 // Second click event 1368 Text('Parent: set.counter to 10') 1369 .fontSize(20).height(50) 1370 .onClick(() => { 1371 // The value cannot be set to 10, and the UI is not updated. 1372 this.counter[0].setSubCounter(10); 1373 }) 1374 Text('Parent: reset entire counter') 1375 .fontSize(20).height(50) 1376 .onClick(() => { 1377 this.counter = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)]; 1378 }) 1379 } 1380 } 1381 } 1382} 1383``` 1384 1385For 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. 1386 1387However, 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**. 1388 1389**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. 1390 1391However, 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. 1392 1393[Correct Usage] 1394 1395To 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: 1396 1397 1398```ts 1399CounterComp({ value: this.counter[0] }); // ParentComp passes ParentCounter to CounterComp. 1400@ObjectLink value: ParentCounter; // @ObjectLink receives ParentCounter. 1401 1402CounterChild({ subValue: this.value.subCounter }); // CounterComp passes SubCounter to CounterChild 1403@ObjectLink subValue: SubCounter; // @ObjectLink receives SubCounter. 1404``` 1405 1406This 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. 1407 1408This 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). 1409 1410 1411```ts 1412let nextId = 1; 1413 1414@Observed 1415class SubCounter { 1416 counter: number; 1417 1418 constructor(c: number) { 1419 this.counter = c; 1420 } 1421} 1422 1423@Observed 1424class ParentCounter { 1425 id: number; 1426 counter: number; 1427 subCounter: SubCounter; 1428 1429 incrCounter() { 1430 this.counter++; 1431 } 1432 1433 incrSubCounter(c: number) { 1434 this.subCounter.counter += c; 1435 } 1436 1437 setSubCounter(c: number): void { 1438 this.subCounter.counter = c; 1439 } 1440 1441 constructor(c: number) { 1442 this.id = nextId++; 1443 this.counter = c; 1444 this.subCounter = new SubCounter(c); 1445 } 1446} 1447 1448@Component 1449struct CounterComp { 1450 @ObjectLink value: ParentCounter; 1451 1452 build() { 1453 Column({ space: 10 }) { 1454 Text(`${this.value.counter}`) 1455 .fontSize(25) 1456 .onClick(() => { 1457 this.value.incrCounter(); 1458 }) 1459 CounterChild({ subValue: this.value.subCounter }) 1460 Divider().height(2) 1461 } 1462 } 1463} 1464 1465@Component 1466struct CounterChild { 1467 @ObjectLink subValue: SubCounter; 1468 1469 build() { 1470 Text(`${this.subValue.counter}`) 1471 .onClick(() => { 1472 this.subValue.counter += 1; 1473 }) 1474 } 1475} 1476 1477@Entry 1478@Component 1479struct ParentComp { 1480 @State counter: ParentCounter[] = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)]; 1481 1482 build() { 1483 Row() { 1484 Column() { 1485 CounterComp({ value: this.counter[0] }) 1486 CounterComp({ value: this.counter[1] }) 1487 CounterComp({ value: this.counter[2] }) 1488 Divider().height(5) 1489 ForEach(this.counter, 1490 (item: ParentCounter) => { 1491 CounterComp({ value: item }) 1492 }, 1493 (item: ParentCounter) => item.id.toString() 1494 ) 1495 Divider().height(5) 1496 Text('Parent: reset entire counter') 1497 .fontSize(20).height(50) 1498 .onClick(() => { 1499 this.counter = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)]; 1500 }) 1501 Text('Parent: incr counter[0].counter') 1502 .fontSize(20).height(50) 1503 .onClick(() => { 1504 this.counter[0].incrCounter(); 1505 this.counter[0].incrSubCounter(10); 1506 }) 1507 Text('Parent: set.counter to 10') 1508 .fontSize(20).height(50) 1509 .onClick(() => { 1510 this.counter[0].setSubCounter(10); 1511 }) 1512 } 1513 } 1514 } 1515} 1516``` 1517 1518### Differences Between \@Prop and \@ObjectLink 1519 1520In 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. 1521 1522 1523```ts 1524let nextId = 1; 1525 1526@Observed 1527class SubCounter { 1528 counter: number; 1529 1530 constructor(c: number) { 1531 this.counter = c; 1532 } 1533} 1534 1535@Observed 1536class ParentCounter { 1537 id: number; 1538 counter: number; 1539 subCounter: SubCounter; 1540 1541 incrCounter() { 1542 this.counter++; 1543 } 1544 1545 incrSubCounter(c: number) { 1546 this.subCounter.counter += c; 1547 } 1548 1549 setSubCounter(c: number): void { 1550 this.subCounter.counter = c; 1551 } 1552 1553 constructor(c: number) { 1554 this.id = nextId++; 1555 this.counter = c; 1556 this.subCounter = new SubCounter(c); 1557 } 1558} 1559 1560@Component 1561struct CounterComp { 1562 @ObjectLink value: ParentCounter; 1563 1564 build() { 1565 Column({ space: 10 }) { 1566 CountChild({ subValue: this.value.subCounter }) 1567 Text(`this.value.counter: increase 7 `) 1568 .fontSize(30) 1569 .onClick(() => { 1570 // Text(`this.subValue.counter: ${this.subValue.counter}`) is re-rendered after clicking. 1571 this.value.incrSubCounter(7); 1572 }) 1573 Divider().height(2) 1574 } 1575 } 1576} 1577 1578@Component 1579struct CountChild { 1580 @ObjectLink subValue: SubCounter; 1581 1582 build() { 1583 Text(`this.subValue.counter: ${this.subValue.counter}`) 1584 .fontSize(30) 1585 } 1586} 1587 1588@Entry 1589@Component 1590struct ParentComp { 1591 @State counter: ParentCounter[] = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)]; 1592 1593 build() { 1594 Row() { 1595 Column() { 1596 CounterComp({ value: this.counter[0] }) 1597 CounterComp({ value: this.counter[1] }) 1598 CounterComp({ value: this.counter[2] }) 1599 Divider().height(5) 1600 ForEach(this.counter, 1601 (item: ParentCounter) => { 1602 CounterComp({ value: item }) 1603 }, 1604 (item: ParentCounter) => item.id.toString() 1605 ) 1606 Divider().height(5) 1607 Text('Parent: reset entire counter') 1608 .fontSize(20).height(50) 1609 .onClick(() => { 1610 this.counter = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)]; 1611 }) 1612 Text('Parent: incr counter[0].counter') 1613 .fontSize(20).height(50) 1614 .onClick(() => { 1615 this.counter[0].incrCounter(); 1616 this.counter[0].incrSubCounter(10); 1617 }) 1618 Text('Parent: set.counter to 10') 1619 .fontSize(20).height(50) 1620 .onClick(() => { 1621 this.counter[0].setSubCounter(10); 1622 }) 1623 } 1624 } 1625 } 1626} 1627``` 1628 1629The following figure shows how \@ObjectLink works. 1630 1631 1632 1633[Incorrect Usage] 1634 1635\@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. 1636 1637 **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. 1638 1639```ts 1640@Component 1641struct CounterComp { 1642 @Prop value: ParentCounter = new ParentCounter(0); 1643 @Prop subValue: SubCounter = new SubCounter(0); 1644 build() { 1645 Column({ space: 10 }) { 1646 Text(`this.subValue.counter: ${this.subValue.counter}`) 1647 .fontSize(20) 1648 .onClick(() => { 1649 this.subValue.counter += 7; 1650 }) 1651 Text(`this.value.counter: increase 7 `) 1652 .fontSize(20) 1653 .onClick(() => { 1654 this.value.incrSubCounter(7); 1655 }) 1656 Divider().height(2) 1657 } 1658 } 1659} 1660``` 1661 1662The following figure shows how \@Prop works. 1663 1664 1665 1666[Correct Usage] 1667 1668Make only one copy of \@Prop value: ParentCounter from **ParentComp** to **CounterComp**. Do not make another copy of **SubCounter**. 1669 1670- Use only one \@Prop **counter: Counter** in the **CounterComp** component. 1671 1672- 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. 1673 1674- \@ObjectLink **subCounter: SubCounter** shares the same **SubCounter** object with **this.counter.subCounter** of \@Prop **counter: Counter** in **CounterComp**. 1675 1676 1677 1678```ts 1679let nextId = 1; 1680 1681@Observed 1682class SubCounter { 1683 counter: number; 1684 constructor(c: number) { 1685 this.counter = c; 1686 } 1687} 1688 1689@Observed 1690class ParentCounter { 1691 id: number; 1692 counter: number; 1693 subCounter: SubCounter; 1694 incrCounter() { 1695 this.counter++; 1696 } 1697 incrSubCounter(c: number) { 1698 this.subCounter.counter += c; 1699 } 1700 setSubCounter(c: number): void { 1701 this.subCounter.counter = c; 1702 } 1703 constructor(c: number) { 1704 this.id = nextId++; 1705 this.counter = c; 1706 this.subCounter = new SubCounter(c); 1707 } 1708} 1709 1710@Component 1711struct SubCounterComp { 1712 @ObjectLink subValue: SubCounter; 1713 build() { 1714 Text(`SubCounterComp: this.subValue.counter: ${this.subValue.counter}`) 1715 .onClick(() => { 1716 this.subValue.counter = 7; 1717 }) 1718 } 1719} 1720@Component 1721struct CounterComp { 1722 @Prop value: ParentCounter; 1723 build() { 1724 Column({ space: 10 }) { 1725 Text(`this.value.incrCounter(): this.value.counter: ${this.value.counter}`) 1726 .fontSize(20) 1727 .onClick(() => { 1728 this.value.incrCounter(); 1729 }) 1730 SubCounterComp({ subValue: this.value.subCounter }) 1731 Text(`this.value.incrSubCounter()`) 1732 .onClick(() => { 1733 this.value.incrSubCounter(77); 1734 }) 1735 Divider().height(2) 1736 } 1737 } 1738} 1739@Entry 1740@Component 1741struct ParentComp { 1742 @State counter: ParentCounter[] = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)]; 1743 build() { 1744 Row() { 1745 Column() { 1746 CounterComp({ value: this.counter[0] }) 1747 CounterComp({ value: this.counter[1] }) 1748 CounterComp({ value: this.counter[2] }) 1749 Divider().height(5) 1750 ForEach(this.counter, 1751 (item: ParentCounter) => { 1752 CounterComp({ value: item }) 1753 }, 1754 (item: ParentCounter) => item.id.toString() 1755 ) 1756 Divider().height(5) 1757 Text('Parent: reset entire counter') 1758 .fontSize(20).height(50) 1759 .onClick(() => { 1760 this.counter = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)]; 1761 }) 1762 Text('Parent: incr counter[0].counter') 1763 .fontSize(20).height(50) 1764 .onClick(() => { 1765 this.counter[0].incrCounter(); 1766 this.counter[0].incrSubCounter(10); 1767 }) 1768 Text('Parent: set.counter to 10') 1769 .fontSize(20).height(50) 1770 .onClick(() => { 1771 this.counter[0].setSubCounter(10); 1772 }) 1773 } 1774 } 1775 } 1776} 1777``` 1778 1779 1780The following figure shows the copy relationship. 1781 1782 1783 1784 1785### Member Variable Changes in the @Observed Decorated Class Constructor Not Taking Effect 1786 1787In 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. 1788 1789If 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. 1790 1791[Incorrect Usage] 1792 1793```ts 1794@Observed 1795class RenderClass { 1796 waitToRender: boolean = false; 1797 1798 constructor() { 1799 setTimeout(() => { 1800 this.waitToRender = true; 1801 console.log("Change the value of waitToRender to" + this.waitToRender); 1802 }, 1000) 1803 } 1804} 1805 1806@Entry 1807@Component 1808struct Index { 1809 @State @Watch('renderClassChange') renderClass: RenderClass = new RenderClass(); 1810 @State textColor: Color = Color.Black; 1811 1812 renderClassChange() { 1813 console.log("The value of renderClass is changed to" + this.renderClass.waitToRender); 1814 } 1815 1816 build() { 1817 Row() { 1818 Column() { 1819 Text("The value of renderClass is" + this.renderClass.waitToRender) 1820 .fontSize(20) 1821 .fontColor(this.textColor) 1822 Button("Show") 1823 .onClick(() => { 1824 // 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. 1825 this.textColor = Color.Red; 1826 }) 1827 } 1828 .width('100%') 1829 } 1830 .height('100%') 1831 } 1832} 1833``` 1834 1835In 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**. 1836 1837[Correct Usage] 1838 1839```ts 1840@Observed 1841class RenderClass { 1842 waitToRender: boolean = false; 1843 1844 constructor() { 1845 } 1846} 1847 1848@Entry 1849@Component 1850struct Index { 1851 @State @Watch('renderClassChange') renderClass: RenderClass = new RenderClass(); 1852 1853 renderClassChange() { 1854 console.log("The value of renderClass is changed to" + this.renderClass.waitToRender); 1855 } 1856 1857 onPageShow() { 1858 setTimeout(() => { 1859 this.renderClass.waitToRender = true; 1860 console.log("Change the value of renderClass to" + this.renderClass.waitToRender); 1861 }, 1000) 1862 } 1863 1864 build() { 1865 Row() { 1866 Column() { 1867 Text("The value of renderClass is" + this.renderClass.waitToRender) 1868 .fontSize(20) 1869 } 1870 .width('100%') 1871 } 1872 .height('100%') 1873 } 1874} 1875``` 1876 1877In 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". 1878 1879In sum, it is recommended that you change the class members decorated by @Observed in components to implement UI re-rendering. 1880 1881### \@ObjectLink Data Source Update Timing 1882 1883```ts 1884@Observed 1885class Person { 1886 name: string = ''; 1887 age: number = 0; 1888 1889 constructor(name: string, age: number) { 1890 this.name = name; 1891 this.age = age; 1892 } 1893} 1894 1895@Observed 1896class Info { 1897 person: Person; 1898 1899 constructor(person: Person) { 1900 this.person = person; 1901 } 1902} 1903 1904@Entry 1905@Component 1906struct Parent { 1907 @State @Watch('onChange01') info: Info = new Info(new Person('Bob', 10)); 1908 1909 onChange01() { 1910 console.log(':::onChange01:' + this.info.person.name); // 2 1911 } 1912 1913 build() { 1914 Column() { 1915 Text(this.info.person.name).height(40) 1916 Child({ 1917 per: this.info.person, clickEvent: () => { 1918 console.log(':::clickEvent before', this.info.person.name); // 1 1919 this.info.person = new Person('Jack', 12); 1920 console.log(':::clickEvent after', this.info.person.name); // 3 1921 } 1922 }) 1923 } 1924 } 1925} 1926 1927@Component 1928struct Child { 1929 @ObjectLink @Watch('onChange02') per: Person; 1930 clickEvent?: () => void; 1931 1932 onChange02() { 1933 console.log(':::onChange02:' + this.per.name); // 5 1934 } 1935 1936 build() { 1937 Column() { 1938 Button(this.per.name) 1939 .height(40) 1940 .onClick(() => { 1941 this.onClickType(); 1942 }) 1943 } 1944 } 1945 1946 private onClickType() { 1947 if (this.clickEvent) { 1948 this.clickEvent(); 1949 } 1950 console.log(':::-------- this.per.name in Child is still:' + this.per.name); // 4 1951 } 1952} 1953``` 1954 1955The 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. 1956 1957When 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**. 1958 1959The meaning of the log is as follows: 1960- Log 1: Before a value is assigned to **Parent @State @Watch('onChange01') info: Info = new Info(new Person('Bob', 10))**. 1961 1962- Log 2: Assign a value to **Parent @State @Watch('onChange01') info: Info = new Info(new Person('Bob', 10))** and execute its \@Watch function synchronously. 1963 1964- Log 3: A value is assigned to **Parent @State @Watch('onChange01') info: Info = new Info(new Person('Bob', 10))**. 1965 1966- 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**. 1967 1968- 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**. 1969 1970The parent-child synchronization principle of \@Prop is the same as that of \@ObjectLink. 1971 1972When **this.info.person.name** is changed in **clickEvent**, this change takes effect immediately. In this case, the value of log 4 is **Jack**. 1973 1974```ts 1975Child({ 1976 per: this.info.person, clickEvent: () => { 1977 console.log(':::clickEvent before', this.info.person.name); // 1 1978 this.info.person.name = 'Jack'; 1979 console.log(':::clickEvent after', this.info.person.name); // 3 1980 } 1981}) 1982``` 1983 1984The **Text** component in **Parent** is not re-rendered because **this.info.person.name** is a value with two-layer nesting. 1985 1986### Using the a.b(this.object) Format Fails to Trigger UI Re-render 1987 1988In 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 native 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. 1989 1990[Incorrect Usage] 1991 1992```ts 1993@Observed 1994class Weather { 1995 temperature:number; 1996 1997 constructor(temperature:number) { 1998 this.temperature = temperature; 1999 } 2000 2001 static increaseTemperature(weather:Weather) { 2002 weather.temperature++; 2003 } 2004} 2005 2006class Day { 2007 weather:Weather; 2008 week:string; 2009 constructor(weather:Weather, week:string) { 2010 this.weather = weather; 2011 this.week = week; 2012 } 2013} 2014 2015@Entry 2016@Component 2017struct Parent { 2018 @State day1: Day = new Day(new Weather(15), 'Monday'); 2019 2020 build() { 2021 Column({ space:10 }) { 2022 Child({ weather: this.day1.weather}) 2023 } 2024 .height('100%') 2025 .width('100%') 2026 } 2027} 2028 2029@Component 2030struct Child { 2031 @ObjectLink weather: Weather; 2032 2033 reduceTemperature (weather:Weather) { 2034 weather.temperature--; 2035 } 2036 2037 build() { 2038 Column({ space:10 }) { 2039 Text(`The temperature of day1 is ${this.weather.temperature} degrees.`) 2040 .fontSize(20) 2041 Button('increaseTemperature') 2042 .onClick(()=>{ 2043 // The UI cannot be re-rendered using a static method. 2044 Weather.increaseTemperature(this.weather); 2045 }) 2046 Button('reduceTemperature') 2047 .onClick(()=>{ 2048 // The UI cannot be re-rendered using this. 2049 this.reduceTemperature(this.weather); 2050 }) 2051 } 2052 .height('100%') 2053 .width('100%') 2054 } 2055} 2056``` 2057 2058You can add a proxy for **this.weather** to re-render the UI by assigning a value to the variable and then calling the variable. 2059 2060[Correct Usage] 2061 2062```ts 2063@Observed 2064class Weather { 2065 temperature:number; 2066 2067 constructor(temperature:number) { 2068 this.temperature = temperature; 2069 } 2070 2071 static increaseTemperature(weather:Weather) { 2072 weather.temperature++; 2073 } 2074} 2075 2076class Day { 2077 weather:Weather; 2078 week:string; 2079 constructor(weather:Weather, week:string) { 2080 this.weather = weather; 2081 this.week = week; 2082 } 2083} 2084 2085@Entry 2086@Component 2087struct Parent { 2088 @State day1: Day = new Day(new Weather(15), 'Monday'); 2089 2090 build() { 2091 Column({ space:10 }) { 2092 Child({ weather: this.day1.weather}) 2093 } 2094 .height('100%') 2095 .width('100%') 2096 } 2097} 2098 2099@Component 2100struct Child { 2101 @ObjectLink weather: Weather; 2102 2103 reduceTemperature (weather:Weather) { 2104 weather.temperature--; 2105 } 2106 2107 build() { 2108 Column({ space:10 }) { 2109 Text(`The temperature of day1 is ${this.weather.temperature} degrees.`) 2110 .fontSize(20) 2111 Button('increaseTemperature') 2112 .onClick(()=>{ 2113 // Add a proxy by assigning a value. 2114 let weather1 = this.weather; 2115 Weather.increaseTemperature(weather1); 2116 }) 2117 Button('reduceTemperature') 2118 .onClick(()=>{ 2119 // Add a proxy by assigning a value. 2120 let weather2 = this.weather; 2121 this.reduceTemperature(weather2); 2122 }) 2123 } 2124 .height('100%') 2125 .width('100%') 2126 } 2127} 2128``` 2129