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