• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# MVVM
2
3
4Rendering or re-rendering the UI based on state – a collection of arrays, objects, or nested objects – is complex, but critical to application performance. In ArkUI, the Model-View-View Model (MVVM) pattern is leveraged for state management. The state management module functions as the view model to bind data to views. When data is changed, the views are directly updated.
5
6
7- Model: stores data and related logic. It represents data transferred between components or other related business logic. It is responsible for processing raw data.
8
9- View: typically represents the UI rendered by \@Component decorated components in ArkUI.
10
11- ViewModel: holds data of custom components stored in state variables, LocalStorage, and AppStorage.
12  - A custom component renders the UI by executing its **build()** method or \@Builder decorated method. In other words, the view model can render views.
13  - The view changes the view model through an event handler, that is, the change of the view model is driven by events. The view model provides the \@Watch callback method to listen for the change of state data.
14  - Any change of the view model must be synchronized back to the model to ensure the consistency between the view model and model, that is, the consistency of the application data.
15  - The view model structure should always be designed to adapt to the build and update of custom components. It is for this purpose that the model and view model are separated.
16
17
18A number of issues with UI construction and update arise from a poor view model design, which does not well support the rendering of custom components, or does not have a view model as a mediator, resulting in the custom component being forcibly adapted to the model. For example, a data model where an application directly reads data from the SQL database into the memory cannot directly adapt to the rendering of custom components. In this scenario, the view model adaptation must be considered during application development.
19
20
21![en-us_image_0000001653986573](figures/en-us_image_0000001653986573.png)
22
23
24In the preceding example involving the SQL database, the application should be designed as follows:
25
26
27- Model: responsible for efficient database operations.
28
29- View model: responsible for efficient UI update based on the ArkUI state management feature.
30
31- Converters/Adapters: responsible for conversion between the model and view model.
32  - Converters/Adapters can convert the model initially read from the database to create and initialize the view model.
33  - In the application scenario, the UI changes the view model through the event handler, and the converters/adapters need to synchronize the updated data of the view model back to the model.
34
35
36Compared with the Model-View (MV) pattern, which forcibly fitting the UI to the SQL database in this example, the MVVM pattern is more complex. The payback is a better UI performance with simplified UI design and implementation, thanks to its isolation of the view model layer.
37
38
39## View Model Data Sources
40
41
42The view model composes data from top-level multiple sources, such as variables decorated by \@State and \@Provide, LocalStorage, and AppStorage. Other decorators synchronize data with these data sources. The decorator to use depends on the extent to which the state needs to be shared between custom components. The following decorators are sorted in ascending order by sharing scope:
43
44
45- \@State: component-level sharing, implemented through the named parameter mechanism. It is sharing between the parent component and the child component by specifying parameters, for example, **CompA: ({ aProp: this.aProp })**.
46
47- \@Provide: component-level sharing, which is multi-level data sharing implemented by binding with \@Consume through a key. No parameter passing is involved during the sharing.
48
49- LocalStorage: page-level sharing, implemented by sharing LocalStorage instances in the current component tree through \@Entry.
50
51- AppStorage: application-level sharing, which is sharing of global application-wide UI state bound with the application process.
52
53
54### State Data Sharing from \@State Decorated Variables to One or More Child Components
55
56
57A one-way and two-way data synchronization relationship can be set up from an \@State decorated variable to an \@Prop, \@Link, or \@ObjectLink decorated variable. For details, see [\@State Decorator](arkts-state.md).
58
59
601. Use the \@State decorated variable **testNum** in the **Parent** root node as the view model data item. Pass **testNum** to the child components **LinkChild** and **Sibling**.
61
62   ```ts
63   // xxx.ets
64   @Entry
65   @Component
66   struct Parent {
67     @State @Watch("testNumChange1") testNum: number = 1;
68
69     testNumChange1(propName: string): void {
70       console.log(`Parent: testNumChange value ${this.testNum}`)
71     }
72
73     build() {
74       Column() {
75         LinkChild({ testNum: $testNum })
76         Sibling({ testNum: $testNum })
77       }
78     }
79   }
80   ```
81
822. In **LinkChild** and **Sibling**, use \@Link to set up a two-way data synchronization with the data source of the **Parent** component. In this example, **LinkLinkChild** and **PropLinkChild** are created in **LinkChild**.
83
84   ```ts
85   @Component
86   struct Sibling {
87     @Link @Watch("testNumChange") testNum: number;
88
89     testNumChange(propName: string): void {
90       console.log(`Sibling: testNumChange value ${this.testNum}`);
91     }
92
93     build() {
94       Text(`Sibling: ${this.testNum}`)
95     }
96   }
97
98   @Component
99   struct LinkChild {
100     @Link @Watch("testNumChange") testNum: number;
101
102     testNumChange(propName: string): void {
103       console.log(`LinkChild: testNumChange value ${this.testNum}`);
104     }
105
106     build() {
107       Column() {
108         Button('incr testNum')
109           .onClick(() => {
110             console.log(`LinkChild: before value change value ${this.testNum}`);
111             this.testNum = this.testNum + 1
112             console.log(`LinkChild: after value change value ${this.testNum}`);
113           })
114         Text(`LinkChild: ${this.testNum}`)
115         LinkLinkChild({ testNumGrand: $testNum })
116         PropLinkChild({ testNumGrand: this.testNum })
117       }
118       .height(200).width(200)
119     }
120   }
121   ```
122
1233. Declare **LinkLinkChild** and **PropLinkChild** as follows. Use \@Prop in **PropLinkChild** to set up a one-way data synchronization with the data source of the **Parent** component.
124
125   ```ts
126   @Component
127   struct LinkLinkChild {
128     @Link @Watch("testNumChange") testNumGrand: number = 0;
129
130     testNumChange(propName: string): void {
131       console.log(`LinkLinkChild: testNumGrand value ${this.testNumGrand}`);
132     }
133
134     build() {
135       Text(`LinkLinkChild: ${this.testNumGrand}`)
136     }
137   }
138
139
140   @Component
141   struct PropLinkChild {
142     @Prop @Watch("testNumChange") testNumGrand: number = 0;
143
144     testNumChange(propName: string): void {
145       console.log(`PropLinkChild: testNumGrand value ${this.testNumGrand}`);
146     }
147
148     build() {
149       Text(`PropLinkChild: ${this.testNumGrand}`)
150         .height(70)
151         .backgroundColor(Color.Red)
152         .onClick(() => {
153           this.testNumGrand += 1;
154         })
155     }
156   }
157   ```
158
159   ![en-us_image_0000001638250945](figures/en-us_image_0000001638250945.png)
160
161   When \@Link **testNum** in **LinkChild** changes:
162
163   1. The changes are first synchronized to its parent component **Parent**, and then from **Parent** to **Siling**.
164
165   2. Changes to \@Link **testNum** in **LinkChild** are also synchronized to child components **LinkLinkChild** and **PropLinkChild**.
166
167   The differences between the \@State decorator and \@Provide, LocalStorage, and AppStorage are as follows:
168
169   - When \@State is used, if you want to pass changes to a grandchild component, you must first pass the changes to the child component and then from the child component to the grandchild component.
170   - The changes can only be passed by specifying parameters of constructors, that is, through the named parameter mechanism CompA: ({ aProp: this.aProp }).
171
172   A complete code example is as follows:
173
174
175   ```ts
176   @Component
177   struct LinkLinkChild {
178     @Link @Watch("testNumChange") testNumGrand: number = 0;
179
180     testNumChange(propName: string): void {
181       console.log(`LinkLinkChild: testNumGrand value ${this.testNumGrand}`);
182     }
183
184     build() {
185       Text(`LinkLinkChild: ${this.testNumGrand}`)
186     }
187   }
188
189
190   @Component
191   struct PropLinkChild {
192     @Prop @Watch("testNumChange") testNumGrand: number = 0;
193
194     testNumChange(propName: string): void {
195       console.log(`PropLinkChild: testNumGrand value ${this.testNumGrand}`);
196     }
197
198     build() {
199       Text(`PropLinkChild: ${this.testNumGrand}`)
200         .height(70)
201         .backgroundColor(Color.Red)
202         .onClick(() => {
203           this.testNumGrand += 1;
204         })
205     }
206   }
207
208
209   @Component
210   struct Sibling {
211     @Link @Watch("testNumChange") testNum: number;
212
213     testNumChange(propName: string): void {
214       console.log(`Sibling: testNumChange value ${this.testNum}`);
215     }
216
217     build() {
218       Text(`Sibling: ${this.testNum}`)
219     }
220   }
221
222   @Component
223   struct LinkChild {
224     @Link @Watch("testNumChange") testNum: number;
225
226     testNumChange(propName: string): void {
227       console.log(`LinkChild: testNumChange value ${this.testNum}`);
228     }
229
230     build() {
231       Column() {
232         Button('incr testNum')
233           .onClick(() => {
234             console.log(`LinkChild: before value change value ${this.testNum}`);
235             this.testNum = this.testNum + 1
236             console.log(`LinkChild: after value change value ${this.testNum}`);
237           })
238         Text(`LinkChild: ${this.testNum}`)
239         LinkLinkChild({ testNumGrand: $testNum })
240         PropLinkChild({ testNumGrand: this.testNum })
241       }
242       .height(200).width(200)
243     }
244   }
245
246
247   @Entry
248   @Component
249   struct Parent {
250     @State @Watch("testNumChange1") testNum: number = 1;
251
252     testNumChange1(propName: string): void {
253       console.log(`Parent: testNumChange value ${this.testNum}`)
254     }
255
256     build() {
257       Column() {
258         LinkChild({ testNum: $testNum })
259         Sibling({ testNum: $testNum })
260       }
261     }
262   }
263   ```
264
265
266### State Data Sharing from \@Provide Decorated Variables to Descendant Components
267
268\@Provide decorated variables can share state data with any descendant component that uses \@Consume to create a two-way synchronization. For details, see [\@Provide and \@Consume Decorators](arkts-provide-and-consume.md).
269
270This \@Provide-\@Consume pattern is more convenient than the \@State-\@Link-\@Link pattern in terms of passing changes from a parent component to a grandchild component. It is suitable for sharing state data in a single page UI component tree.
271
272In the \@Provide-\@Consume pattern, changes are passed by binding \@Consume to \@Provide in the ancestor component through a key, instead of by specifying parameters in the constructor.
273
274The following example uses the \@Provide-\@Consume pattern to pass changes from a parent component to a grandchild component:
275
276
277```ts
278@Component
279struct LinkLinkChild {
280  @Consume @Watch("testNumChange") testNum: number = 0;
281
282  testNumChange(propName: string): void {
283    console.log(`LinkLinkChild: testNum value ${this.testNum}`);
284  }
285
286  build() {
287    Text(`LinkLinkChild: ${this.testNum}`)
288  }
289}
290
291@Component
292struct PropLinkChild {
293  @Prop @Watch("testNumChange") testNumGrand: number = 0;
294
295  testNumChange(propName: string): void {
296    console.log(`PropLinkChild: testNumGrand value ${this.testNumGrand}`);
297  }
298
299  build() {
300    Text(`PropLinkChild: ${this.testNumGrand}`)
301      .height(70)
302      .backgroundColor(Color.Red)
303      .onClick(() => {
304        this.testNumGrand += 1;
305      })
306  }
307}
308
309@Component
310struct Sibling {
311  @Consume @Watch("testNumChange") testNum: number;
312
313  testNumChange(propName: string): void {
314    console.log(`Sibling: testNumChange value ${this.testNum}`);
315  }
316
317  build() {
318    Text(`Sibling: ${this.testNum}`)
319  }
320}
321
322@Component
323struct LinkChild {
324  @Consume @Watch("testNumChange") testNum: number;
325
326  testNumChange(propName: string): void {
327    console.log(`LinkChild: testNumChange value ${this.testNum}`);
328  }
329
330  build() {
331    Column() {
332      Button('incr testNum')
333        .onClick(() => {
334          console.log(`LinkChild: before value change value ${this.testNum}`);
335          this.testNum = this.testNum + 1
336          console.log(`LinkChild: after value change value ${this.testNum}`);
337        })
338      Text(`LinkChild: ${this.testNum}`)
339      LinkLinkChild({ /* empty */ })
340      PropLinkChild({ testNumGrand: this.testNum })
341    }
342    .height(200).width(200)
343  }
344}
345
346@Entry
347@Component
348struct Parent {
349  @Provide @Watch("testNumChange1") testNum: number = 1;
350
351  testNumChange1(propName: string): void {
352    console.log(`Parent: testNumChange value ${this.testNum}`)
353  }
354
355  build() {
356    Column() {
357      LinkChild({ /* empty */ })
358      Sibling({ /* empty */ })
359    }
360  }
361}
362```
363
364
365### One- or Two-Way Synchronization for Properties in LocalStorage Instances
366
367You can use \@LocalStorageLink to set up a one-way synchronization for a property in a LocalStorage instance, or use \@LocalStorageProp to set up a two-way synchronization. A LocalStorage instance can be regarded as a map of the \@State decorated variables. For details, see [LocalStorage](arkts-localstorage.md).
368
369LocalStorage objects can be shared on several pages of an ArkUI application. In this way, state can be shared across pages of an application using \@LocalStorageLink, \@LocalStorageProp, and LocalStorage.
370
371Below is an example.
372
3731. Create a LocalStorage instance and inject it into the root node through \@Entry(storage).
374
3752. When the \@LocalStorageLink("testNum") variable is initialized in the **Parent** component, the **testNum** property is created in the LocalStorage instance and the specified initial value is set to 1, that is, \@LocalStorageLink("testNum") testNum: number = 1.
376
3773. In the child components, bind \@LocalStorageLink or \@LocalStorageProp to the same property name key to pass data.
378
379The LocalStorage instance can be considered as a map of the \@State decorated variable, and the property name is the key in the map.
380
381The synchronization between \@LocalStorageLink and the corresponding property in LocalStorage is two-way, the same as that between \@State and \@Link.
382
383The following figure shows the flow of component state update.
384
385![en-us_image_0000001588450934](figures/en-us_image_0000001588450934.png)
386
387
388```ts
389@Component
390struct LinkLinkChild {
391  @LocalStorageLink("testNum") @Watch("testNumChange") testNum: number = 1;
392
393  testNumChange(propName: string): void {
394    console.log(`LinkLinkChild: testNum value ${this.testNum}`);
395  }
396
397  build() {
398    Text(`LinkLinkChild: ${this.testNum}`)
399  }
400}
401
402@Component
403struct PropLinkChild {
404  @LocalStorageProp("testNum") @Watch("testNumChange") testNumGrand: number = 1;
405
406  testNumChange(propName: string): void {
407    console.log(`PropLinkChild: testNumGrand value ${this.testNumGrand}`);
408  }
409
410  build() {
411    Text(`PropLinkChild: ${this.testNumGrand}`)
412      .height(70)
413      .backgroundColor(Color.Red)
414      .onClick(() => {
415        this.testNumGrand += 1;
416      })
417  }
418}
419
420@Component
421struct Sibling {
422  @LocalStorageLink("testNum") @Watch("testNumChange") testNum: number = 1;
423
424  testNumChange(propName: string): void {
425    console.log(`Sibling: testNumChange value ${this.testNum}`);
426  }
427
428  build() {
429    Text(`Sibling: ${this.testNum}`)
430  }
431}
432
433@Component
434struct LinkChild {
435  @LocalStorageLink("testNum") @Watch("testNumChange") testNum: number = 1;
436
437  testNumChange(propName: string): void {
438    console.log(`LinkChild: testNumChange value ${this.testNum}`);
439  }
440
441  build() {
442    Column() {
443      Button('incr testNum')
444        .onClick(() => {
445          console.log(`LinkChild: before value change value ${this.testNum}`);
446          this.testNum = this.testNum + 1
447          console.log(`LinkChild: after value change value ${this.testNum}`);
448        })
449      Text(`LinkChild: ${this.testNum}`)
450      LinkLinkChild({ /* empty */ })
451      PropLinkChild({ /* empty */ })
452    }
453    .height(200).width(200)
454  }
455}
456
457// Create a LocalStorage object to hold the data.
458const storage = new LocalStorage();
459@Entry(storage)
460@Component
461struct Parent {
462  @LocalStorageLink("testNum") @Watch("testNumChange1") testNum: number = 1;
463
464  testNumChange1(propName: string): void {
465    console.log(`Parent: testNumChange value ${this.testNum}`)
466  }
467
468  build() {
469    Column() {
470      LinkChild({ /* empty */ })
471      Sibling({ /* empty */ })
472    }
473  }
474}
475```
476
477
478### One- or Two-Way Synchronization for Properties in AppStorage
479
480AppStorage is a singleton object of LocalStorage. ArkUI creates this object when an application is started and uses \@StorageLink and \@StorageProp to share data across pages. The usage of AppStorage is similar to that of LocalStorage.
481
482You can also use PersistentStorage to persist specific properties in AppStorage to files on the local disk. In this way, \@StorageLink and \@StorageProp decorated properties can restore upon application re-start to the values as they were when the application was closed. For details, see [PersistentStorage](arkts-persiststorage.md).
483
484An example is as follows:
485
486
487```ts
488@Component
489struct LinkLinkChild {
490  @StorageLink("testNum") @Watch("testNumChange") testNum: number = 1;
491
492  testNumChange(propName: string): void {
493    console.log(`LinkLinkChild: testNum value ${this.testNum}`);
494  }
495
496  build() {
497    Text(`LinkLinkChild: ${this.testNum}`)
498  }
499}
500
501@Component
502struct PropLinkChild {
503  @StorageProp("testNum") @Watch("testNumChange") testNumGrand: number = 1;
504
505  testNumChange(propName: string): void {
506    console.log(`PropLinkChild: testNumGrand value ${this.testNumGrand}`);
507  }
508
509  build() {
510    Text(`PropLinkChild: ${this.testNumGrand}`)
511      .height(70)
512      .backgroundColor(Color.Red)
513      .onClick(() => {
514        this.testNumGrand += 1;
515      })
516  }
517}
518
519@Component
520struct Sibling {
521  @StorageLink("testNum") @Watch("testNumChange") testNum: number = 1;
522
523  testNumChange(propName: string): void {
524    console.log(`Sibling: testNumChange value ${this.testNum}`);
525  }
526
527  build() {
528    Text(`Sibling: ${this.testNum}`)
529  }
530}
531
532@Component
533struct LinkChild {
534  @StorageLink("testNum") @Watch("testNumChange") testNum: number = 1;
535
536  testNumChange(propName: string): void {
537    console.log(`LinkChild: testNumChange value ${this.testNum}`);
538  }
539
540  build() {
541    Column() {
542      Button('incr testNum')
543        .onClick(() => {
544          console.log(`LinkChild: before value change value ${this.testNum}`);
545          this.testNum = this.testNum + 1
546          console.log(`LinkChild: after value change value ${this.testNum}`);
547        })
548      Text(`LinkChild: ${this.testNum}`)
549      LinkLinkChild({ /* empty */
550      })
551      PropLinkChild({ /* empty */
552      })
553    }
554    .height(200).width(200)
555  }
556}
557
558
559@Entry
560@Component
561struct Parent {
562  @StorageLink("testNum") @Watch("testNumChange1") testNum: number = 1;
563
564  testNumChange1(propName: string): void {
565    console.log(`Parent: testNumChange value ${this.testNum}`)
566  }
567
568  build() {
569    Column() {
570      LinkChild({ /* empty */
571      })
572      Sibling({ /* empty */
573      })
574    }
575  }
576}
577```
578
579
580## View Model Nesting Scenario
581
582
583In most cases, view model data items are of complex types, such as arrays of objects, nested objects, or combinations of these types. In nested scenarios, you can use \@Observed and \@Prop or \@ObjectLink to observe changes.
584
585
586### \@Prop and \@ObjectLink Nested Data Structures
587
588When possible, design a separate \@Component decorator 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 \@Component decorators: one for rendering an external array/object, and the other for rendering a class object nested within the array/object. Variables decorated by \@Prop, \@Link, and \@ObjectLink can only observe changes at the first layer.
589
590- For a class:
591  - The value assignment change can be observed: this.obj=new ClassObj(...)
592  - The change of the object property can be observed: this.obj.a=new ClassA(...)
593  - Property changes at a deeper layer cannot be observed: this.obj.a.b = 47
594
595- For an array:
596  - The overall value assignment of the array can be observed: this.arr=[...]
597  - The deletion, insertion, and replacement of data items can be observed: this.arr[1] = new ClassA(); this.arr.pop(); this.arr.push (new ClassA(...))), this.arr.sort(...)
598  - Array changes at a deeper layer cannot be observed: this.arr[1].b = 47
599
600To observe changes of nested objects inside a class, use \@ObjectLink or \@Prop. \@ObjectLink is preferred, which initializes itself through a reference to an internal property of a nested object. \@Prop initializes itself through a deep copy of the nested object to implement one-way synchronization. The deep copy of \@Prop significantly outperforms the reference copy of \@ObjectLink.
601
602\@ObjectLink or \@Prop can be used to store nested objects of a class. This class must be decorated with the \@Observed decorator. Otherwise, property changes of the class do not trigger UI update or refresh. \@Observed implements a custom constructor for its decorated class. This constructor creates an instance of a class and uses the ES6 proxy wrapper (implemented by the ArkUI framework) to intercept all get and set operations on the decorated class property. "Set" observes the property value. When value assignment occurs, the ArkUI framework is notified of the update. "Get" collects which UI components depend on this state variable to minimize UI re-render.
603
604In the nesting scenario, use the \@Observed decorator as follows:
605
606- If the nested data is a class, directly decorate it with \@Observed.
607
608- If the nested data is an array, you can observe the array change in the following way:
609
610  ```ts
611  @Observed class ObservedArray<T> extends Array<T> {
612      constructor(args: T[]) {
613          super(...args);
614      }
615      /* otherwise empty */
616  }
617  ```
618
619  The view model is the outer class.
620
621
622  ```ts
623  class Outer {
624    innerArrayProp : ObservedArray<string> = [];
625    ...
626  }
627  ```
628
629
630### Differences Between \@Prop and \@ObjectLink in Nested Data Structures
631
632In the following example:
633
634- The parent component **ViewB** renders \@State arrA: Array\<ClassA>. \@State can observe the allocation of new arrays, and insertion, deletion, and replacement of array items.
635
636- The child component **ViewA** renders each object of **ClassA**.
637
638- With \@ObjectLink a: ClassA:
639
640  - When \@Observed ClassA is used, the changes of **ClassA** objects nested in the array can be observed.
641
642  - When \@Observed ClassA is not used:
643    This.arrA[Math.floor(this.arrA.length/2)].c=10 in **ViewB** cannot be observed, and therefore the **ViewA** component will not be updated.
644
645    For the first and second array items in the array, both of them initialize two **ViewA** objects and render the same **ViewA** instance. When this.a.c += 1; is assigned to a property in **ViewA**, another **ViewA** initialized from the same **ClassA** is not re-rendered.
646
647![en-us_image_0000001588610894](figures/en-us_image_0000001588610894.png)
648
649
650```ts
651let NextID: number = 1;
652
653// Use the class decorator @Observed to decorate ClassA.
654@Observed
655class ClassA {
656  public id: number;
657  public c: number;
658
659  constructor(c: number) {
660    this.id = NextID++;
661    this.c = c;
662  }
663}
664
665@Component
666struct ViewA {
667  @ObjectLink a: ClassA;
668  label: string = "ViewA1";
669
670  build() {
671    Row() {
672      Button(`ViewA [${this.label}] this.a.c= ${this.a.c} +1`)
673        .onClick(() => {
674          // Change the object property.
675          this.a.c += 1;
676        })
677    }
678  }
679}
680
681@Entry
682@Component
683struct ViewB {
684  @State arrA: ClassA[] = [new ClassA(0), new ClassA(0)];
685
686  build() {
687    Column() {
688      ForEach(this.arrA,
689        (item: ClassA) => {
690          ViewA({ label: `#${item.id}`, a: item })
691        },
692        (item: ClassA): string => { return item.id.toString(); }
693      )
694
695      Divider().height(10)
696
697      if (this.arrA.length) {
698        ViewA({ label: `ViewA this.arrA[first]`, a: this.arrA[0] })
699        ViewA({ label: `ViewA this.arrA[last]`, a: this.arrA[this.arrA.length-1] })
700      }
701
702      Divider().height(10)
703
704      Button(`ViewB: reset array`)
705        .onClick(() => {
706          // Replace the entire array, which will be observed by @State this.arrA.
707          this.arrA = [new ClassA(0), new ClassA(0)];
708        })
709      Button(`array push`)
710        .onClick(() => {
711          // Insert data into the array, which will be observed by @State this.arrA.
712          this.arrA.push(new ClassA(0))
713        })
714      Button(`array shift`)
715        .onClick(() => {
716          // Remove data from the array, which will be observed by @State this.arrA.
717          this.arrA.shift()
718        })
719      Button(`ViewB: chg item property in middle`)
720        .onClick(() => {
721          // Replace an item in the array, which will be observed by @State this.arrA.
722          this.arrA[Math.floor(this.arrA.length / 2)] = new ClassA(11);
723        })
724      Button(`ViewB: chg item property in middle`)
725        .onClick(() => {
726          // Change property c of an item in the array, which will be observed by @ObjectLink in ViewA.
727          this.arrA[Math.floor(this.arrA.length / 2)].c = 10;
728        })
729    }
730  }
731}
732```
733
734In **ViewA**, replace \@ObjectLink with \@Prop.
735
736
737```ts
738@Component
739struct ViewA {
740
741  @Prop a: ClassA = new ClassA(0);
742  label : string = "ViewA1";
743
744  build() {
745     Row() {
746        Button(`ViewA [${this.label}] this.a.c= ${this.a.c} +1`)
747        .onClick(() => {
748            // change object property
749            this.a.c += 1;
750        })
751     }
752  }
753}
754```
755
756When \@ObjectLink is used, if you click the first or second item of the array, the following two **ViewA** instances change synchronously.
757
758Unlike \@ObjectLink, \@Prop sets up a one-way data synchronization. Clicking the button in **ViewA** triggers only the re-render of the button itself and is not propagated to other **ViewA** instances. **ClassA** in **ViewA** is only a copy, not an object of its parent component \@State arrA : Array&lt;ClassA&gt, nor a **ClassA** instance of any other **ViewA**. As a result, though on the surface, the array and **ViewA** have the same object passed in, two irrelevant objects are used for rendering on the UI.
759
760Note that there is another difference between \@Prop and \@ObjectLink: \@ObjectLink decorated variables are readable only and cannot be assigned values, whereas \@Prop decorated variables can be assigned values.
761
762- \@ObjectLink implements two-way data synchronization because it is initialized through a reference to the data source.
763
764- \@Prop implements one-way data synchronization and requires a deep copy of the data source.
765
766- To assign a new object to \@Prop is to overwrite the local value. However, for \@ObjectLink that implements two-way data synchronization, to assign a new object is to update the array item or class property in the data source, which is not possible in TypeScript/JavaScript.
767
768
769## Example
770
771
772The following example discusses the application design of nested view models, especially how a custom component renders a nested object. This scenario is common in real-world application development.
773
774
775Let's develop a phonebook application to implement the following features:
776
777
778- Display the phone numbers of contacts and the local device ("Me").
779
780- You can select a contact and edit its information, including the phone number and address.
781
782- When you update contact information, the changes are saved only after you click **Save Changes**.
783
784- You can click **Delete Contact** to delete a contact from the contacts list.
785
786
787In this example, the view model needs to include the following:
788
789
790- **AddressBook** (class)
791  - **me** (device): stores a **Person** class.
792  - **contacts**: stores a **Person** class array.
793
794
795The **AddressBook** class is declared as follows:
796
797
798
799```ts
800export class AddressBook {
801  me: Person;
802  contacts: ObservedArray<Person>;
803804  constructor(me: Person, contacts: Person[]) {
805    this.me = me;
806    this.contacts = new ObservedArray<Person>(contacts);
807  }
808}
809```
810
811
812- Person (class)
813  - name : string
814  - address : Address
815  - phones: ObservedArray&lt;string&gt;
816  - Address (class)
817    - street : string
818    - zip : number
819    - city : string
820
821
822The **Address** class is declared as follows:
823
824
825
826```ts
827@Observed
828export class Address {
829  street: string;
830  zip: number;
831  city: string;
832
833  constructor(street: string,
834              zip: number,
835              city: string) {
836    this.street = street;
837    this.zip = zip;
838    this.city = city;
839  }
840}
841```
842
843
844The **Person** class is declared as follows:
845
846
847
848```ts
849@Observed
850export class Person {
851  id_: string;
852  name: string;
853  address: Address;
854  phones: ObservedArray<string>;
855
856  constructor(name: string,
857              street: string,
858              zip: number,
859              city: string,
860              phones: string[]) {
861    this.id_ = `${nextId}`;
862    nextId++;
863    this.name = name;
864    this.address = new Address(street, zip, city);
865    this.phones = new ObservedArray<string>(phones);
866  }
867}
868```
869
870
871Note that **phones** is a nested property. To observe its change, you need to extend the array and decorate it with \@Observed. The **ObservedArray** class is declared as follows:
872
873
874
875```ts
876@Observed
877export class ObservedArray<T> extends Array<T> {
878  constructor(args: T[]) {
879    console.log(`ObservedArray: ${JSON.stringify(args)} `)
880    if (args instanceof Array) {
881      super(...args);
882    } else {
883      super(args)
884    }
885  }
886}
887```
888
889
890- **selected**: reference to **Person**.
891
892
893The update process is as follows:
894
895
8961. Initialize all data in the root node **PageEntry**, and establish two-way data synchronization between **me** and **contacts** and its child component **AddressBookView**. The default value of **selectedPerson** is **me**. Note that **selectedPerson** is not data in the **PageEntry** data source, but a reference to a **Person** object in the data source.
897   **PageEntry** and **AddressBookView** are declared as follows:
898
899
900   ```ts
901   @Component
902   struct AddressBookView {
903
904       @ObjectLink me : Person;
905       @ObjectLink contacts : ObservedArray<Person>;
906       @State selectedPerson: Person = new Person("", "", 0, "", []);
907
908       aboutToAppear() {
909           this.selectedPerson = this.me;
910       }
911
912       build() {
913           Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Start}) {
914               Text("Me:")
915               PersonView({person: this.me, phones: this.me.phones, selectedPerson: this.$selectedPerson})
916
917               Divider().height(8)
918
919              ForEach(this.contacts, (contact: Person) => {
920                PersonView({ person: contact, phones: contact.phones as ObservedArray<string>, selectedPerson: this.$selectedPerson })
921              },
922                (contact: Person): string => { return contact.id_; }
923              )
924
925               Divider().height(8)
926
927               Text("Edit:")
928               PersonEditView({ selectedPerson: this.$selectedPerson, name: this.selectedPerson.name, address: this.selectedPerson.address, phones: this.selectedPerson.phones })
929           }
930               .borderStyle(BorderStyle.Solid).borderWidth(5).borderColor(0xAFEEEE).borderRadius(5)
931       }
932   }
933
934   @Entry
935   @Component
936   struct PageEntry {
937     @Provide addrBook: AddressBook = new AddressBook(
938       new Person("Gigi", "Itamerenkatu 9", 180, "Helsinki", ["18*********", "18*********", "18*********"]),
939       [
940         new Person("Oly", "Itamerenkatu 9", 180, "Helsinki", ["18*********", "18*********"]),
941         new Person("Sam", "Itamerenkatu 9", 180, "Helsinki", ["18*********", "18*********"]),
942         new Person("Vivi", "Itamerenkatu 9", 180, "Helsinki", ["18*********", "18*********"]),
943       ]);
944
945     build() {
946       Column() {
947         AddressBookView({ me: this.addrBook.me, contacts: this.addrBook.contacts, selectedPerson: this.addrBook.me })
948       }
949     }
950   }
951   ```
952
9532. **PersonView** is the view that shows a contact name and preferred phone number in the phonebook. When a user selects a contact (person), that contact is highlighted and needs to be synchronized back to the **selectedPerson** of the parent component **AddressBookView**. In this case, two-way data synchronization needs to be established through \@Link.
954   **PersonView** is declared as follows:
955
956
957   ```ts
958   // Display the contact name and preferred phone number.
959   // To update the phone number, @ObjectLink person and @ObjectLink phones are required.
960   // this.person.phones[0] cannot be used to display the preferred phone number because @ObjectLink person only proxies the Person property and the changes inside the array cannot be observed.
961   // Trigger the onClick event to update selectedPerson.
962   @Component
963   struct PersonView {
964
965       @ObjectLink person : Person;
966       @ObjectLink phones :  ObservedArray<string>;
967
968       @Link selectedPerson : Person;
969
970       build() {
971           Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
972             Text(this.person.name)
973             if (this.phones.length > 0) {
974               Text(this.phones[0])
975             }
976           }
977           .height(55)
978           .backgroundColor(this.selectedPerson.name == this.person.name ? "#ffa0a0" : "#ffffff")
979           .onClick(() => {
980               this.selectedPerson = this.person;
981           })
982       }
983   }
984   ```
985
9863. The information about the selected contact (person) is displayed in the **PersonEditView** object. The data synchronization for the **PersonEditView** can be implemented as follows:
987
988   - When the user's keyboard input is received in the Edit state through the **Input.onChange** callback event, the change should be reflected in the current **PersonEditView**, but does not need to be synchronized to the data source before **Save Changes** is clicked. In this case, \@Prop is used to make a deep copy of the information about the current contact (person).
989
990   - **PersonEditView** establishes through \@Link **seletedPerson: Person** two-way data synchronization with **selectedPerson** of **AddressBookView**. When a user clicks **Save Changes**, the change to \@Prop is assigned to \@Link **seletedPerson: Person**. In this way, the data is synchronized back to the data source.
991
992   - In **PersonEditView**, \@Consume **addrBook: AddressBook** is used to set up two-way synchronization with the root node **PageEntry**. When a user deletes a contact on the **PersonEditView** page, the deletion is directly synchronized to **PageEntry**, which then instructs **AddressBookView** to update the contracts list page. **PersonEditView** is declared as follows:
993
994     ```ts
995     // Render the information about the contact (person).
996     // The @Prop decorated variable makes a deep copy from the parent component AddressBookView and retains the changes locally. The changes of TextInput apply only to the local copy.
997     // Click Save Changes to copy all data to @Link through @Prop and synchronize the data to other components.
998     @Component
999     struct PersonEditView {
1000
1001         @Consume addrBook : AddressBook;
1002
1003         /* Reference pointing to selectedPerson in the parent component. */
1004         @Link selectedPerson: Person;
1005
1006         /* Make changes on the local copy until you click Save Changes. */
1007         @Prop name: string = "";
1008         @Prop address : Address = new Address("", 0, "");
1009         @Prop phones : ObservedArray<string> = [];
1010
1011         selectedPersonIndex() : number {
1012             return this.addrBook.contacts.findIndex((person: Person) => person.id_ == this.selectedPerson.id_);
1013         }
1014
1015         build() {
1016             Column() {
1017                 TextInput({ text: this.name})
1018                     .onChange((value) => {
1019                         this.name = value;
1020                       })
1021                 TextInput({text: this.address.street})
1022                     .onChange((value) => {
1023                         this.address.street = value;
1024                     })
1025
1026                 TextInput({text: this.address.city})
1027                     .onChange((value) => {
1028                         this.address.city = value;
1029                     })
1030
1031                 TextInput({text: this.address.zip.toString()})
1032                     .onChange((value) => {
1033                         const result = Number.parseInt(value);
1034                         this.address.zip= Number.isNaN(result) ? 0 : result;
1035                     })
1036
1037                 if (this.phones.length > 0) {
1038                   ForEach(this.phones,
1039                     (phone: ResourceStr, index?:number) => {
1040                       TextInput({ text: phone })
1041                         .width(150)
1042                         .onChange((value) => {
1043                           console.log(`${index}. ${value} value has changed`)
1044                           this.phones[index!] = value;
1045                         })
1046                     },
1047                     (phone: ResourceStr, index?:number) => `${index}-${phone}`
1048                   )
1049                 }
1050
1051                 Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
1052                     Text("Save Changes")
1053                         .onClick(() => {
1054                             // Assign the updated value of the local copy to the reference pointing to selectedPerson in the parent component.
1055                             // Avoid creating new objects. Modify existing properties instead.
1056                             this.selectedPerson.name = this.name;
1057                             this.selectedPerson.address.street = this.address.street
1058                             this.selectedPerson.address.city   =  this.address.city
1059                             this.selectedPerson.address.zip    = this.address.zip
1060                             this.phones.forEach((phone : string, index : number) => { this.selectedPerson.phones[index] = phone } );
1061                         })
1062                     if (this.selectedPersonIndex()!=-1) {
1063                         Text("Delete Contact")
1064                             .onClick(() => {
1065                                 let index = this.selectedPersonIndex();
1066                                 console.log(`delete contact at index ${index}`);
1067
1068                                 // Delete the current contact.
1069                                 this.addrBook.contacts.splice(index, 1);
1070
1071                                 // Delete the current selectedPerson. The selected contact is then changed to the contact immediately before the deleted contact.
1072                                 index = (index < this.addrBook.contacts.length) ? index : index-1;
1073
1074                                 // If all contracts are deleted, the **me** object is selected.
1075                                 this.selectedPerson = (index>=0) ? this.addrBook.contacts[index] : this.addrBook.me;
1076                             })
1077                     }
1078                 }
1079
1080             }
1081         }
1082     }
1083     ```
1084
1085     Pay attention to the following differences between \@ObjectLink and \@Link:
1086
1087     1. To implement two-way data synchronization with the parent component **PageView** in **AddressBookView**, you need to use \@ObjectLink, instead of \@Link, to decorate **me: Person** and **contacts: ObservedArray\<Person>**. The reasons are as follows:
1088        - The type of the \@Link decorated variable must be the same as that of the data source, and \@Link can only observe the changes at the first layer.
1089        - \@ObjectLink allows for initialization from the property of the data source. It functions as a proxy for the properties of the \@Observed decorated class and can observe the changes of the properties of that class.
1090     2. When the contact name (**Person.name**) or preferred phone number (**Person.phones[0]**) is updated, **PersonView** needs to be updated. As the update to **Person.phones[0]** occurs at the second layer, it cannot be observed if \@Link is used. In addition, \@Link requires its decorated variable be of the same type as the data source. Therefore, \@ObjectLink is required in **PersonView**, that is, \@ObjectLink **person: Person** and \@ObjectLink **phones: ObservedArray\<string>**.
1091
1092     ![en-us_image_0000001605293914](figures/en-us_image_0000001605293914.png)
1093
1094     Now you have a basic idea of how to build a view model. In the root node of an application, the view model may comprise a huge amount of nested data, which is more often the case. Yet, we can make reasonable separation of the data in the UI tree structure. We can adapt the view model data items to views so that the view at each layer contains relatively flat data, and you only need to observe changes at the current layer.
1095
1096     In this way, the UI re-render workload is minimized, leading to higher application performance.
1097
1098     The complete sample code is as follows:
1099
1100
1101```ts
1102
1103 // ViewModel classes
1104 let nextId = 0;
1105
1106 @Observed
1107 export class ObservedArray<T> extends Array<T> {
1108   constructor(args: T[]) {
1109     console.log(`ObservedArray: ${JSON.stringify(args)} `)
1110     if (args instanceof Array) {
1111       super(...args);
1112     } else {
1113       super(args)
1114     }
1115   }
1116 }
1117
1118 @Observed
1119 export class Address {
1120   street: string;
1121   zip: number;
1122   city: string;
1123
1124   constructor(street: string,
1125               zip: number,
1126               city: string) {
1127     this.street = street;
1128     this.zip = zip;
1129     this.city = city;
1130   }
1131 }
1132
1133 @Observed
1134 export class Person {
1135   id_: string;
1136   name: string;
1137   address: Address;
1138   phones: ObservedArray<string>;
1139
1140   constructor(name: string,
1141               street: string,
1142               zip: number,
1143               city: string,
1144               phones: string[]) {
1145     this.id_ = `${nextId}`;
1146     nextId++;
1147     this.name = name;
1148     this.address = new Address(street, zip, city);
1149     this.phones = new ObservedArray<string>(phones);
1150   }
1151 }
1152
1153 export class AddressBook {
1154   me: Person;
1155   contacts: ObservedArray<Person>;
1156
1157   constructor(me: Person, contacts: Person[]) {
1158     this.me = me;
1159     this.contacts = new ObservedArray<Person>(contacts);
1160   }
1161 }
1162
1163 // Render the name of the Person object and the first phone number in the @Observed array <string>.
1164 // To update the phone number, @ObjectLink person and @ObjectLink phones are required.
1165 // this.person.phones cannot be used. Otherwise, changes to items inside the array will not be observed.
1166 // Update selectedPerson in onClick in AddressBookView and PersonEditView.
1167 @Component
1168 struct PersonView {
1169   @ObjectLink person: Person;
1170   @ObjectLink phones: ObservedArray<string>;
1171   @Link selectedPerson: Person;
1172
1173   build() {
1174     Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
1175       Text(this.person.name)
1176       if (this.phones.length) {
1177         Text(this.phones[0])
1178       }
1179     }
1180     .height(55)
1181     .backgroundColor(this.selectedPerson.name == this.person.name ? "#ffa0a0" : "#ffffff")
1182     .onClick(() => {
1183       this.selectedPerson = this.person;
1184     })
1185   }
1186 }
1187
1188 // Render the information about the contact (person).
1189 // The @Prop decorated variable makes a deep copy from the parent component AddressBookView and retains the changes locally. The changes of TextInput apply only to the local copy.
1190 // Click Save Changes to copy all data to @Link through @Prop and synchronize the data to other components.
1191 @Component
1192 struct PersonEditView {
1193   @Consume addrBook: AddressBook;
1194
1195   /* Reference pointing to selectedPerson in the parent component. */
1196   @Link selectedPerson: Person;
1197
1198   /* Make changes on the local copy until you click Save Changes. */
1199   @Prop name: string = "";
1200   @Prop address: Address = new Address("", 0, "");
1201   @Prop phones: ObservedArray<string> = [];
1202
1203   selectedPersonIndex(): number {
1204     return this.addrBook.contacts.findIndex((person: Person) => person.id_ == this.selectedPerson.id_);
1205   }
1206
1207   build() {
1208     Column() {
1209       TextInput({ text: this.name })
1210         .onChange((value) => {
1211           this.name = value;
1212         })
1213       TextInput({ text: this.address.street })
1214         .onChange((value) => {
1215           this.address.street = value;
1216         })
1217
1218       TextInput({ text: this.address.city })
1219         .onChange((value) => {
1220           this.address.city = value;
1221         })
1222
1223       TextInput({ text: this.address.zip.toString() })
1224         .onChange((value) => {
1225           const result = Number.parseInt(value);
1226           this.address.zip = Number.isNaN(result) ? 0 : result;
1227         })
1228
1229       if (this.phones.length > 0) {
1230         ForEach(this.phones,
1231           (phone: ResourceStr, index?:number) => {
1232             TextInput({ text: phone })
1233               .width(150)
1234               .onChange((value) => {
1235                 console.log(`${index}. ${value} value has changed`)
1236                 this.phones[index!] = value;
1237               })
1238           },
1239           (phone: ResourceStr, index?:number) => `${index}-${phone}`
1240         )
1241       }
1242
1243       Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
1244         Text("Save Changes")
1245           .onClick(() => {
1246             // Assign the updated value of the local copy to the reference pointing to selectedPerson in the parent component.
1247             // Avoid creating new objects. Modify existing properties instead.
1248             this.selectedPerson.name = this.name;
1249             this.selectedPerson.address.street = this.address.street
1250             this.selectedPerson.address.city = this.address.city
1251             this.selectedPerson.address.zip = this.address.zip
1252             this.phones.forEach((phone: string, index: number) => {
1253               this.selectedPerson.phones[index] = phone
1254             });
1255           })
1256         if (this.selectedPersonIndex() != -1) {
1257           Text("Delete Contact")
1258             .onClick(() => {
1259               let index = this.selectedPersonIndex();
1260               console.log(`delete contact at index ${index}`);
1261
1262               // Delete the current contact.
1263               this.addrBook.contacts.splice(index, 1);
1264
1265               // Delete the current selectedPerson. The selected contact is then changed to the contact immediately before the deleted contact.
1266               index = (index < this.addrBook.contacts.length) ? index : index - 1;
1267
1268               // If all contracts are deleted, the **me** object is selected.
1269               this.selectedPerson = (index >= 0) ? this.addrBook.contacts[index] : this.addrBook.me;
1270             })
1271         }
1272       }
1273
1274     }
1275   }
1276 }
1277
1278 @Component
1279 struct AddressBookView {
1280   @ObjectLink me: Person;
1281   @ObjectLink contacts: ObservedArray<Person>;
1282   @State selectedPerson: Person = new Person("", "", 0, "", []);
1283
1284   aboutToAppear() {
1285     this.selectedPerson = this.me;
1286   }
1287
1288   build() {
1289     Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Start }) {
1290       Text("Me:")
1291       PersonView({ person: this.me, phones: this.me.phones, selectedPerson: this.$selectedPerson })
1292
1293       Divider().height(8)
1294
1295       ForEach(this.contacts, (contact: Person) => {
1296         PersonView({ person: contact, phones: contact.phones as ObservedArray<string>, selectedPerson: this.$selectedPerson })
1297       },
1298         (contact: Person): string => { return contact.id_; }
1299       )
1300
1301       Divider().height(8)
1302
1303       Text("Edit:")
1304       PersonEditView({
1305         selectedPerson: this.$selectedPerson,
1306         name: this.selectedPerson.name,
1307         address: this.selectedPerson.address,
1308         phones: this.selectedPerson.phones
1309       })
1310     }
1311     .borderStyle(BorderStyle.Solid).borderWidth(5).borderColor(0xAFEEEE).borderRadius(5)
1312   }
1313 }
1314
1315 @Entry
1316 @Component
1317 struct PageEntry {
1318   @Provide addrBook: AddressBook = new AddressBook(
1319     new Person("Gigi", "Itamerenkatu 9", 180, "Helsinki", ["18*********", "18*********", "18*********"]),
1320     [
1321       new Person("Oly", "Itamerenkatu 9", 180, "Helsinki", ["18*********", "18*********"]),
1322       new Person("Sam", "Itamerenkatu 9", 180, "Helsinki", ["18*********", "18*********"]),
1323       new Person("Vivi", "Itamerenkatu 9", 180, "Helsinki", ["18*********", "18*********"]),
1324     ]);
1325
1326   build() {
1327     Column() {
1328       AddressBookView({ me: this.addrBook.me, contacts: this.addrBook.contacts, selectedPerson: this.addrBook.me })
1329     }
1330   }
1331 }
1332```
1333