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