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