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