• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# LocalStorage: Storing Page-Level UI State
2
3
4LocalStorage provides storage for the page-level UI state. The parameters of the LocalStorage type accepted through the \@Entry decorator share the same LocalStorage instance on the page. LocalStorage also allows for state sharing across pages in a UIAbility instance.
5
6
7This topic focuses on the usage scenarios of LocalStorage and its associated decorators: \@LocalStorageProp and \@LocalStorageLink.
8
9
10Before reading this topic, you are advised to read [State Management Overview](./arkts-state-management-overview.md) to have a basic understanding of the positioning of AppStorage in the state management framework.
11
12LocalStorage also provides APIs for manual create, retrieve, update, delete (CRUD) operations outside custom components. For details, see [LocalStorage API Reference](../../reference/apis-arkui/arkui-ts/ts-state-management.md#localstorage9). For best practices, see [State Management](https://developer.huawei.com/consumer/en/doc/best-practices/bpta-status-management).
13
14> **NOTE**
15>
16> LocalStorage is supported since API version 9.
17
18
19## Overview
20
21LocalStorage is an in-memory "database" that ArkTS provides for storing state variables required to build pages of the application UI.
22
23- An application can create multiple LocalStorage instances. These instances can be shared on a page or, by using the **getSharedLocalStorage** API, across multiple pages and UIAbility instances.
24
25- The root node of a component tree, that is, the \@Component decorated by \@Entry, can be assigned to a LocalStorage instance. All child instances of this custom component automatically gain access to the same LocalStorage instance.
26
27- The \@Component decorated components can automatically inherit the LocalStorage instance from the parent component or receive the specified LocalStorage instance. For details, see [Providing a Custom Component with Access to a LocalStorage Instance](#providing-a-custom-component-with-access-to-a-localstorage-instance).
28
29- All properties in LocalStorage are mutable.
30
31The application determines the lifecycle of a LocalStorage object. The JS Engine will garbage collect a LocalStorage object when the application releases the last reference to it, which includes deleting the last custom component.
32
33LocalStorage provides two decorators based on the synchronization type of the component decorated with \@Component:
34
35- [@LocalStorageProp](#localstorageprop): creates a one-way data synchronization with the named property in LocalStorage.
36
37- [@LocalStorageLink](#localstoragelink): creates a two-way data synchronization with the named property in LocalStorage.
38
39
40## \@LocalStorageProp
41
42As mentioned above, if you want to establish a binding between LocalStorage and a custom component, you need to use the \@LocalStorageProp and \@LocalStorageLink decorators. Specially, use \@LocalStorageProp(key) or \@LocalStorageLink(key) to decorate variables in the component, where **key** identifies the property in LocalStorage.
43
44
45When a custom component is initialized, the \@LocalStorageProp(key)/\@LocalStorageLink(key) decorated variable is initialized with the value of the property with the given key in LocalStorage. Local initialization is mandatory. If an property with the given key is missing from LocalStorage, it will be added with the stated initializing value. (Whether the property with the given key exists in LocalStorage depends on the application logic.)
46
47
48> **NOTE**
49>
50> This decorator can be used in ArkTS widgets since API version 9.
51>
52> This decorator can be used in atomic services since API version 11.
53
54By decorating a variable with \@LocalStorageProp(key), a one-way data synchronization is established from the property with the given key in LocalStorage to the variable. This means that, local changes (if any) will not be synchronized to LocalStorage, and an update to the property with the given key in LocalStorage – for example, a change made with the **set** API – will overwrite local changes.
55
56
57### Rules for Using the \@LocalStorageProp Decorator
58
59| \@LocalStorageProp Decorator| Description                                                        |
60| ---------------------------- | ------------------------------------------------------------ |
61| Parameters                  | **key**: constant string, mandatory (the string must be quoted)                 |
62| Allowed variable types          | Object, class, string, number, Boolean, enum, and array of these types.<br>(Applicable to API version 12 or later) **Map**, **Set**, **Date**, **undefined**, and **null**. For details about the scenarios of nested objects, see [Observed Changes and Behavior](#observed-changes-and-behavior).<br>API version 12 or later: union types, for example, string \| number, string \| undefined, or ClassA \| null. For details, see [Using Union Types in LocalStorage](#using-union-types-in-localstorage).<br>**NOTE**<br>The variable type must be specified. Whenever possible, use the same type as that of the corresponding property in LocalStorage. Otherwise, implicit type conversion occurs, causing application behavior exceptions.<br>When **undefined** or **null** is used, you are advised to explicitly specify the type to pass the TypeScript type check. For example, **@LocalStorageProp("AA") a: number \| null = null** is supported, but **@LocalStorageProp("AA") a: number = null** is not.<br>**any** is not supported.|
63| Synchronization type                    | One-way: from the property in LocalStorage to the component variable. The component variable can be changed locally, but an update from LocalStorage will overwrite local changes.|
64| Initial value for the decorated variable          | Mandatory. If the property does not exist in LocalStorage, it will be created and initialized with this value.|
65
66
67### Variable Transfer/Access Rules
68
69| Behavior   | Description                                                                                 |
70| ---------- |-------------------------------------------------------------------------------------|
71| Initialization and update from the parent component| Forbidden.|
72| Child component initialization    | Supported. The \@LocalStorageProp decorated variable can be used to initialize an \@State, \@Link, \@Prop, or \@Provide decorated variable in the child component.                                          |
73| Access from outside the component | Not supported.                                                                                 |
74
75
76![en-us_image_0000001501936014](figures/en-us_image_0000001501936014.png)
77
78  **Figure 1** \@LocalStorageProp initialization rule
79
80### Observed Changes and Behavior
81
82**Observed Changes**
83
84
85- When the decorated variable is of the Boolean, string, or number type, its value change can be observed.
86
87- When the decorated variable is of the class or object type, its value change as well as value changes of all its properties can be observed. For details, see [Using LocalStorage from Inside the UI](#using-localstorage-from-inside-the-ui).
88
89- When the decorated object is an array, you can observe the changes of adding, deleting, and updating array units.
90
91- When the decorated object is of the **Date** type, the following changes can be observed: (1) complete **Date** object reassignment; (2) property changes caused by calling **setFullYear**, **setMonth**, **setDate**, **setHours**, **setMinutes**, **setSeconds**, **setMilliseconds**, **setTime**, **setUTCFullYear**, **setUTCMonth**, **setUTCDate**, **setUTCHours**, **setUTCMinutes**, **setUTCSeconds**, or **setUTCMilliseconds**. For details, see [Decorating Variables of the Date Type](#decorating-variables-of-the-date-type).
92
93- When the decorated object is of the **Map** type, the following changes can be observed: (1) complete **Map** object reassignment; (2) changes caused by calling **set**, **clear**, or **delete**. For details, see [Decorating Variables of the Map Type](#decorating-variables-of-the-map-type).
94
95- When the decorated object is of the **Set** type, the following changes can be observed: (1) complete **Set** object reassignment; (2) changes caused by calling **add**, **clear**, or **delete**. For details, see [Decorating Variables of the Set Type](#decorating-variables-of-the-set-type).
96
97
98**Framework Behavior**
99
100
1011. When a variable decorated by \@LocalStorageProp (key) is updated, LocalStorage is not written back, but the current custom component is re-rendered.
102
1032. When the value of the corresponding key in LocalStorage changes, all variables decorated by \@LocalStorageProp (key) are updated synchronously to overwrite the local modification.
104
105The following figure shows the data synchronization between LocalStorage and \@LocalStorageProp.
106
107![LocalStorageProp_framework_behavior](figures/LocalStorageProp_framework_behavior.png)
108
109  Figure 2 Data synchronization between LocalStorage and \@LocalStorageProp
110
111## \@LocalStorageLink
112
113> **NOTE**
114>
115> This decorator can be used in atomic services since API version 11.
116
117\@LocalStorageLink is required if you need to synchronize the changes of the state variables in a custom component back to LocalStorage.
118
119\@LocalStorageLink(key) creates a two-way data synchronization with the property with the given key in LocalStorage.
120
1211. Local changes are synchronized to LocalStorage.
122
1232. Changes in LocalStorage are synchronized to all properties with the given key, including one-way bound variables (\@LocalStorageProp decorated variables and one-way bound variables created through \@Prop) and two-way bound variables (\@LocalStorageLink decorated variables and two-way bound variables created through **link**).
124
125### Rules for Using the \@LocalStorageLink Decorator
126
127| \@LocalStorageLink Decorator| Description                                                        |
128| ---------------------------- | ------------------------------------------------------------ |
129| Parameters                  | **key**: constant string, mandatory (the string must be quoted)                 |
130| Allowed variable types          | Object, class, string, number, Boolean, enum, and array of these types.<br>(Applicable to API version 12 or later) **Map**, **Set**, **Date**, **undefined**, and **null**. For details about the scenarios of nested objects, see [Observed Changes and Behavior](#observed-changes-and-behavior-1).<br>(Applicable to API version 12 or later) Union type of the preceding types, for example, **string \| number, string \| undefined**, or **ClassA \| null. For details, see [Using Union Types in LocalStorage](#using-union-types-in-localstorage).<br>**Notice**<br>The variable type must be specified. Whenever possible, use the same type as that of the corresponding property in LocalStorage. Otherwise, implicit type conversion occurs, causing application behavior exceptions.<br>When **undefined** or **null** is used, you are advised to explicitly specify the type to pass the TypeScript type check. For example, **@LocalStorageLink("AA") a: number \| null = null** is supported, but **@LocalStorageProp("AA") a: number = null** is not.<br>**any** is not supported.|
131| Synchronization type                    | Two-way: from the property in LocalStorage to the custom component variable and back|
132| Initial value for the decorated variable          | Mandatory. If the property does not exist in LocalStorage, it will be created and initialized with this value.|
133
134
135### Variable Transfer/Access Rules
136
137| Behavior     | Description                                                                                 |
138| ---------- |-------------------------------------------------------------------------------------|
139| Initialization and update from the parent component| Forbidden.|
140| Child component initialization    | Supported. The \@StorageProp decorated variable can be used to initialize an \@State, \@Link, \@Prop, or \@Provide decorated variable in the child component.                                          |
141| Access from outside the component | No                                                                                 |
142
143
144![en-us_image_0000001552855957](figures/en-us_image_0000001552855957.png)
145
146  **Figure 3** \@LocalStorageLink initialization rule
147
148### Observed Changes and Behavior
149
150**Observed Changes**
151
152
153- When the decorated variable is of the Boolean, string, or number type, its value change can be observed.
154
155- When the decorated variable is of the class or object type, its value change as well as value changes of all its properties can be observed. For details, see [Using LocalStorage from Inside the UI](#using-localstorage-from-inside-the-ui).
156
157- When the decorated object is an array, you can observe the changes of adding, deleting, and updating array units.
158
159- When the decorated object is of the **Date** type, the following changes can be observed: (1) complete **Date** object reassignment; (2) property changes caused by calling **setFullYear**, **setMonth**, **setDate**, **setHours**, **setMinutes**, **setSeconds**, **setMilliseconds**, **setTime**, **setUTCFullYear**, **setUTCMonth**, **setUTCDate**, **setUTCHours**, **setUTCMinutes**, **setUTCSeconds**, or **setUTCMilliseconds**. For details, see [Decorating Variables of the Date Type](#decorating-variables-of-the-date-type).
160
161- When the decorated object is of the **Map** type, the following changes can be observed: (1) complete **Map** object reassignment; (2) changes caused by calling **set**, **clear**, or **delete**. For details, see [Decorating Variables of the Map Type](#decorating-variables-of-the-map-type).
162
163- When the decorated object is of the **Set** type, the following changes can be observed: (1) complete **Set** object reassignment; (2) changes caused by calling **add**, **clear**, or **delete**. For details, see [Decorating Variables of the Set Type](#decorating-variables-of-the-set-type).
164
165
166**Framework Behavior**
167
168
1691. When a variable decorated by \@LocalStorageLink(key) is updated, the change is synchronized back to the corresponding key in LocalStorage and triggers re-rendering of the owning custom component.
170
1712. When the value of a key in LocalStorage changes, all data bound to that key (including both two-way binding with \@LocalStorageLink and one-way binding with \@LocalStorageProp) will be synchronized.
172
173The figure below shows the data synchronization between LocalStorage and \@LocalStorageLink.
174
175![LocalStorageLink_framework_behavior](figures/LocalStorageLink_framework_behavior.png)
176
177  **Figure 4** Data synchronization between LocalStorage and \@LocalStorageLink
178
179## Constraints
180
1811. The parameter of \@LocalStorageProp and \@LocalStorageLink must be of the string type. Otherwise, an error is reported during compilation.
182
183    ```ts
184    let storage = new LocalStorage();
185    storage.setOrCreate('PropA', 48);
186
187    // Incorrect format. An error is reported during compilation.
188    @LocalStorageProp() localStorageProp: number = 1;
189    @LocalStorageLink() localStorageLink: number = 2;
190
191    // Correct usage.
192    @LocalStorageProp('PropA') localStorageProp: number = 1;
193    @LocalStorageLink('PropA') localStorageLink: number = 2;
194    ```
195
1962. \@LocalStorageProp and \@LocalStorageLink cannot decorate variables of the function type. Otherwise, the framework throws a runtime error.
197
1983. Once created, a named property cannot have its type changed. A value of same type must be used for subsequent calls to the Set method.
199
2004. LocalStorage is a page-level storage mechanism. The [getSharedLocalStorage](../../reference/apis-arkui/arkts-apis-uicontext-uicontext.md#getsharedlocalstorage12) API can only obtain the **LocalStorage** instance passed through [windowStage.loadContent](../../reference/apis-arkui/arkts-apis-window-Window.md#loadcontent9) in the current **Stage** context. Otherwise, it returns **undefined**. For the example, see [Sharing a LocalStorage Instance from UIAbility to Multiple Pages](#sharing-a-localstorage-instance-from-uiability-to-multiple-pages).
201
202
203## Use Scenarios
204
205
206### Using LocalStorage in Application Logic
207
208
209```ts
210let para: Record<string,number> = { 'PropA': 47 };
211let storage: LocalStorage = new LocalStorage(para); // Create an instance and initialize it with the given object.
212let propA: number | undefined = storage.get('PropA'); // propA == 47
213let link1: SubscribedAbstractProperty<number> = storage.link('PropA'); // link1.get() == 47
214let link2: SubscribedAbstractProperty<number> = storage.link('PropA'); // link2.get() == 47
215let prop: SubscribedAbstractProperty<number> = storage.prop('PropA'); // prop.get() == 47
216link1.set(48); // Two-way synchronization: link1.get() == link2.get() == prop.get() == 48
217prop.set(1); // One-way synchronization: prop.get() == 1; but link1.get() == link2.get() == 48
218link1.set(49); // Two-way synchronization: link1.get() == link2.get() == prop.get() == 49
219```
220
221
222### Using LocalStorage from Inside the UI
223
224The two decorators \@LocalStorageProp and \@LocalStorageLink can work together to obtain the state variable stored in a LocalStorage instance in the UI component.
225
226This example uses \@LocalStorageLink to implement the following:
227
228- Use the **build** function to create a LocalStorage instance named **storage**.
229
230- Use the \@Entry decorator to add **storage** to the top-level component **Parent**.
231
232- Use \@LocalStorageLink to create a two-way data synchronization with the given property in LocalStorage.
233
234 ```ts
235class Data {
236  code: number;
237
238  constructor(code: number) {
239    this.code = code;
240  }
241}
242// Create a new instance and initialize it with the given object.
243let para: Record<string, number> = { 'PropA': 47 };
244let storage: LocalStorage = new LocalStorage(para);
245storage.setOrCreate('PropB', new Data(50));
246
247@Component
248struct Child {
249  // @LocalStorageLink creates a two-way data synchronization with the PropA property in LocalStorage.
250  @LocalStorageLink('PropA') childLinkNumber: number = 1;
251  // @LocalStorageLink creates a two-way data synchronization with the PropB property in LocalStorage.
252  @LocalStorageLink('PropB') childLinkObject: Data = new Data(0);
253
254  build() {
255    Column({ space: 15 }) {
256      // The changes will be synchronized to PropA in LocalStorage and with Parent.storageLink1.
257      Button(`Child from LocalStorage ${this.childLinkNumber}`)
258        .onClick(() => {
259          this.childLinkNumber += 1;
260        })
261      // The changes will be synchronized to PropB and Parent.parentLinkObject.code in LocalStorage.
262      Button(`Child from LocalStorage ${this.childLinkObject.code}`)
263        .onClick(() => {
264          this.childLinkObject.code += 1;
265        })
266    }
267  }
268}
269// Make LocalStorage accessible from the @Component decorated component.
270@Entry(storage)
271@Component
272struct Parent {
273  // @LocalStorageLink creates a two-way data synchronization with the PropA property in LocalStorage.
274  @LocalStorageLink('PropA') parentLinkNumber: number = 1;
275  // @LocalStorageLink creates a two-way data synchronization with the PropB property in LocalStorage.
276  @LocalStorageLink('PropB') parentLinkObject: Data = new Data(0);
277
278  build() {
279    Column({ space: 15 }) {
280      // Because PropA in LocalStorage has been initialized, the value of this.parentLinkNumber is 47.
281      Button(`Parent from LocalStorage ${this.parentLinkNumber}`)
282        .onClick(() => {
283          this.parentLinkNumber += 1;
284        })
285      // Because PropB in LocalStorage has been initialized, the value of this.parentLinkObject.code is 50.
286      Button(`Parent from LocalStorage ${this.parentLinkObject.code}`)
287        .onClick(() => {
288          this.parentLinkObject.code += 1;
289        })
290      // The @Component decorated child component automatically obtains access to the Parent LocalStorage instance.
291      Child()
292    }
293  }
294}
295```
296
297
298### Implementing One-Way Synchronization with @LocalStorageProp and LocalStorage
299
300This example demonstrates one-way data synchronization between **Parent** and **Child** components and the **'PropA'** property in LocalStorage:
301
302- The change of **this.storageProp1** in **Parent** takes effect only in **Parent** and is not synchronized to **storage**.
303
304- In the **Child** component, the value of **storageProp2** bound to **Text** is still 47.
305
306```ts
307// Create a new instance and initialize it with the given object.
308let para: Record<string, number> = { 'PropA': 47 };
309let storage: LocalStorage = new LocalStorage(para);
310// Make LocalStorage accessible from the @Component decorated component.
311@Entry(storage)
312@Component
313struct Parent {
314  // @LocalStorageProp creates a one-way data synchronization with the PropA property in LocalStorage.
315  @LocalStorageProp('PropA') storageProp1: number = 1;
316
317  build() {
318    Column({ space: 15 }) {
319      // The initial value is 47. After the button is clicked, the value is incremented by 1. The change takes effect only in storageProp1 in the current component and is not synchronized to LocalStorage.
320      Button(`Parent from LocalStorage ${this.storageProp1}`)
321        .onClick(() => {
322          this.storageProp1 += 1;
323        })
324      Child()
325    }
326  }
327}
328
329@Component
330struct Child {
331  // @LocalStorageProp creates a one-way data synchronization with the PropA property in LocalStorage.
332  @LocalStorageProp('PropA') storageProp2: number = 2;
333
334  build() {
335    Column({ space: 15 }) {
336      // When Parent changes, the current storageProp2 does not change, and 47 is displayed.
337      Text(`Parent from LocalStorage ${this.storageProp2}`)
338    }
339  }
340}
341```
342
343
344### Implementing Two-Way Synchronization with @LocalStorageLink and LocalStorage
345
346This example shows how to create a two-way data synchronization between an \@LocalStorageLink decorated variable and LocalStorage.
347
348
349```ts
350// Create a LocalStorage instance.
351let para: Record<string, number> = { 'PropA': 47 };
352let storage: LocalStorage = new LocalStorage(para);
353// Call the link API (available since API version 9) to create a two-way data synchronization with PropA. linkToPropA is a global variable.
354let linkToPropA: SubscribedAbstractProperty<object> = storage.link('PropA');
355
356@Entry(storage)
357@Component
358struct Parent {
359
360  // @LocalStorageLink('PropA') creates a two-way synchronization with PropA in the Parent custom component. The initial value is 47, because PropA has been set to 47 during LocalStorage construction.
361  @LocalStorageLink('PropA') storageLink: number = 1;
362
363  build() {
364    Column() {
365      Text(`incr @LocalStorageLink variable`)
366        // Clicking incr @LocalStorageLink variable increases the value of this.storageLink by 1. The change is synchronized back to the storage. The global variable linkToPropA also changes.
367
368        .onClick(() => {
369          this.storageLink += 1;
370        })
371
372      // Avoid using the global variable linkToPropA.get() in the component. Doing so may cause errors due to different lifecycles.
373      Text(`@LocalStorageLink: ${this.storageLink} - linkToPropA: ${linkToPropA.get()}`)
374    }
375  }
376}
377```
378
379
380### Syncing State Variables Between Sibling Components
381
382This example shows how to use \@LocalStorageLink to create a two-way synchronization for the state between sibling components.
383
384Check the changes in the **Parent** custom component.
385
3861. Clicking **playCount ${this.playCount} dec by 1** decreases the value of **this.playCount** by 1. This change is synchronized to LocalStorage and to the components bound to **playCountLink** in the **Child** component.
387
3882. Click **countStorage ${this.playCount} incr by 1** to call the **set** API in LocalStorage to update the properties corresponding to **countStorage** in LocalStorage. The components bound to** playCountLink** in the **Child** component are updated synchronously.
389
3903. The **playCount in LocalStorage for debug ${storage.get&lt;number&gt;('countStorage')}** **Text** component is not updated synchronously, because **storage.get&lt;number&gt;('countStorage')** returns a regular variable. The update of a regular variable does not cause the **Text** component to be re-rendered.
391
392Changes in the **Child** custom component:
393
3941. The update of **playCountLink** is synchronized to LocalStorage, and the parent and sibling child custom components are re-rendered accordingly.
395
396```ts
397let count: Record<string, number> = { 'countStorage': 1 };
398let storage: LocalStorage = new LocalStorage(count);
399
400@Component
401struct Child {
402  // Name the child component instance.
403  label: string = 'no name';
404  // Two-way synchronization with countStorage in LocalStorage.
405  @LocalStorageLink('countStorage') playCountLink: number = 0;
406
407  build() {
408    Row() {
409      Text(this.label)
410        .width(50).height(60).fontSize(12)
411      Text(`playCountLink ${this.playCountLink}: inc by 1`)
412        .onClick(() => {
413          this.playCountLink += 1;
414        })
415        .width(200).height(60).fontSize(12)
416    }.width(300).height(60)
417  }
418}
419
420@Entry(storage)
421@Component
422struct Parent {
423  @LocalStorageLink('countStorage') playCount: number = 0;
424
425  build() {
426    Column() {
427      Row() {
428        Text('Parent')
429          .width(50).height(60).fontSize(12)
430        Text(`playCount ${this.playCount} dec by 1`)
431          .onClick(() => {
432            this.playCount -= 1;
433          })
434          .width(250).height(60).fontSize(12)
435      }.width(300).height(60)
436
437      Row() {
438        Text('LocalStorage')
439          .width(50).height(60).fontSize(12)
440        Text(`countStorage ${this.playCount} incr by 1`)
441          .onClick(() => {
442            storage.set<number | undefined>('countStorage', Number(storage.get<number>('countStorage')) + 1);
443          })
444          .width(250).height(60).fontSize(12)
445      }.width(300).height(60)
446
447      Child({ label: 'ChildA' })
448      Child({ label: 'ChildB' })
449
450      Text(`playCount in LocalStorage for debug ${storage.get<number>('countStorage')}`)
451        .width(300).height(60).fontSize(12)
452    }
453  }
454}
455```
456
457
458### Sharing a LocalStorage Instance from UIAbility to Multiple Pages
459
460In the preceding examples, the LocalStorage instance is shared only in an \@Entry decorated component and its child component (a page). To enable a LocalStorage instance to be shared across pages, you can create a LocalStorage instance in the owning UIAbility and pass it through windowStage.[loadContent](../../reference/apis-arkui/arkts-apis-window-Window.md#loadcontent9).
461
462
463```ts
464// EntryAbility.ets
465import { UIAbility } from '@kit.AbilityKit';
466import { window } from '@kit.ArkUI';
467
468export default class EntryAbility extends UIAbility {
469  para: Record<string, number> = {
470    'PropA': 47
471  };
472  storage: LocalStorage = new LocalStorage(this.para);
473
474  onWindowStageCreate(windowStage: window.WindowStage) {
475    windowStage.loadContent('pages/Index', this.storage);
476  }
477}
478```
479> **NOTE**
480>
481> To obtain the LocalStorage instance shared by the current stage in UI pages, use **getSharedLocalStorage**.
482>
483> **this.getUIContext().getSharedLocalStorage()** works only on emulators and real devices, not in DevEco Studio Previewer.
484
485
486In the following example, propA on the **Index** page uses a shared LocalStorage instance. Clicking the button navigates to the **Page** page. When you modify the value of **propA** using **Change propA** and then return to the **Index** page, the updated value of **propA** will be synchronized between the two pages.
487```ts
488// index.ets
489
490// The Previewer does not support accessing LocalStorage instances shared across pages.
491@Entry({ useSharedStorage: true })
492@Component
493struct Index {
494  // You can use @LocalStorageLink/Prop to establish a relationship with the variables in the LocalStorage instance.
495  @LocalStorageLink('PropA') propA: number = 1;
496  pageStack: NavPathStack = new NavPathStack();
497
498  build() {
499    Navigation(this.pageStack) {
500      Row(){
501        Column() {
502          Text(`${this.propA}`)
503            .fontSize(50)
504            .fontWeight(FontWeight.Bold)
505          Button("To Page")
506            .onClick(() => {
507              this.pageStack.pushPathByName('Page', null);
508            })
509        }
510        .width('100%')
511      }
512      .height('100%')
513    }
514  }
515}
516```
517
518```ts
519// Page.ets
520
521@Builder
522export function PageBuilder() {
523  Page()
524}
525
526// The Page component obtains the LocalStorage instance of the parent component Index.
527@Component
528struct Page {
529  @LocalStorageLink('PropA') propA: number = 2;
530  pathStack: NavPathStack = new NavPathStack();
531
532  build() {
533    NavDestination() {
534      Row(){
535        Column() {
536          Text(`${this.propA}`)
537            .fontSize(50)
538            .fontWeight(FontWeight.Bold)
539
540          Button("Change propA")
541            .onClick(() => {
542              this.propA = 100;
543            })
544
545          Button("Back Index")
546            .onClick(() => {
547              this.pathStack.pop();
548            })
549        }
550        .width('100%')
551      }
552    }
553    .onReady((context: NavDestinationContext) => {
554      this.pathStack = context.pathStack;
555    })
556  }
557}
558```
559When using **Navigation**, create a **route_map.json** file as shown below in the **src/main/resources/base/profile** directory, replacing the value of **pageSourceFile** with the actual path to **Page**. Then, add **"routerMap": "$profile: route_map"** to the **module.json5** file.
560```json
561{
562  "routerMap": [
563    {
564      "name": "Page",
565      "pageSourceFile": "src/main/ets/pages/Page.ets",
566      "buildFunction": "PageBuilder",
567      "data": {
568        "description" : "LocalStorage example"
569      }
570    }
571  ]
572}
573```
574
575> **NOTE**
576>
577> It is good practice to always create a LocalStorage instance with meaningful default values, which serve as a backup when execution exceptions occur and are also useful for unit testing of pages.
578
579
580### Providing a Custom Component with Access to a LocalStorage Instance
581
582LocalStorage instances are accessible to both root nodes through \@Entry and custom components (child nodes) through constructor parameters.
583
584This example uses \@LocalStorageLink to implement the following:
585
586- The text in the parent component reads **PropA**, value of **PropA** in the LocalStorage instance **localStorage1**.
587
588- The text in the **Child** component reads **PropB**, value of **PropB** in the LocalStorage instance **localStorage2**.
589
590> **NOTE**
591>
592> LocalStorage instances are accessible to custom components since API version 12.
593> When a custom child component has defined member properties, a LocalStorage instance must be passed in as the second parameter. Otherwise, a type mismatch error is reported at compile time.
594> Custom components with defined properties cannot accept a single LocalStorage instance as the only parameter. Components without defined properties may accept a single LocalStorage instance.
595> When properties do not require parent initialization, **{}** must be passed in as the first parameter.
596> The LocalStorage instance passed to child components as a constructor parameter is determined at initialization. You can use @LocalStorageLink or LocalStorage APIs to modify the property values stored in the LocalStorage instance, but the LocalStorage instance itself cannot be dynamically modified.
597
598```ts
599let localStorage1: LocalStorage = new LocalStorage();
600localStorage1.setOrCreate('PropA', 'PropA');
601
602let localStorage2: LocalStorage = new LocalStorage();
603localStorage2.setOrCreate('PropB', 'PropB');
604
605@Entry(localStorage1)
606@Component
607struct Index {
608  // PropA is in two-way synchronization with PropA in localStorage1.
609  @LocalStorageLink('PropA') PropA: string = 'Hello World';
610  @State count: number = 0;
611
612  build() {
613    Row() {
614      Column() {
615        Text(this.PropA)
616          .fontSize(50)
617          .fontWeight(FontWeight.Bold)
618        // Use the LocalStorage instance localStorage2.
619        Child({ count: this.count }, localStorage2)
620      }
621      .width('100%')
622    }
623    .height('100%')
624  }
625}
626
627
628@Component
629struct Child {
630  @Link count: number;
631  // 'Hello World' is in two-way synchronization with 'PropB' in localStorage2. If there is no 'PropB' in localStorage2, the default value 'Hello World' is used.
632  @LocalStorageLink('PropB') PropB: string = 'Hello World';
633
634  build() {
635    Text(this.PropB)
636      .fontSize(50)
637      .fontWeight(FontWeight.Bold)
638  }
639}
640```
641
6421. If a custom component does not have any property defined, it can accept a LocalStorage instance as the only input parameter.
643
644    ```ts
645    let localStorage1: LocalStorage = new LocalStorage();
646    localStorage1.setOrCreate('PropA', 'PropA');
647
648    let localStorage2: LocalStorage = new LocalStorage();
649    localStorage2.setOrCreate('PropB', 'PropB');
650
651    @Entry(localStorage1)
652    @Component
653    struct Index {
654      // PropA is in two-way synchronization with PropA in localStorage1.
655      @LocalStorageLink('PropA') PropA: string = 'Hello World';
656      @State count: number = 0;
657
658      build() {
659        Row() {
660          Column() {
661            Text(this.PropA)
662              .fontSize(50)
663              .fontWeight(FontWeight.Bold)
664            // Use the LocalStorage instance localStorage2.
665            Child(localStorage2)
666          }
667          .width('100%')
668        }
669        .height('100%')
670      }
671    }
672
673    @Component
674    struct Child {
675      build() {
676        Text("hello")
677          .fontSize(50)
678          .fontWeight(FontWeight.Bold)
679      }
680    }
681    ```
682
6832. If the defined property does not need to be initialized from the parent component, {} must be passed in as the first parameter.
684
685    ```ts
686    let localStorage1: LocalStorage = new LocalStorage();
687    localStorage1.setOrCreate('PropA', 'PropA');
688
689    let localStorage2: LocalStorage = new LocalStorage();
690    localStorage2.setOrCreate('PropB', 'PropB');
691
692    @Entry(localStorage1)
693    @Component
694    struct Index {
695      // PropA is in two-way synchronization with PropA in localStorage1.
696      @LocalStorageLink('PropA') PropA: string = 'Hello World';
697      @State count: number = 0;
698
699      build() {
700        Row() {
701          Column() {
702            Text(this.PropA)
703              .fontSize(50)
704              .fontWeight(FontWeight.Bold)
705            // Use the LocalStorage instance localStorage2.
706            Child({}, localStorage2)
707          }
708          .width('100%')
709        }
710        .height('100%')
711      }
712    }
713
714    @Component
715    struct Child {
716      @State count: number = 5;
717      // Hello World is in two-way synchronization with PropB in localStorage2. If there is no PropB in localStorage2, the default value Hello World is used.
718      @LocalStorageLink('PropB') PropB: string = 'Hello World';
719
720      build() {
721        Text(this.PropB)
722          .fontSize(50)
723          .fontWeight(FontWeight.Bold)
724      }
725    }
726    ```
727
728
729### Using LocalStorage with a Navigation Component
730
731You can pass multiple LocalStorage instances to a custom component and bind them to different target navigation pages, which can then display the property values of the bound instances.
732
733This example uses \@LocalStorageLink to implement the following:
734
735- Clicking **Next Page** in the parent component creates and redirects to the page named **pageOne**. The text displayed on the page is the value of **PropA** bound to the LocalStorage instance **localStorageA**, that is, **PropA**.
736
737- Clicking **Next Page** on the page creates and redirects to the page named **pageTwo**. The text displayed on the page is the value of **PropB** bound to the LocalStorage instance **localStorageB**, that is, **PropB**.
738
739- Clicking **Next Page** on the page again creates and redirects to the page named **pageTree**. The text displayed on the page is the value of **PropC** bound to the LocalStorage instance **localStorageC**, that is, **PropC**.
740
741- Clicking **Next Page** on the page again creates and redirects to the page named **pageOne**. The text displayed on the page is the value of **PropA** bound to the LocalStorage instance **localStorageA**, that is, **PropA**.
742
743- The **Text** component in the **NavigationContentMsgStack** custom component shares the value of **PropA** bound to the LocalStorage instance in the custom component tree.
744
745
746```ts
747let localStorageA: LocalStorage = new LocalStorage();
748localStorageA.setOrCreate('PropA', 'PropA');
749
750let localStorageB: LocalStorage = new LocalStorage();
751localStorageB.setOrCreate('PropB', 'PropB');
752
753let localStorageC: LocalStorage = new LocalStorage();
754localStorageC.setOrCreate('PropC', 'PropC');
755
756@Entry
757@Component
758struct MyNavigationTestStack {
759  @Provide('pageInfo') pageInfo: NavPathStack = new NavPathStack();
760
761  @Builder
762  PageMap(name: string) {
763    if (name === 'pageOne') {
764      // Pass multiple LocalStorage instances.
765      PageOneStack({}, localStorageA)
766    } else if (name === 'pageTwo') {
767      PageTwoStack({}, localStorageB)
768    } else if (name === 'pageThree') {
769      PageThreeStack({}, localStorageC)
770    }
771  }
772
773  build() {
774    Column({ space: 5 }) {
775      Navigation(this.pageInfo) {
776        Column() {
777          Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
778            .width('80%')
779            .height(40)
780            .margin(20)
781            .onClick(() => {
782              this.pageInfo.pushPath({ name: 'pageOne' }); // Push the navigation destination page specified by name to the navigation stack.
783            })
784        }
785      }.title('NavIndex')
786      .navDestination(this.PageMap)
787      .mode(NavigationMode.Stack)
788      .borderWidth(1)
789    }
790  }
791}
792
793@Component
794struct PageOneStack {
795  @Consume('pageInfo') pageInfo: NavPathStack;
796  @LocalStorageLink('PropA') PropA: string = 'Hello World';
797
798  build() {
799    NavDestination() {
800      Column() {
801        NavigationContentMsgStack()
802        // Display the value of PropA in the bound LocalStorage instance.
803        Text(`${this.PropA}`)
804        Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
805          .width('80%')
806          .height(40)
807          .margin(20)
808          .onClick(() => {
809            this.pageInfo.pushPathByName('pageTwo', null);
810          })
811      }.width('100%').height('100%')
812    }.title('pageOne')
813    .onBackPressed(() => {
814      this.pageInfo.pop();
815      return true;
816    })
817  }
818}
819
820@Component
821struct PageTwoStack {
822  @Consume('pageInfo') pageInfo: NavPathStack;
823  @LocalStorageLink('PropB') PropB: string = 'Hello World';
824
825  build() {
826    NavDestination() {
827      Column() {
828        NavigationContentMsgStack()
829        // If there is no PropB in the bound LocalStorage instance, the locally initialized value Hello World is displayed.
830        Text(`${this.PropB}`)
831        Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
832          .width('80%')
833          .height(40)
834          .margin(20)
835          .onClick(() => {
836            this.pageInfo.pushPathByName('pageThree', null);
837          })
838
839      }.width('100%').height('100%')
840    }.title('pageTwo')
841    .onBackPressed(() => {
842      this.pageInfo.pop();
843      return true;
844    })
845  }
846}
847
848@Component
849struct PageThreeStack {
850  @Consume('pageInfo') pageInfo: NavPathStack;
851  @LocalStorageLink('PropC') PropC: string = 'pageThreeStack';
852
853  build() {
854    NavDestination() {
855      Column() {
856        NavigationContentMsgStack()
857
858        // If there is no PropC in the bound LocalStorage instance, the locally initialized value pageThreeStack is displayed.
859        Text(`${this.PropC}`)
860        Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
861          .width('80%')
862          .height(40)
863          .margin(20)
864          .onClick(() => {
865            this.pageInfo.pushPathByName('pageOne', null);
866          })
867
868      }.width('100%').height('100%')
869    }.title('pageThree')
870    .onBackPressed(() => {
871      this.pageInfo.pop();
872      return true;
873    })
874  }
875}
876
877@Component
878struct NavigationContentMsgStack {
879  @LocalStorageLink('PropA') PropA: string = 'Hello';
880
881  build() {
882    Column() {
883      Text(`${this.PropA}`)
884        .fontSize(30)
885        .fontWeight(FontWeight.Bold)
886    }
887  }
888}
889```
890
891
892### Using Union Types in LocalStorage
893
894The following example demonstrates how to use union types in LocalStorage. The type of variable **A** is **number | null**, and the type of variable **B** is **number | undefined**. The **Text** components display **null** and **undefined** upon initialization, numbers when clicked, and **null** and **undefined** when clicked again.
895
896```ts
897@Component
898struct LocalStorageLinkComponent {
899  @LocalStorageLink("LinkA") LinkA: number | null = null;
900  @LocalStorageLink("LinkB") LinkB: number | undefined = undefined;
901
902  build() {
903    Column() {
904      Text("@LocalStorageLink initialization, @LocalStorageLink value")
905      Text(this.LinkA + "").fontSize(20).onClick(() => {
906        this.LinkA ? this.LinkA = null : this.LinkA = 1;
907      })
908      Text(this.LinkB + "").fontSize(20).onClick(() => {
909        this.LinkB ? this.LinkB = undefined : this.LinkB = 1;
910      })
911    }
912    .borderWidth(3).borderColor(Color.Green)
913
914  }
915}
916
917@Component
918struct LocalStoragePropComponent {
919  @LocalStorageProp("PropA") PropA: number | null = null;
920  @LocalStorageProp("PropB") PropB: number | undefined = undefined;
921
922  build() {
923    Column() {
924      Text("@LocalStorageProp initialization, @LocalStorageProp value")
925      Text(this.PropA + "").fontSize(20).onClick(() => {
926        this.PropA ? this.PropA = null : this.PropA = 1;
927      })
928      Text(this.PropB + "").fontSize(20).onClick(() => {
929        this.PropB ? this.PropB = undefined : this.PropB = 1;
930      })
931    }
932    .borderWidth(3).borderColor(Color.Yellow)
933
934  }
935}
936
937let storage: LocalStorage = new LocalStorage();
938
939@Entry(storage)
940@Component
941struct Index {
942  build() {
943    Row() {
944      Column() {
945        LocalStorageLinkComponent()
946        LocalStoragePropComponent()
947      }
948      .width('100%')
949    }
950    .height('100%')
951  }
952}
953```
954
955
956### Decorating Variables of the Date Type
957
958> **NOTE**
959>
960> LocalStorage supports the Date type since API version 12.
961
962In this example, the **selectedDate** variable decorated by \@LocalStorageLink is of the Date type. After the button is clicked, the value of **selectedDate** changes, and the UI is re-rendered.
963
964```ts
965@Entry
966@Component
967struct LocalDateSample {
968  @LocalStorageLink("date") selectedDate: Date = new Date('2021-08-08');
969
970  build() {
971    Column() {
972      Button('set selectedDate to 2023-07-08')
973        .margin(10)
974        .onClick(() => {
975          this.selectedDate = new Date('2023-07-08');
976        })
977      Button('increase the year by 1')
978        .margin(10)
979        .onClick(() => {
980          this.selectedDate.setFullYear(this.selectedDate.getFullYear() + 1);
981        })
982      Button('increase the month by 1')
983        .margin(10)
984        .onClick(() => {
985          this.selectedDate.setMonth(this.selectedDate.getMonth() + 1);
986        })
987      Button('increase the day by 1')
988        .margin(10)
989        .onClick(() => {
990          this.selectedDate.setDate(this.selectedDate.getDate() + 1);
991        })
992      DatePicker({
993        start: new Date('1970-1-1'),
994        end: new Date('2100-1-1'),
995        selected: $$this.selectedDate
996      })
997    }.width('100%')
998  }
999}
1000```
1001
1002
1003### Decorating Variables of the Map Type
1004
1005> **NOTE**
1006>
1007> LocalStorage supports the Map type since API version 12.
1008
1009In this example, the **message** variable decorated by @LocalStorageLink is of the **Map\<number, string\>** type. After the button is clicked, the value of **message** changes, and the UI is re-rendered.
1010
1011```ts
1012@Entry
1013@Component
1014struct LocalMapSample {
1015  @LocalStorageLink("map") message: Map<number, string> = new Map([[0, "a"], [1, "b"], [3, "c"]]);
1016
1017  build() {
1018    Row() {
1019      Column() {
1020        ForEach(Array.from(this.message.entries()), (item: [number, string]) => {
1021          Text(`${item[0]}`).fontSize(30)
1022          Text(`${item[1]}`).fontSize(30)
1023          Divider()
1024        })
1025        Button('init map').onClick(() => {
1026          this.message = new Map([[0, "a"], [1, "b"], [3, "c"]]);
1027        })
1028        Button('set new one').onClick(() => {
1029          this.message.set(4, "d");
1030        })
1031        Button('clear').onClick(() => {
1032          this.message.clear();
1033        })
1034        Button('replace the existing one').onClick(() => {
1035          this.message.set(0, "aa");
1036        })
1037        Button('delete the existing one').onClick(() => {
1038          this.message.delete(0);
1039        })
1040      }
1041      .width('100%')
1042    }
1043    .height('100%')
1044  }
1045}
1046```
1047
1048
1049### Decorating Variables of the Set Type
1050
1051> **NOTE**
1052>
1053> LocalStorage supports the Set type since API version 12.
1054
1055In this example, the **memberSet** variable decorated by @LocalStorageLink is of the **Set\<number\>** type. After the button is clicked, the value of **memberSet** changes, and the UI is re-rendered.
1056
1057```ts
1058@Entry
1059@Component
1060struct LocalSetSample {
1061  @LocalStorageLink("set") memberSet: Set<number> = new Set([0, 1, 2, 3, 4]);
1062
1063  build() {
1064    Row() {
1065      Column() {
1066        ForEach(Array.from(this.memberSet.entries()), (item: [number, string]) => {
1067          Text(`${item[0]}`)
1068            .fontSize(30)
1069          Divider()
1070        })
1071        Button('init set')
1072          .onClick(() => {
1073            this.memberSet = new Set([0, 1, 2, 3, 4]);
1074          })
1075        Button('set new one')
1076          .onClick(() => {
1077            this.memberSet.add(5);
1078          })
1079        Button('clear')
1080          .onClick(() => {
1081            this.memberSet.clear();
1082          })
1083        Button('delete the first one')
1084          .onClick(() => {
1085            this.memberSet.delete(0);
1086          })
1087      }
1088      .width('100%')
1089    }
1090    .height('100%')
1091  }
1092}
1093```
1094
1095### Changing State Variables Outside a Custom Component
1096
1097```ts
1098let storage = new LocalStorage();
1099storage.setOrCreate('count', 47);
1100
1101class Model {
1102  storage: LocalStorage = storage;
1103
1104  call(propName: string, value: number) {
1105    this.storage.setOrCreate<number>(propName, value);
1106  }
1107}
1108
1109let model: Model = new Model();
1110
1111@Entry({ storage: storage })
1112@Component
1113struct Test {
1114  @LocalStorageLink('count') count: number = 0;
1115
1116  build() {
1117    Column() {
1118      Text(`Value of count: ${this.count}`)
1119      Button('change')
1120        .onClick(() => {
1121          model.call('count', this.count + 1);
1122        })
1123    }
1124  }
1125}
1126```
1127
1128<!--no_check-->
1129