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![en-us_image_0000001502255262](figures/en-us_image_0000001502255262.png)
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![Observed_ObjectLink_nested_object](figures/Observed_ObjectLink_nested_object.gif)
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![Observed_ObjectLink_object_array](figures/Observed_ObjectLink_object_array.gif)
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![Observed_ObjectLink_2D_array](figures/Observed_ObjectLink_2D_array.gif)
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![Observed_ObjectLink_inherit_map](figures/Observed_ObjectLink_inherit_map.gif)
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![Observed_ObjectLink_inherit_set](figures/Observed_ObjectLink_inherit_set.gif)
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![ObjectLink-support-union-types](figures/ObjectLink-support-union-types.gif)
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![en-us_image_0000001651665921](figures/en-us_image_0000001651665921.png)
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![en-us_image_0000001602146116](figures/en-us_image_0000001602146116.png)
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![en-us_image_0000001653949465](figures/en-us_image_0000001653949465.png)
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