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 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 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 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 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<number>('countStorage')}** **Text** component is not updated synchronously, because **storage.get<number>('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