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