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