1# LocalStorage: Storing 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 between pages with UIAbility instances. 5 6 7This topic describes only the LocalStorage application scenarios and related 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 state management framework. 11 12LocalStorage also provides APIs for you to manually add, delete, change, and query keys of Storage outside the custom component. You are advised to read this topic together with [LocalStorage API reference](../reference/apis-arkui/arkui-ts/ts-state-management.md#localstorage9). 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 **getShared** API, across pages in a UIAbility instance. 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 [Example of Providing a Custom Component with Access to a LocalStorage Instance](#example-of-providing-a-custom-component-with-access-to-a-localstorage-instance). 28 29- All attributes 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 attribute in LocalStorage. 36 37- [@LocalStorageLink](#localstoragelink): creates a two-way data synchronization with the named attribute 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 attribute in LocalStorage. 43 44 45When a custom component is initialized, the \@LocalStorageProp(key)/\@LocalStorageLink(key) decorated variable is initialized with the value of the attribute with the given key in LocalStorage. Local initialization is mandatory. If an attribute with the given key is missing from LocalStorage, it will be added with the stated initializing value. (Whether the attribute 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 attribute 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 attribute with the given key in LocalStorage – for example, a change made with the **set** API – will overwrite local changes. 55 56 57### Rules of Use 58 59| \@LocalStorageProp Decorator| Description | 60| ----------------------- | ---------------------------------------- | 61| Decorator 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, and Date types. For details about the scenarios of nested objects, see [Observed Changes and Behavior](#observed-changes-and-behavior).<br>The type must be specified. Whenever possible, use the same type as that of the corresponding attribute in LocalStorage. Otherwise, implicit type conversion occurs, causing application behavior exceptions.<br>**any** is not supported. **undefined** and **null** are supported since API version 12.<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 [Union Type @LocalStorage](#union-type).<br>**NOTE**<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 recommended; **@LocalStorageProp("AA") a: number = null** is not recommended.| 63| Synchronization type | One-way: from the attribute 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 attribute does not exist in LocalStorage, it will be created and initialized with this value.| 65 66 67### Variable Transfer/Access Rules 68 69| Transfer/Access | 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 **Figure 1** \@LocalStorageProp initialization rule 76 77 78 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 attributes can be observed. For details, see [Example for Using LocalStorage from Inside the UI](#example-for-using-localstorage-from-inside-the-ui). 88 89- When the decorated variable is of the array type, the addition, deletion, and updates of array items can be observed. 90 91- When the decorated object is of the **Date** type, the overall value changes of **Date** can be observed. In addition, you can call the following APIs to update **Date** properties: **setFullYear**, **setMonth**, **setDate**, **setHours**, **setMinutes**, **setSeconds**, **setMilliseconds**, **setTime**, **setUTCFullYear**, **setUTCMonth**, **setUTCDate**, **setUTCHours**, **setUTCMinutes**, **setUTCSeconds**, and **setUTCMilliseconds**. For details, see [Decorating Variables of the Date Type](#decorating-variables-of-the-date-type). 92 93- When the decorated variable is **Map**, value changes of **Map** can be observed. In addition, you can call the **set**, **clear**, and **delete** APIs of **Map** to update its value. For details, see [Decorating Variables of the Map Type](#decorating-variables-of-the-map-type). 94 95- When the decorated variable is **Set**, value changes of **Set** can be observed. In addition, you can call the **add**, **clear**, and **delete** APIs of **Set** to update its value. For details, see [Decorating Variables of the Set Type](#decorating-variables-of-the-set-type). 96 97 98**Framework Behavior** 99 100 101- Value changes of the variables decorated by \@LocalStorageProp are not synchronized to LocalStorage. 102 103- Value changes of the variables decorated by \@LocalStorageProp will cause a re-render of components associated with the current custom component. 104 105- When an attribute with the given key in LocalStorage is updated, the change is synchronized to all the \@LocalStorageProp(key) decorated variables and overwrite all local changes of these variables. 106 107 108 109 110## \@LocalStorageLink 111 112> **NOTE** 113> 114> This decorator can be used in atomic services since API version 11. 115 116\@LocalStorageLink is required if you need to synchronize the changes of the state variables in a custom component back to LocalStorage. 117 118\@LocalStorageLink(key) creates a two-way data synchronization with the attribute with the given key in LocalStorage. 119 1201. If a local change occurs, it is synchronized to LocalStorage. 121 1222. Changes in LocalStorage are synchronized to all attributes 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). 123 124### Rules of Use 125 126| \@LocalStorageLink Decorator| Description | 127| ----------------------- | ---------------------------------------- | 128| Decorator parameters | **key**: constant string, mandatory (the string must be quoted) | 129| Allowed variable types | Object, class, string, number, Boolean, enum, and array of these types.<br>(Applicable to API version 12 or later) Map, Set, and Date types. For details about the scenarios of nested objects, see [Observed Changes and Behavior](#observed-changes-and-behavior).<br>The type must be specified. Whenever possible, use the same type as that of the corresponding attribute in LocalStorage. Otherwise, implicit type conversion occurs, causing application behavior exceptions.<br>**any** is not supported. **undefined** and **null** are supported since API version 12.<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 [Union Type @LocalStorage](#union-type).<br>**NOTE**<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 recommended. **@LocalStorageLink("AA") a: number = null** is not recommended.| 130| Synchronization type | Two-way: from the attribute in LocalStorage to the custom component variable and back| 131| Initial value for the decorated variable | Mandatory. If the attribute does not exist in LocalStorage, it will be created and initialized with this value.| 132 133 134### Variable Transfer/Access Rules 135 136| Transfer/Access | Description | 137| ---------- | ---------------------------------------- | 138| Initialization and update from the parent component| Forbidden.| 139| 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.| 140| Access from outside the component | Not supported. | 141 142 143 **Figure 2** \@LocalStorageLink initialization rule 144 145 146 147 148 149### Observed Changes and Behavior 150 151**Observed Changes** 152 153 154- When the decorated variable is of the Boolean, string, or number type, its value change can be observed. 155 156- When the decorated variable is of the class or object type, its value change as well as value changes of all its attributes can be observed. For details, see [Example for Using LocalStorage from Inside the UI](#example-for-using-localstorage-from-inside-the-ui). 157 158- When the decorated variable is of the array type, the addition, deletion, and updates of array items can be observed. 159 160- When the decorated object is of the **Date** type, the overall value changes of **Date** can be observed. In addition, you can call the following APIs to update **Date** properties: **setFullYear**, **setMonth**, **setDate**, **setHours**, **setMinutes**, **setSeconds**, **setMilliseconds**, **setTime**, **setUTCFullYear**, **setUTCMonth**, **setUTCDate**, **setUTCHours**, **setUTCMinutes**, **setUTCSeconds**, and **setUTCMilliseconds**. For details, see [Decorating Variables of the Date Type](#decorating-variables-of-the-date-type). 161 162- When the decorated variable is **Map**, value changes of **Map** can be observed. In addition, you can call the **set**, **clear**, and **delete** APIs of **Map** to update its value. For details, see [Decorating Variables of the Map Type](#decorating-variables-of-the-map-type). 163 164- When the decorated variable is **Set**, value changes of **Set** can be observed. In addition, you can call the **add**, **clear**, and **delete** APIs of **Set** to update its value. For details, see [Decorating Variables of the Set Type](#decorating-variables-of-the-set-type). 165 166 167**Framework Behavior** 168 169 1701. When the value change of the \@LocalStorageLink(key) decorated variable is observed, the change is synchronized to the attribute with the give key value in LocalStorage. 171 1722. Once the attribute with the given key in LocalStorage is updated, all the data (including \@LocalStorageLink and \@LocalStorageProp decorated variables) bound to the attribute key is changed synchronously. 173 1743. When the data decorated by \@LocalStorageLink(key) is a state variable, the change of the data is synchronized to LocalStorage, and the owning custom component is re-rendered. 175 176 177 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 184let storage = new LocalStorage(); 185storage.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 format. 192@LocalStorageProp('PropA') localStorageProp: number = 1; 193@LocalStorageLink('PropA') localStorageLink: number = 2; 194``` 195 1962. \@StorageProp and \@StorageLink cannot decorate variables of the function type. Otherwise, the framework throws a runtime error. 197 1983. Once created, a named attribute cannot have its type changed. Subsequent calls to **Set** must set a value of same type. 199 2004. LocalStorage provides page-level storage. The [getShared](../reference/apis-arkui/arkui-ts/ts-state-management.md#getshared10) API can only obtain the LocalStorage instance passed through [windowStage.loadContent](../reference/apis-arkui/js-apis-window.md#loadcontent9) in the current stage. If the instance is not available, **undefined** is returned. For the example, see [Example of Sharing a LocalStorage Instance from UIAbility to One or More Pages](#example-of-sharing-a-localstorage-instance-from-uiability-to-one-or-more-pages). 201 202 203## Use Scenarios 204 205 206### Example of 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### Example for 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 attribute 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 attribute in LocalStorage. 250 @LocalStorageLink('PropA') childLinkNumber: number = 1; 251 // @LocalStorageLink creates a two-way data synchronization with the PropB attribute in LocalStorage. 252 @LocalStorageLink('PropB') childLinkObject: Data = new Data(0); 253 254 build() { 255 Column({ space: 15 }) { 256 Button(`Child from LocalStorage ${this.childLinkNumber}`) // The changes will be synchronized to PropA in LocalStorage and with Parent.parentLinkNumber. 257 .onClick(() => { 258 this.childLinkNumber += 1; 259 }) 260 261 Button(`Child from LocalStorage ${this.childLinkObject.code}`) // The changes will be synchronized to PropB in LocalStorage and with Parent.parentLinkObject.code. 262 .onClick(() => { 263 this.childLinkObject.code += 1; 264 }) 265 } 266 } 267} 268// Make LocalStorage accessible from the @Component decorated component. 269@Entry(storage) 270@Component 271struct Parent { 272 // @LocalStorageLink creates a two-way data synchronization with the PropA attribute in LocalStorage. 273 @LocalStorageLink('PropA') parentLinkNumber: number = 1; 274 // @LocalStorageLink creates a two-way data synchronization with the PropB attribute in LocalStorage. 275 @LocalStorageLink('PropB') parentLinkObject: Data = new Data(0); 276 277 build() { 278 Column({ space: 15 }) { 279 Button(`Parent from LocalStorage ${this.parentLinkNumber}`) // The value of this.parentLinkNumber is 47 because PropA in LocalStorage has been initialized. 280 .onClick(() => { 281 this.parentLinkNumber += 1; 282 }) 283 284 Button(`Parent from LocalStorage ${this.parentLinkObject.code}`) // The value of this.parentLinkObject.code is 50 because PropB in LocalStorage has been initialized. 285 .onClick(() => { 286 this.parentLinkObject.code += 1; 287 }) 288 // The @Component decorated child component automatically obtains access to the Parent LocalStorage instance. 289 Child() 290 } 291 } 292} 293 ``` 294 295 296### Simple Example of Using \@LocalStorageProp with LocalStorage 297 298In this example, the **Parent** and **Child** components create local data that is one-way synchronized with the PropA attribute in the LocalStorage instance **storage**. 299 300- The change of **this.storageProp1** in **Parent** takes effect only in **Parent** and is not synchronized to **storage**. 301 302- In the **Child** component, the value of **storageProp2** bound to **Text** is still 47. 303 304```ts 305// Create a new instance and initialize it with the given object. 306let para: Record<string, number> = { 'PropA': 47 }; 307let storage: LocalStorage = new LocalStorage(para); 308// Make LocalStorage accessible from the @Component decorated component. 309@Entry(storage) 310@Component 311struct Parent { 312 // @LocalStorageProp creates a one-way data synchronization with the PropA attribute in LocalStorage. 313 @LocalStorageProp('PropA') storageProp1: number = 1; 314 315 build() { 316 Column({ space: 15 }) { 317 // 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. 318 Button(`Parent from LocalStorage ${this.storageProp1}`) 319 .onClick(() => { 320 this.storageProp1 += 1; 321 }) 322 Child() 323 } 324 } 325} 326 327@Component 328struct Child { 329 // @LocalStorageProp creates a one-way data synchronization with the PropA attribute in LocalStorage. 330 @LocalStorageProp('PropA') storageProp2: number = 2; 331 332 build() { 333 Column({ space: 15 }) { 334 // When Parent changes, the current storageProp2 does not change, and 47 is displayed. 335 Text(`Parent from LocalStorage ${this.storageProp2}`) 336 } 337 } 338} 339``` 340 341 342### Simple Example of Using \@LocalStorageLink with LocalStorage 343 344This example shows how to create a two-way data synchronization between an \@LocalStorageLink decorated variable and LocalStorage. 345 346 347```ts 348// Create a LocalStorage instance. 349let para: Record<string, number> = { 'PropA': 47 }; 350let storage: LocalStorage = new LocalStorage(para); 351// Call the link API (available since API version 9) to create a two-way data synchronization with PropA. linkToPropA is a global variable. 352let linkToPropA: SubscribedAbstractProperty<object> = storage.link('PropA'); 353 354@Entry(storage) 355@Component 356struct Parent { 357 358 // @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. 359 @LocalStorageLink('PropA') storageLink: number = 1; 360 361 build() { 362 Column() { 363 Text(`incr @LocalStorageLink variable`) 364 // 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. 365 366 .onClick(() => { 367 this.storageLink += 1; 368 }) 369 370 // Avoid using the global variable linkToPropA.get() in the component. Doing so may cause errors due to different lifecycles. 371 Text(`@LocalStorageLink: ${this.storageLink} - linkToPropA: ${linkToPropA.get()}`) 372 } 373 } 374} 375``` 376 377 378### Example of Syncing State Variables Between Sibling Components 379 380This example shows how to use \@LocalStorageLink to create a two-way synchronization for the state between sibling components. 381 382Check the changes in the **Parent** custom component. 383 3841. 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. 385 3862. Click **countStorage ${this.playCount} incr by 1** to call the **set** API in LocalStorage to update the attributes corresponding to **countStorage** in LocalStorage. The components bound to** playCountLink** in the **Child** component are updated synchronously. 387 3883. 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. 389 390Changes in the **Child** custom component: 391 3921. The update of **playCountLink** is synchronized to LocalStorage, and the parent and sibling child custom components are re-rendered accordingly. 393 394```ts 395let count: Record<string, number> = { 'countStorage': 1 }; 396let storage: LocalStorage = new LocalStorage(count); 397 398@Component 399struct Child { 400 // Name the child component instance. 401 label: string = 'no name'; 402 // Two-way synchronization with countStorage in LocalStorage. 403 @LocalStorageLink('countStorage') playCountLink: number = 0; 404 405 build() { 406 Row() { 407 Text(this.label) 408 .width(50).height(60).fontSize(12) 409 Text(`playCountLink ${this.playCountLink}: inc by 1`) 410 .onClick(() => { 411 this.playCountLink += 1; 412 }) 413 .width(200).height(60).fontSize(12) 414 }.width(300).height(60) 415 } 416} 417 418@Entry(storage) 419@Component 420struct Parent { 421 @LocalStorageLink('countStorage') playCount: number = 0; 422 423 build() { 424 Column() { 425 Row() { 426 Text('Parent') 427 .width(50).height(60).fontSize(12) 428 Text(`playCount ${this.playCount} dec by 1`) 429 .onClick(() => { 430 this.playCount -= 1; 431 }) 432 .width(250).height(60).fontSize(12) 433 }.width(300).height(60) 434 435 Row() { 436 Text('LocalStorage') 437 .width(50).height(60).fontSize(12) 438 Text(`countStorage ${this.playCount} incr by 1`) 439 .onClick(() => { 440 storage.set<number | undefined>('countStorage', Number(storage.get<number>('countStorage')) + 1); 441 }) 442 .width(250).height(60).fontSize(12) 443 }.width(300).height(60) 444 445 Child({ label: 'ChildA' }) 446 Child({ label: 'ChildB' }) 447 448 Text(`playCount in LocalStorage for debug ${storage.get<number>('countStorage')}`) 449 .width(300).height(60).fontSize(12) 450 } 451 } 452} 453``` 454 455 456### Example of Sharing a LocalStorage Instance from UIAbility to One or More Pages 457 458In 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 its owning UIAbility and call windowStage.[loadContent](../reference/apis-arkui/js-apis-window.md#loadcontent9). 459 460 461```ts 462// EntryAbility.ets 463import { UIAbility } from '@kit.AbilityKit'; 464import { window } from '@kit.ArkUI'; 465 466export default class EntryAbility extends UIAbility { 467 para: Record<string, number> = { 468 'PropA': 47 469 }; 470 storage: LocalStorage = new LocalStorage(this.para); 471 472 onWindowStageCreate(windowStage: window.WindowStage) { 473 windowStage.loadContent('pages/Index', this.storage); 474 } 475} 476``` 477> **NOTE** 478> 479> On the page, call the **getShared** API to obtain the LocalStorage instance shared through **loadContent**. 480> 481> **LocalStorage.getShared()** works only on emulators and real devices, not in DevEco Studio Previewer. 482 483 484In the following example, **propA** on the **Index** page uses the **getShared()** API to obtain the shared LocalStorage instance. Click the button to go to the **Page** page. Click **Change propA** and then return to the **Index** page. It can be observed that the value of **propA** on the page is changed. 485```ts 486// index.ets 487 488// Use the getShared API to obtain the LocalStorage instance shared by stage. 489@Entry({ storage: LocalStorage.getShared() }) 490@Component 491struct Index { 492 // You can use @LocalStorageLink/Prop to establish a relationship with the variables in the LocalStorage instance. 493 @LocalStorageLink('PropA') propA: number = 1; 494 pageStack: NavPathStack = new NavPathStack(); 495 496 build() { 497 Navigation(this.pageStack) { 498 Row(){ 499 Column() { 500 Text(`${this.propA}`) 501 .fontSize(50) 502 .fontWeight(FontWeight.Bold) 503 Button("To Page") 504 .onClick(() => { 505 this.pageStack.pushPathByName('Page', null); 506 }) 507 } 508 .width('100%') 509 } 510 .height('100%') 511 } 512 } 513} 514``` 515 516```ts 517// Page.ets 518 519@Builder 520export function PageBuilder() { 521 Page() 522} 523 524// The Page component obtains the LocalStorage instance of the parent component Index. 525@Component 526struct Page { 527 @LocalStorageLink('PropA') propA: number = 2; 528 pathStack: NavPathStack = new NavPathStack(); 529 530 build() { 531 NavDestination() { 532 Row(){ 533 Column() { 534 Text(`${this.propA}`) 535 .fontSize(50) 536 .fontWeight(FontWeight.Bold) 537 538 Button("Change propA") 539 .onClick(() => { 540 this.propA = 100; 541 }) 542 543 Button("Back Index") 544 .onClick(() => { 545 this.pathStack.pop(); 546 }) 547 } 548 .width('100%') 549 } 550 } 551 .onReady((context: NavDestinationContext) => { 552 this.pathStack = context.pathStack; 553 }) 554 } 555} 556``` 557When using **Navigation**, you need to add the **route_map.json** file to the **src/main/resources/base/profile** directory, replace the value of **pageSourceFile** with the path of **Page**, and add **"routerMap": "$profile: route_map"** to the **module.json5** file. 558```json 559{ 560 "routerMap": [ 561 { 562 "name": "Page", 563 "pageSourceFile": "src/main/ets/pages/Page.ets", 564 "buildFunction": "PageBuilder", 565 "data": { 566 "description" : "LocalStorage example" 567 } 568 } 569 ] 570} 571``` 572 573> **NOTE** 574> 575> 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. 576 577 578### Example of Providing a Custom Component with Access to a LocalStorage Instance 579 580LocalStorage instances are accessible to both root nodes through @Entry and custom components (child nodes) through constructors. 581 582This example uses \@LocalStorageLink to implement the following: 583 584- The text in the parent component reads **PropA**, value of **PropA** in the LocalStorage instance **localStorage1**. 585 586- The text in the **Child** component reads **PropB**, value of **PropB** in the LocalStorage instance **localStorage2**. 587 588> **NOTE** 589> 590> LocalStorage instances are accessible to custom components since API version 12. 591> If a custom component functions as a subnode and has member attributes defined, a LocalStorage instance must be passed in as the second parameter. Otherwise, a type mismatch error is reported at compile time. 592> If a custom component has any attribute defined, it does not accept a LocalStorage instance as the only input parameter. If a custom component does not have any attribute defined, it can accept a LocalStorage instance as the only input parameter. 593> If the defined attribute does not need to be initialized from the parent component, {} must be passed in as the first parameter. 594> The LocalStorage instance that is passed to a child component as a constructor parameter is determined at initialization. You can use @LocalStorageLink or the API of LocalStorage to modify the attribute values stored in the LocalStorage instance, but the LocalStorage instance itself cannot be dynamically modified. 595 596```ts 597let localStorage1: LocalStorage = new LocalStorage(); 598localStorage1.setOrCreate('PropA', 'PropA'); 599 600let localStorage2: LocalStorage = new LocalStorage(); 601localStorage2.setOrCreate('PropB', 'PropB'); 602 603@Entry(localStorage1) 604@Component 605struct Index { 606 // PropA is in two-way synchronization with PropA in localStorage1. 607 @LocalStorageLink('PropA') PropA: string = 'Hello World'; 608 @State count: number = 0; 609 610 build() { 611 Row() { 612 Column() { 613 Text(this.PropA) 614 .fontSize(50) 615 .fontWeight(FontWeight.Bold) 616 // Use the LocalStorage instance localStorage2. 617 Child({ count: this.count }, localStorage2) 618 } 619 .width('100%') 620 } 621 .height('100%') 622 } 623} 624 625 626@Component 627struct Child { 628 @Link count: number; 629 // 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. 630 @LocalStorageLink('PropB') PropB: string = 'Hello World'; 631 632 build() { 633 Text(this.PropB) 634 .fontSize(50) 635 .fontWeight(FontWeight.Bold) 636 } 637} 638``` 639 6401. If a custom component does not have any attribute defined, it can accept a LocalStorage instance as the only input parameter. 641 642```ts 643let localStorage1: LocalStorage = new LocalStorage(); 644localStorage1.setOrCreate('PropA', 'PropA'); 645 646let localStorage2: LocalStorage = new LocalStorage(); 647localStorage2.setOrCreate('PropB', 'PropB'); 648 649@Entry(localStorage1) 650@Component 651struct Index { 652 // PropA is in two-way synchronization with PropA in localStorage1. 653 @LocalStorageLink('PropA') PropA: string = 'Hello World'; 654 @State count: number = 0; 655 656 build() { 657 Row() { 658 Column() { 659 Text(this.PropA) 660 .fontSize(50) 661 .fontWeight(FontWeight.Bold) 662 // Use the LocalStorage instance localStorage2. 663 Child(localStorage2) 664 } 665 .width('100%') 666 } 667 .height('100%') 668 } 669} 670 671 672@Component 673struct Child { 674 build() { 675 Text("hello") 676 .fontSize(50) 677 .fontWeight(FontWeight.Bold) 678 } 679} 680``` 681 6822. If the defined attribute does not need to be initialized from the parent component, {} must be passed in as the first parameter. 683 684```ts 685let localStorage1: LocalStorage = new LocalStorage(); 686localStorage1.setOrCreate('PropA', 'PropA'); 687 688let localStorage2: LocalStorage = new LocalStorage(); 689localStorage2.setOrCreate('PropB', 'PropB'); 690 691@Entry(localStorage1) 692@Component 693struct Index { 694 // PropA is in two-way synchronization with PropA in localStorage1. 695 @LocalStorageLink('PropA') PropA: string = 'Hello World'; 696 @State count: number = 0; 697 698 build() { 699 Row() { 700 Column() { 701 Text(this.PropA) 702 .fontSize(50) 703 .fontWeight(FontWeight.Bold) 704 // Use the LocalStorage instance localStorage2. 705 Child({}, localStorage2) 706 } 707 .width('100%') 708 } 709 .height('100%') 710 } 711} 712 713 714@Component 715struct 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 attribute 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### Union Type 893 894In the following example, 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 LocalStorLink { 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 LocalStorProp { 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 LocalStorLink() 946 LocalStorProp() 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