1# MVVM
2
3
4Rendering or re-rendering the UI based on state is complex, but critical to application performance. State data covers a collection of arrays, objects, or nested objects. In ArkUI, the Model-View-View Model (MVVM) pattern is leveraged for state management, where the state management module functions as the view model to bind data (part of model) to views. When data is changed, the views are 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 components decorated by \@Component.
10
11- View model: holds data stored in custom component state variables, LocalStorage, and AppStorage.
12  - A custom component renders the UI by executing its **build()** method or an \@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 re-render 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 well 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 updates 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 into a view model, and then initialize it.
33  - When the UI changes the view model through the event handler, the converters/adapters synchronize the updated data of the view model back to the model.
34
35
36Compared with the Model-View (MV) pattern, which forcibly fits 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 multiple top-level sources, such as variables decorated by \@State and \@Provide, LocalStorage, and AppStorage. Other decorators synchronize data with these data sources. The top-level data source to use depends on the extent to which the state needs to be shared between custom components as described below 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 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 application-wide UI state bound with the application process.
52
53
54### State Data Sharing Through \@State
55
56
57A one- or 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 **LinkChild** component.
124
125   ```ts
126   @Component
127   struct LinkLinkChild {
128     @Link @Watch("testNumChange") testNumGrand: number;
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 **Sibling**.
164
165   2. The changes are also synchronized to the child components **LinkLinkChild** and **PropLinkChild**.
166
167   Different from \@Provide, LocalStorage, and AppStorage, \@State is used with the following constraints:
168
169   - 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;
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 Through \@Provide
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;
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
369A LocalStorage instance 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, with the initial value set to **1**, is created in the LocalStorage instance, that is, \@LocalStorageLink("testNum") testNum: number = 1.
376
3773. In the child components, use \@LocalStorageLink or \@LocalStorageProp to bind the same property name key to pass data.
378
379The LocalStorage instance can be considered as a map of the \@State decorated variables, 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 instance to hold 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 of LocalStorage. ArkUI creates this instance when an application is started and uses \@StorageLink and \@StorageProp to implement data sharing 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## Nested View Model
581
582
583In most cases, view model data items are of complex types, such as arrays of objects, nested objects, or their combinations. 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 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. For variables decorated by \@State, \@Prop, \@Link, and \@ObjectLink, only changes at the first layer can be observed.
589
590- For a class:
591  - Value assignment changes can be observed: this.obj=new ClassObj(...)
592  - Object property changes 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 reference copy of \@ObjectLink significantly outperforms the deep copy of \@Prop.
601
602\@ObjectLink or \@Prop can be used to store nested objects of a class. This class must be decorated with \@Observed. Otherwise, its property changes will not trigger UI re-rendering. \@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 UI components that depend on this state variable to minimize UI re-rendering.
603
604In the nested 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          if (args instanceof Array) {
614            super(...args);
615          } else {
616            super(args)
617          }
618      }
619      /* otherwise empty */
620  }
621  ```
622
623  The view model is the outer class.
624
625
626  ```ts
627  class Outer {
628    innerArrayProp : ObservedArray<string> = [];
629    ...
630  }
631  ```
632
633
634### Differences Between \@Prop and \@ObjectLink in Nested Data Structures
635
636In the following example:
637
638- 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.
639
640- The child component **ViewA** renders each object of **ClassA**.
641
642- With \@ObjectLink a: ClassA:
643
644  - When \@Observed ClassA is used, the changes of **ClassA** objects nested in the array can be observed.
645
646  - When \@Observed ClassA is not used,
647    this.arrA[Math.floor(this.arrA.length/2)].c=10 in **ViewB** cannot be observed, and therefore the **ViewA** component will not be updated.
648
649    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.
650
651![en-us_image_0000001588610894](figures/en-us_image_0000001588610894.png)
652
653
654```ts
655let NextID: number = 1;
656
657// Use the class decorator @Observed to decorate ClassA.
658@Observed
659class ClassA {
660  public id: number;
661  public c: number;
662
663  constructor(c: number) {
664    this.id = NextID++;
665    this.c = c;
666  }
667}
668
669@Component
670struct ViewA {
671  @ObjectLink a: ClassA;
672  label: string = "ViewA1";
673
674  build() {
675    Row() {
676      Button(`ViewA [${this.label}] this.a.c= ${this.a.c} +1`)
677        .onClick(() => {
678          // Change the object property.
679          this.a.c += 1;
680        })
681    }
682  }
683}
684
685@Entry
686@Component
687struct ViewB {
688  @State arrA: ClassA[] = [new ClassA(0), new ClassA(0)];
689
690  build() {
691    Column() {
692      ForEach(this.arrA,
693        (item: ClassA) => {
694          ViewA({ label: `#${item.id}`, a: item })
695        },
696        (item: ClassA): string => { return item.id.toString(); }
697      )
698
699      Divider().height(10)
700
701      if (this.arrA.length) {
702        ViewA({ label: `ViewA this.arrA[first]`, a: this.arrA[0] })
703        ViewA({ label: `ViewA this.arrA[last]`, a: this.arrA[this.arrA.length-1] })
704      }
705
706      Divider().height(10)
707
708      Button(`ViewB: reset array`)
709        .onClick(() => {
710          // Replace the entire array, which will be observed by @State this.arrA.
711          this.arrA = [new ClassA(0), new ClassA(0)];
712        })
713      Button(`array push`)
714        .onClick(() => {
715          // Insert data into the array, which will be observed by @State this.arrA.
716          this.arrA.push(new ClassA(0))
717        })
718      Button(`array shift`)
719        .onClick(() => {
720          // Remove data from the array, which will be observed by @State this.arrA.
721          this.arrA.shift()
722        })
723      Button(`ViewB: chg item property in middle`)
724        .onClick(() => {
725          // Replace an item in the array, which will be observed by @State this.arrA.
726          this.arrA[Math.floor(this.arrA.length / 2)] = new ClassA(11);
727        })
728      Button(`ViewB: chg item property in middle`)
729        .onClick(() => {
730          // Change property c of an item in the array, which will be observed by @ObjectLink in ViewA.
731          this.arrA[Math.floor(this.arrA.length / 2)].c = 10;
732        })
733    }
734  }
735}
736```
737
738In **ViewA**, replace \@ObjectLink with \@Prop.
739
740
741```ts
742@Component
743struct ViewA {
744
745  @Prop a: ClassA = new ClassA(0);
746  label : string = "ViewA1";
747
748  build() {
749     Row() {
750        Button(`ViewA [${this.label}] this.a.c= ${this.a.c} +1`)
751        .onClick(() => {
752            // Change the object property.
753            this.a.c += 1;
754        })
755     }
756  }
757}
758```
759
760When \@ObjectLink is used, if you click the first or second item of the array, the following two **ViewA** instances change synchronously.
761
762Unlike \@ObjectLink, \@Prop sets up a one-way data synchronization. Clicking the button in **ViewA** triggers only the re-rendering 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\<ClassA>, 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.
763
764Note the differences between \@Prop and \@ObjectLink: \@ObjectLink decorated variables are readable only and cannot be assigned values, whereas \@Prop decorated variables can be assigned values.
765
766- \@ObjectLink implements two-way data synchronization because it is initialized through a reference to the data source.
767
768- \@Prop implements one-way data synchronization and requires a deep copy of the data source.
769
770- To assign a new object to \@Prop is to overwrite the local value. However, for \@ObjectLink, 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.
771
772
773## Example
774
775
776The following example discusses the design of nested view models, especially how a custom component renders a nested object. This scenario is common in real-world application development.
777
778
779Let's develop a phonebook application to implement the following features:
780
781
782- Display the phone numbers of contacts and the local device ("Me").
783
784- You can select a contact and edit its information, including the phone number and address.
785
786- When you update contact information, the changes are saved only after you click **Save Changes**.
787
788- You can click **Delete Contact** to delete a contact from the contacts list.
789
790
791In this example, the view model needs to include the following:
792
793
794- **AddressBook** (class)
795  - **me**: stores a **Person** class.
796  - **contacts**: stores a **Person** class array.
797
798
799The **AddressBook** class is declared as follows:
800
801```ts
802export class AddressBook {
803  me: Person;
804  contacts: ObservedArray<Person>;
805  
806  constructor(me: Person, contacts: Person[]) {
807    this.me = me;
808    this.contacts = new ObservedArray<Person>(contacts);
809  }
810}
811```
812
813
814- Person (class)
815  - name : string
816  - address : Address
817  - phones: ObservedArray\<string>;
818  - Address (class)
819    - street : string
820    - zip : number
821    - city : string
822
823
824The **Address** class is declared as follows:
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```ts
847let nextId = 0;
848
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 to an **ObservedArray** class and decorate it with \@Observed. The **ObservedArray** class is declared as follows:
872
873```ts
874@Observed
875export class ObservedArray<T> extends Array<T> {
876  constructor(args: T[]) {
877    console.log(`ObservedArray: ${JSON.stringify(args)} `)
878    if (args instanceof Array) {
879      super(...args);
880    } else {
881      super(args)
882    }
883  }
884}
885```
886
887
888- **selected**: reference to **Person**.
889
890
891The update process is as follows:
892
893
8941. 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.
895   **PageEntry** and **AddressBookView** are declared as follows:
896
897
898   ```ts
899   @Component
900   struct AddressBookView {
901   
902       @ObjectLink me : Person;
903       @ObjectLink contacts : ObservedArray<Person>;
904       @State selectedPerson: Person = new Person("", "", 0, "", []);
905   
906       aboutToAppear() {
907           this.selectedPerson = this.me;
908       }
909   
910       build() {
911           Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Start}) {
912               Text("Me:")
913               PersonView({
914                person: this.me,
915                phones: this.me.phones,
916                selectedPerson: this.selectedPerson
917              })
918   
919               Divider().height(8)
920   
921              ForEach(this.contacts, (contact: Person) => {
922                PersonView({
923                  person: contact,
924                  phones: contact.phones as ObservedArray<string>,
925                  selectedPerson: this.selectedPerson
926                })
927              },
928                (contact: Person): string => { return contact.id_; }
929              )
930
931               Divider().height(8)
932   
933               Text("Edit:")
934               PersonEditView({ 
935                selectedPerson: this.selectedPerson, 
936                name: this.selectedPerson.name, 
937                address: this.selectedPerson.address, 
938                phones: this.selectedPerson.phones 
939              })
940           }
941               .borderStyle(BorderStyle.Solid).borderWidth(5).borderColor(0xAFEEEE).borderRadius(5)
942       }
943   }
944   
945   @Entry
946   @Component
947   struct PageEntry {
948     @Provide addrBook: AddressBook = new AddressBook(
949       new Person("Gigi", "Itamerenkatu 9", 180, "Helsinki", ["18*********", "18*********", "18*********"]),
950       [
951         new Person("Oly", "Itamerenkatu 9", 180, "Helsinki", ["18*********", "18*********"]),
952         new Person("Sam", "Itamerenkatu 9", 180, "Helsinki", ["18*********", "18*********"]),
953         new Person("Vivi", "Itamerenkatu 9", 180, "Helsinki", ["18*********", "18*********"]),
954       ]);
955   
956     build() {
957       Column() {
958         AddressBookView({ 
959          me: this.addrBook.me, 
960          contacts: this.addrBook.contacts, 
961          selectedPerson: this.addrBook.me 
962        })
963       }
964     }
965   }
966   ```
967
9682. **PersonView** is the view that shows a contact name and preferred phone number in the phonebook. When you select 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.
969   **PersonView** is declared as follows:
970
971
972   ```ts
973   // Display the contact name and preferred phone number.
974   // To update the phone number, @ObjectLink person and @ObjectLink phones are required.
975   // this.person.phones[0] cannot be used to display the preferred phone number because @ObjectLink person only proxies the Person property and cannot observe the changes inside the array.
976   // Trigger the onClick event to update selectedPerson.
977   @Component
978   struct PersonView {
979   
980       @ObjectLink person : Person;
981       @ObjectLink phones :  ObservedArray<string>;
982   
983       @Link selectedPerson : Person;
984   
985       build() {
986           Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
987             Text(this.person.name)
988             if (this.phones.length > 0) {
989               Text(this.phones[0])
990             }
991           }
992           .height(55)
993           .backgroundColor(this.selectedPerson.name == this.person.name ? "#ffa0a0" : "#ffffff")
994           .onClick(() => {
995               this.selectedPerson = this.person;
996           })
997       }
998   }
999   ```
1000
10013. The information about the selected contact (person) is displayed in the **PersonEditView** object. The data synchronization for the **PersonEditView** can be implemented as follows:
1002
1003   - 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 back 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).
1004
1005   - Through \@Link **seletedPerson: Person**, **PersonEditView** establishes two-way data synchronization with **selectedPerson** of **AddressBookView**. When you click **Save Changes**, the change to \@Prop is assigned to \@Link **seletedPerson: Person**. In this way, the data is synchronized back to the data source.
1006
1007   - In **PersonEditView**, \@Consume **addrBook: AddressBook** is used to set up two-way synchronization with the root node **PageEntry**. When you delete 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:
1008
1009     ```ts
1010     // Render the information about the contact (person).
1011     // 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.
1012     // Click Save Changes to copy all data to @Link through @Prop and synchronize the data to other components.
1013     @Component
1014     struct PersonEditView {
1015     
1016         @Consume addrBook : AddressBook;
1017     
1018         /* Reference pointing to selectedPerson in the parent component. */
1019         @Link selectedPerson: Person;
1020     
1021         /* Make changes on the local copy until you click Save Changes. */
1022         @Prop name: string = "";
1023         @Prop address : Address = new Address("", 0, "");
1024         @Prop phones : ObservedArray<string> = [];
1025     
1026         selectedPersonIndex() : number {
1027             return this.addrBook.contacts.findIndex((person: Person) => person.id_ == this.selectedPerson.id_);
1028         }
1029     
1030         build() {
1031             Column() {
1032                 TextInput({ text: this.name})
1033                     .onChange((value) => {
1034                         this.name = value;
1035                       })
1036                 TextInput({text: this.address.street})
1037                     .onChange((value) => {
1038                         this.address.street = value;
1039                     })
1040     
1041                 TextInput({text: this.address.city})
1042                     .onChange((value) => {
1043                         this.address.city = value;
1044                     })
1045     
1046                 TextInput({text: this.address.zip.toString()})
1047                     .onChange((value) => {
1048                         const result = Number.parseInt(value);
1049                         this.address.zip= Number.isNaN(result) ? 0 : result;
1050                     })
1051     
1052                 if (this.phones.length > 0) {
1053                   ForEach(this.phones,
1054                     (phone: ResourceStr, index?:number) => {
1055                       TextInput({ text: phone })
1056                         .width(150)
1057                         .onChange((value) => {
1058                           console.log(`${index}. ${value} value has changed`)
1059                           this.phones[index!] = value;
1060                         })
1061                     },
1062                     (phone: ResourceStr, index?:number) => `${index}`
1063                   )
1064                 }
1065
1066                 Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
1067                     Text("Save Changes")
1068                         .onClick(() => {
1069                             // Assign the updated value of the local copy to the reference pointing to selectedPerson in the parent component.
1070                             // Do not create new objects. Modify the properties of the existing objects instead.
1071                             this.selectedPerson.name = this.name;
1072                             this.selectedPerson.address = new Address(this.address.street, this.address.zip, this.address.city)
1073                             this.phones.forEach((phone : string, index : number) => { this.selectedPerson.phones[index] = phone } );
1074                         })
1075                     if (this.selectedPersonIndex()!=-1) {
1076                         Text("Delete Contact")
1077                             .onClick(() => {
1078                                 let index = this.selectedPersonIndex();
1079                                 console.log(`delete contact at index ${index}`);
1080     
1081                                 // Delete the current contact.
1082                                 this.addrBook.contacts.splice(index, 1);
1083     
1084                                 // Delete the current selectedPerson. The selected contact is then changed to the contact immediately before the deleted contact.
1085                                 index = (index < this.addrBook.contacts.length) ? index : index-1;
1086     
1087                                 // If all contracts are deleted, the me object is selected.
1088                                 this.selectedPerson = (index>=0) ? this.addrBook.contacts[index] : this.addrBook.me;
1089                             })
1090                     }
1091                 }
1092     
1093             }
1094         }
1095     }
1096     ```
1097
1098     Pay attention to the following differences between \@ObjectLink and \@Link:
1099
1100     1. To implement two-way data synchronization with the parent component, you need to use \@ObjectLink, instead of \@Link, to decorate **me: Person** and **contacts: ObservedArray\<Person>** in **AddressBookView**. The reasons are as follows:
1101        - 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.
1102        - \@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.
1103     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>**.
1104
1105     ![en-us_image_0000001605293914](figures/en-us_image_0000001605293914.png)
1106
1107     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, you can make reasonable separation of the data in the UI tree structure. You 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.
1108
1109     In this way, the UI re-render workload is minimized, leading to higher application performance.
1110
1111     The complete sample code is as follows:
1112
1113
1114```ts
1115// ViewModel classes
1116let nextId = 0;
1117
1118@Observed
1119export class ObservedArray<T> extends Array<T> {
1120  constructor(args: T[]) {
1121    console.log(`ObservedArray: ${JSON.stringify(args)} `)
1122    if (args instanceof Array) {
1123      super(...args);
1124    } else {
1125      super(args)
1126    }
1127  }
1128}
1129
1130@Observed
1131export class Address {
1132  street: string;
1133  zip: number;
1134  city: string;
1135
1136  constructor(street: string,
1137              zip: number,
1138              city: string) {
1139    this.street = street;
1140    this.zip = zip;
1141    this.city = city;
1142  }
1143}
1144
1145@Observed
1146export class Person {
1147  id_: string;
1148  name: string;
1149  address: Address;
1150  phones: ObservedArray<string>;
1151
1152  constructor(name: string,
1153              street: string,
1154              zip: number,
1155              city: string,
1156              phones: string[]) {
1157    this.id_ = `${nextId}`;
1158    nextId++;
1159    this.name = name;
1160    this.address = new Address(street, zip, city);
1161    this.phones = new ObservedArray<string>(phones);
1162  }
1163}
1164
1165export class AddressBook {
1166  me: Person;
1167  contacts: ObservedArray<Person>;
1168
1169  constructor(me: Person, contacts: Person[]) {
1170    this.me = me;
1171    this.contacts = new ObservedArray<Person>(contacts);
1172  }
1173}
1174
1175// Render the name of the Person object and the first phone number in the @Observed array <string>.
1176// To update the phone number, @ObjectLink person and @ObjectLink phones are required.
1177// this.person.phones cannot be used. Otherwise, changes to items inside the array will not be observed.
1178// Update selectedPerson in onClick in AddressBookView and PersonEditView.
1179@Component
1180struct PersonView {
1181  @ObjectLink person: Person;
1182  @ObjectLink phones: ObservedArray<string>;
1183  @Link selectedPerson: Person;
1184
1185  build() { 
1186    Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
1187      Text(this.person.name)
1188      if (this.phones.length) {
1189        Text(this.phones[0])
1190      }
1191    }
1192    .height(55)
1193    .backgroundColor(this.selectedPerson.name == this.person.name ? "#ffa0a0" : "#ffffff")
1194    .onClick(() => {
1195      this.selectedPerson = this.person;
1196    })
1197  }
1198}
1199
1200@Component
1201struct phonesNumber {
1202  @ObjectLink phoneNumber: ObservedArray<string>
1203
1204  build() {
1205    Column() {
1206
1207      ForEach(this.phoneNumber,
1208        (phone: ResourceStr, index?: number) => {
1209          TextInput({ text: phone })
1210            .width(150)
1211            .onChange((value) => {
1212              console.log(`${index}. ${value} value has changed`)
1213              this.phoneNumber[index!] = value;
1214            })
1215        },
1216        (phone: ResourceStr, index: number) => `${this.phoneNumber[index] + index}`
1217      )
1218    }
1219  }
1220}
1221
1222
1223// Render the information about the contact (person).
1224// 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.
1225// Click Save Changes to copy all data to @Link through @Prop and synchronize the data to other components.
1226@Component
1227struct PersonEditView {
1228  @Consume addrBook: AddressBook;
1229  /* Reference pointing to selectedPerson in the parent component. */
1230  @Link selectedPerson: Person;
1231  /* Make changes on the local copy until you click Save Changes. */
1232  @Prop name: string = "";
1233  @Prop address: Address = new Address("", 0, "");
1234  @Prop phones: ObservedArray<string> = [];
1235
1236  selectedPersonIndex(): number {
1237    return this.addrBook.contacts.findIndex((person: Person) => person.id_ == this.selectedPerson.id_);
1238  }
1239
1240  build() {
1241    Column() {
1242      TextInput({ text: this.name })
1243        .onChange((value) => {
1244          this.name = value;
1245        })
1246      TextInput({ text: this.address.street })
1247        .onChange((value) => {
1248          this.address.street = value;
1249        })
1250
1251      TextInput({ text: this.address.city })
1252        .onChange((value) => {
1253          this.address.city = value;
1254        })
1255
1256      TextInput({ text: this.address.zip.toString() })
1257        .onChange((value) => {
1258          const result = Number.parseInt(value);
1259          this.address.zip = Number.isNaN(result) ? 0 : result;
1260        })
1261
1262      if (this.phones.length > 0) {
1263        phonesNumber({ phoneNumber: this.phones })
1264      }
1265
1266      Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
1267        Text("Save Changes")
1268          .onClick(() => {
1269            // Assign the updated value of the local copy to the reference pointing to selectedPerson in the parent component.
1270            // Do not create new objects. Modify the properties of the existing objects instead.
1271            this.selectedPerson.name = this.name;
1272            this.selectedPerson.address = new Address(this.address.street, this.address.zip, this.address.city)
1273            this.phones.forEach((phone: string, index: number) => {
1274              this.selectedPerson.phones[index] = phone
1275            });
1276          })
1277        if (this.selectedPersonIndex() != -1) {
1278          Text("Delete Contact")
1279            .onClick(() => {
1280              let index = this.selectedPersonIndex();
1281              console.log(`delete contact at index ${index}`);
1282
1283              // Delete the current contact.
1284              this.addrBook.contacts.splice(index, 1);
1285
1286              // Delete the current selectedPerson. The selected contact is then changed to the contact immediately before the deleted contact.
1287              index = (index < this.addrBook.contacts.length) ? index : index - 1;
1288
1289              // If all contracts are deleted, the me object is selected.
1290              this.selectedPerson = (index >= 0) ? this.addrBook.contacts[index] : this.addrBook.me;
1291            })
1292        }
1293      }
1294
1295    }
1296  }
1297}
1298
1299@Component
1300struct AddressBookView {
1301  @ObjectLink me: Person;
1302  @ObjectLink contacts: ObservedArray<Person>;
1303  @State selectedPerson: Person = new Person("", "", 0, "", []);
1304
1305  aboutToAppear() {
1306    this.selectedPerson = this.me;
1307  }
1308
1309  build() {
1310    Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Start }) {
1311      Text("Me:")
1312      PersonView({
1313        person: this.me,
1314        phones: this.me.phones,
1315        selectedPerson: this.selectedPerson
1316      })
1317
1318      Divider().height(8)
1319
1320      ForEach(this.contacts, (contact: Person) => {
1321        PersonView({
1322          person: contact,
1323          phones: contact.phones as ObservedArray<string>,
1324          selectedPerson: this.selectedPerson
1325        })
1326      },
1327        (contact: Person): string => {
1328          return contact.id_;
1329        }
1330      )
1331
1332      Divider().height(8)
1333
1334      Text("Edit:")
1335      PersonEditView({
1336        selectedPerson: this.selectedPerson,
1337        name: this.selectedPerson.name,
1338        address: this.selectedPerson.address,
1339        phones: this.selectedPerson.phones
1340      })
1341    }
1342    .borderStyle(BorderStyle.Solid).borderWidth(5).borderColor(0xAFEEEE).borderRadius(5)
1343  }
1344}
1345
1346@Entry
1347@Component
1348struct PageEntry {
1349  @Provide addrBook: AddressBook = new AddressBook(
1350    new Person("Gigi", "Itamerenkatu 9", 180, "Helsinki", ["18*********", "18*********", "18*********"]),
1351    [
1352      new Person("Oly", "Itamerenkatu 9", 180, "Helsinki", ["11*********", "12*********"]),
1353      new Person("Sam", "Itamerenkatu 9", 180, "Helsinki", ["13*********", "14*********"]),
1354      new Person("Vivi", "Itamerenkatu 9", 180, "Helsinki", ["15*********", "168*********"]),
1355    ]);
1356
1357  build() {
1358    Column() {
1359      AddressBookView({
1360        me: this.addrBook.me,
1361        contacts: this.addrBook.contacts,
1362        selectedPerson: this.addrBook.me
1363      })
1364    }
1365  }
1366}
1367```
1368
1369