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