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 10> **NOTE** 11> 12> LocalStorage is supported since API version 9. 13 14 15## Overview 16 17LocalStorage is an in-memory "database" that ArkTS provides for storing state variables required to build pages of the application UI. 18 19- 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. 20 21- 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. 22 23- An \@Component decorated component has access to at most one LocalStorage instance and to [AppStorage](arkts-appstorage.md). A component not decorated with \@Entry cannot be assigned a LocalStorage instance. It can only accept a LocalStorage instance passed from its parent component through \@Entry. A LocalStorage instance can be assigned to multiple components in the component tree. 24 25- All attributes in LocalStorage are mutable. 26 27The 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. 28 29LocalStorage provides two decorators based on the synchronization type of the component decorated with \@Component: 30 31- [@LocalStorageProp](#localstorageprop): creates a one-way data synchronization with the named attribute in LocalStorage. 32 33- [@LocalStorageLink](#localstoragelink): creates a two-way data synchronization with the named attribute in LocalStorage. 34 35 36## Constraints 37 38- Once created, a named attribute cannot have its type changed. Subsequent calls to **Set** must set a value of same type. 39- 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). 40 41 42## \@LocalStorageProp 43 44As 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. 45 46 47When 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.) 48 49 50> **NOTE** 51> 52> This decorator can be used in ArkTS widgets since API version 9. 53> 54> This decorator can be used in atomic services since API version 11. 55 56By 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. 57 58 59### Rules of Use 60 61| \@LocalStorageProp Decorator | Description | 62| ----------------------- | ---------------------------------------- | 63| Decorator parameters | **key**: constant string, mandatory (the string must be quoted) | 64| 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. | 65| 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. | 66| Initial value for the decorated variable | Mandatory. If the attribute does not exist in LocalStorage, it will be created and initialized with this value. | 67 68 69### Variable Transfer/Access Rules 70 71| Transfer/Access | Description | 72| ---------- | ---------------------------------------- | 73| Initialization and update from the parent component | Forbidden. | 74| 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. | 75| Access from outside the component | Not supported. | 76 77 **Figure 1** \@LocalStorageProp initialization rule 78 79 80 81 82### Observed Changes and Behavior 83 84**Observed Changes** 85 86 87- When the decorated variable is of the Boolean, string, or number type, its value change can be observed. 88 89- 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). 90 91- When the decorated variable is of the array type, the addition, deletion, and updates of array items can be observed. 92 93- 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). 94 95- 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). 96 97- 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). 98 99 100**Framework Behavior** 101 102 103- Value changes of the variables decorated by \@LocalStorageProp are not synchronized to LocalStorage. 104 105- Value changes of the variables decorated by \@LocalStorageProp will cause a re-render of components associated with the current custom component. 106 107- 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. 108 109 110 111 112## \@LocalStorageLink 113 114> **NOTE** 115> 116> This decorator can be used in atomic services since API version 11. 117 118\@LocalStorageLink is required if you need to synchronize the changes of the state variables in a custom component back to LocalStorage. 119 120\@LocalStorageLink(key) creates a two-way data synchronization with the attribute with the given key in LocalStorage. 121 1221. If a local change occurs, it is synchronized to LocalStorage. 123 1242. 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). 125 126### Rules of Use 127 128| \@LocalStorageLink Decorator | Description | 129| ----------------------- | ---------------------------------------- | 130| Decorator parameters | **key**: constant string, mandatory (the string must be quoted) | 131| 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. | 132| Synchronization type | Two-way: from the attribute in LocalStorage to the custom component variable and back | 133| Initial value for the decorated variable | Mandatory. If the attribute does not exist in LocalStorage, it will be created and initialized with this value. | 134 135 136### Variable Transfer/Access Rules 137 138| Transfer/Access | Description | 139| ---------- | ---------------------------------------- | 140| Initialization and update from the parent component | Forbidden. | 141| 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. | 142| Access from outside the component | Not supported. | 143 144 145 **Figure 2** \@LocalStorageLink initialization rule 146 147 148 149 150 151### Observed Changes and Behavior 152 153**Observed Changes** 154 155 156- When the decorated variable is of the Boolean, string, or number type, its value change can be observed. 157 158- 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). 159 160- When the decorated variable is of the array type, the addition, deletion, and updates of array items can be observed. 161 162- 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). 163 164- 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). 165 166- 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). 167 168 169**Framework Behavior** 170 171 1721. 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. 173 1742. 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. 175 1763. 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. 177 178 179 180 181## Use Scenarios 182 183 184### Example of Using LocalStorage in Application Logic 185 186 187```ts 188let para: Record<string,number> = { 'PropA': 47 }; 189let storage: LocalStorage = new LocalStorage(para); // Create an instance and initialize it with the given object. 190let propA: number | undefined = storage.get('PropA') // propA == 47 191let link1: SubscribedAbstractProperty<number> = storage.link('PropA'); // link1.get() == 47 192let link2: SubscribedAbstractProperty<number> = storage.link('PropA'); // link2.get() == 47 193let prop: SubscribedAbstractProperty<number> = storage.prop('PropA'); // prop.get() == 47 194link1.set(48); // two-way sync: link1.get() == link2.get() == prop.get() == 48 195prop.set(1); // one-way sync: prop.get() == 1; but link1.get() == link2.get() == 48 196link1.set(49); // two-way sync: link1.get() == link2.get() == prop.get() == 49 197``` 198 199 200### Example for Using LocalStorage from Inside the UI 201 202The two decorators \@LocalStorageProp and \@LocalStorageLink can work together to obtain the state variable stored in a LocalStorage instance in the UI component. 203 204This example uses \@LocalStorageLink to implement the following: 205 206- Use the **build** function to create a LocalStorage instance named **storage**. 207 208- Use the \@Entry decorator to add **storage** to the top-level component **CompA**. 209 210- Use \@LocalStorageLink to create a two-way data synchronization with the given attribute in LocalStorage. 211 212 ```ts 213class PropB { 214 code: number; 215 216 constructor(code: number) { 217 this.code = code; 218 } 219} 220// Create a new instance and initialize it with the given object. 221let para: Record<string, number> = { 'PropA': 47 }; 222let storage: LocalStorage = new LocalStorage(para); 223storage.setOrCreate('PropB', new PropB(50)); 224 225@Component 226struct Child { 227 // @LocalStorageLink creates a two-way data synchronization with the PropA attribute in LocalStorage. 228 @LocalStorageLink('PropA') childLinkNumber: number = 1; 229 // @LocalStorageLink creates a two-way data synchronization with the PropB attribute in LocalStorage. 230 @LocalStorageLink('PropB') childLinkObject: PropB = new PropB(0); 231 232 build() { 233 Column() { 234 Button(`Child from LocalStorage ${this.childLinkNumber}`) // The changes will be synchronized to PropA in LocalStorage and with Parent.parentLinkNumber. 235 .onClick(() => { 236 this.childLinkNumber += 1; 237 }) 238 Button(`Child from LocalStorage ${this.childLinkObject.code}`) // The changes will be synchronized to PropB in LocalStorage and with Parent.parentLinkObject.code. 239 .onClick(() => { 240 this.childLinkObject.code += 1; 241 }) 242 } 243 } 244} 245// Make LocalStorage accessible from the @Component decorated component. 246@Entry(storage) 247@Component 248struct CompA { 249 // @LocalStorageLink creates a two-way data synchronization with the PropA attribute in LocalStorage. 250 @LocalStorageLink('PropA') parentLinkNumber: number = 1; 251 // @LocalStorageLink creates a two-way data synchronization with the PropB attribute in LocalStorage. 252 @LocalStorageLink('PropB') parentLinkObject: PropB = new PropB(0); 253 254 build() { 255 Column({ space: 15 }) { 256 Button(`Parent from LocalStorage ${this.parentLinkNumber}`) // initial value from LocalStorage will be 47, because 'PropA' initialized already 257 .onClick(() => { 258 this.parentLinkNumber += 1; 259 }) 260 261 Button(`Parent from LocalStorage ${this.parentLinkObject.code}`) // initial value from LocalStorage will be 50, because 'PropB' initialized already 262 .onClick(() => { 263 this.parentLinkObject.code += 1; 264 }) 265 // The @Component decorated child component automatically obtains access to the CompA LocalStorage instance. 266 Child() 267 } 268 } 269} 270``` 271 272 273### Simple Example of Using \@LocalStorageProp with LocalStorage 274 275In this example, the **CompA** and **Child** components create local data that is one-way synchronized with the PropA attribute in the LocalStorage instance **storage**. 276 277- The change of **this.storageProp1** in **CompA** takes effect only in **CompA** and is not synchronized to **storage**. 278 279- In the **Child** component, the value of **storageProp2** bound to **Text** is still 47. 280 281```ts 282// Create a new instance and initialize it with the given object. 283let para: Record<string, number> = { 'PropA': 47 }; 284let storage: LocalStorage = new LocalStorage(para); 285// Make LocalStorage accessible from the @Component decorated component. 286@Entry(storage) 287@Component 288struct CompA { 289 // @LocalStorageProp creates a one-way data synchronization with the PropA attribute in LocalStorage. 290 @LocalStorageProp('PropA') storageProp1: number = 1; 291 292 build() { 293 Column({ space: 15 }) { 294 // 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. 295 Button(`Parent from LocalStorage ${this.storageProp1}`) 296 .onClick(() => { 297 this.storageProp1 += 1 298 }) 299 Child() 300 } 301 } 302} 303 304@Component 305struct Child { 306 // @LocalStorageProp creates a one-way data synchronization with the PropA attribute in LocalStorage. 307 @LocalStorageProp('PropA') storageProp2: number = 2; 308 309 build() { 310 Column({ space: 15 }) { 311 // When CompA changes, the current storageProp2 does not change, and 47 is displayed. 312 Text(`Parent from LocalStorage ${this.storageProp2}`) 313 } 314 } 315} 316``` 317 318 319### Simple Example of Using \@LocalStorageLink and LocalStorage 320 321This example shows how to create a two-way data synchronization between an \@LocalStorageLink decorated variable and LocalStorage. 322 323 324```ts 325// Create a LocalStorage instance. 326let para: Record<string, number> = { 'PropA': 47 }; 327let storage: LocalStorage = new LocalStorage(para); 328// Call the link API (available since API version 9) to create a two-way data synchronization with PropA. linkToPropA is a global variable. 329let linkToPropA: SubscribedAbstractProperty<object> = storage.link('PropA'); 330 331@Entry(storage) 332@Component 333struct CompA { 334 335 // @LocalStorageLink('PropA') creates a two-way synchronization with PropA in the CompA custom component. The initial value is 47, because PropA has been set to 47 during LocalStorage construction. 336 @LocalStorageLink('PropA') storageLink: number = 1; 337 338 build() { 339 Column() { 340 Text(`incr @LocalStorageLink variable`) 341 // 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. 342 343 .onClick(() => { 344 this.storageLink += 1 345 }) 346 347 // Avoid using the global variable linkToPropA.get() in the component. Doing so may cause errors due to different lifecycles. 348 Text(`@LocalStorageLink: ${this.storageLink} - linkToPropA: ${linkToPropA.get()}`) 349 } 350 } 351} 352``` 353 354 355### Example of Syncing State Variables Between Sibling Components 356 357This example shows how to use \@LocalStorageLink to create a two-way synchronization for the state between sibling components. 358 359Check the changes in the **Parent** custom component. 360 3611. 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. 362 3632. 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. 364 3653. 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. 366 367Changes in the **Child** custom component: 368 3691. The update of **playCountLink** is synchronized to LocalStorage, and the parent and sibling child custom components are re-rendered accordingly. 370 371```ts 372let ls: Record<string, number> = { 'countStorage': 1 } 373let storage: LocalStorage = new LocalStorage(ls); 374 375@Component 376struct Child { 377 // Name the child component instance. 378 label: string = 'no name'; 379 // Two-way synchronization with countStorage in LocalStorage. 380 @LocalStorageLink('countStorage') playCountLink: number = 0; 381 382 build() { 383 Row() { 384 Text(this.label) 385 .width(50).height(60).fontSize(12) 386 Text(`playCountLink ${this.playCountLink}: inc by 1`) 387 .onClick(() => { 388 this.playCountLink += 1; 389 }) 390 .width(200).height(60).fontSize(12) 391 }.width(300).height(60) 392 } 393} 394 395@Entry(storage) 396@Component 397struct Parent { 398 @LocalStorageLink('countStorage') playCount: number = 0; 399 400 build() { 401 Column() { 402 Row() { 403 Text('Parent') 404 .width(50).height(60).fontSize(12) 405 Text(`playCount ${this.playCount} dec by 1`) 406 .onClick(() => { 407 this.playCount -= 1; 408 }) 409 .width(250).height(60).fontSize(12) 410 }.width(300).height(60) 411 412 Row() { 413 Text('LocalStorage') 414 .width(50).height(60).fontSize(12) 415 Text(`countStorage ${this.playCount} incr by 1`) 416 .onClick(() => { 417 storage.set<number | undefined>('countStorage', Number(storage.get<number>('countStorage')) + 1); 418 }) 419 .width(250).height(60).fontSize(12) 420 }.width(300).height(60) 421 422 Child({ label: 'ChildA' }) 423 Child({ label: 'ChildB' }) 424 425 Text(`playCount in LocalStorage for debug ${storage.get<number>('countStorage')}`) 426 .width(300).height(60).fontSize(12) 427 } 428 } 429} 430``` 431 432 433### Example of Sharing a LocalStorage Instance from UIAbility to One or More Pages 434 435In 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). 436 437 438```ts 439// EntryAbility.ets 440import { UIAbility } from '@kit.AbilityKit'; 441import { window } from '@kit.ArkUI'; 442 443export default class EntryAbility extends UIAbility { 444para:Record<string, number> = { 'PropA': 47 }; 445storage: LocalStorage = new LocalStorage(this.para); 446 447onWindowStageCreate(windowStage: window.WindowStage) { 448windowStage.loadContent('pages/Index', this.storage); 449} 450} 451``` 452> **NOTE** 453> 454> On the page, call the **getShared** API to obtain the LocalStorage instance shared through **loadContent**. 455> 456> **LocalStorage.getShared()** works only on emulators and real devices, not in DevEco Studio Previewer. 457 458 459In 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. 460```ts 461// index.ets 462import { router } from '@kit.ArkUI'; 463 464// Use the getShared API to obtain the LocalStorage instance shared by stage. 465let storage = LocalStorage.getShared() 466 467@Entry(storage) 468@Component 469struct Index { 470 // The LocalStorage instance can be accessed using 471 // @LocalStorageLink/Prop decorated variables. 472 @LocalStorageLink('PropA') propA: number = 1; 473 474 build() { 475 Row() { 476 Column() { 477 Text(`${this.propA}`) 478 .fontSize(50) 479 .fontWeight(FontWeight.Bold) 480 Button("To Page") 481 .onClick(() => { 482 router.pushUrl({ 483 url: 'pages/Page' 484 }) 485 }) 486 } 487 .width('100%') 488 } 489 .height('100%') 490 } 491} 492``` 493 494```ts 495// Page.ets 496import { router } from '@kit.ArkUI'; 497 498let storage = LocalStorage.getShared() 499 500@Entry(storage) 501@Component 502struct Page { 503 @LocalStorageLink('PropA') propA: number = 2; 504 505 build() { 506 Row() { 507 Column() { 508 Text(`${this.propA}`) 509 .fontSize(50) 510 .fontWeight(FontWeight.Bold) 511 512 Button("Change propA") 513 .onClick(() => { 514 this.propA = 100; 515 }) 516 517 Button("Back Index") 518 .onClick(() => { 519 router.back() 520 }) 521 } 522 .width('100%') 523 } 524 } 525} 526``` 527 528> **NOTE** 529> 530> 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. 531 532 533### Example of Providing a Custom Component with Access to a LocalStorage Instance 534 535LocalStorage instances are accessible to both root nodes through @Entry and custom components (child nodes) through constructors. 536 537This example uses \@LocalStorageLink to implement the following: 538 539- The text in the parent component reads **PropA**, value of **PropA** in the LocalStorage instance **localStorage1**. 540 541- The text in the **Child** component reads **PropB**, value of **PropB** in the LocalStorage instance **localStorage2**. 542 543> **NOTE** 544> 545> LocalStorage instances are accessible to custom components since API version 12. 546> 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. 547> 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. 548> If the defined attribute does not need to be initialized from the parent component, {} must be passed in as the first parameter. 549> 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. 550 551```ts 552let localStorage1: LocalStorage = new LocalStorage(); 553localStorage1.setOrCreate('PropA', 'PropA'); 554 555let localStorage2: LocalStorage = new LocalStorage(); 556localStorage2.setOrCreate('PropB', 'PropB'); 557 558@Entry(localStorage1) 559@Component 560struct Index { 561 // PropA is in two-way synchronization with PropA in localStorage1. 562 @LocalStorageLink('PropA') PropA: string = 'Hello World'; 563 @State count: number = 0; 564 565 build() { 566 Row() { 567 Column() { 568 Text(this.PropA) 569 .fontSize(50) 570 .fontWeight(FontWeight.Bold) 571 // Use the LocalStorage instance localStorage2. 572 Child({ count: this.count }, localStorage2) 573 } 574 .width('100%') 575 } 576 .height('100%') 577 } 578} 579 580 581@Component 582struct Child { 583 @Link count: number; 584 // 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. 585 @LocalStorageLink('PropB') PropB: string = 'Hello World'; 586 587 build() { 588 Text(this.PropB) 589 .fontSize(50) 590 .fontWeight(FontWeight.Bold) 591 } 592} 593``` 594 5951. If a custom component does not have any attribute defined, it can accept a LocalStorage instance as the only input parameter. 596 597```ts 598let localStorage1: LocalStorage = new LocalStorage(); 599localStorage1.setOrCreate('PropA', 'PropA'); 600 601let localStorage2: LocalStorage = new LocalStorage(); 602localStorage2.setOrCreate('PropB', 'PropB'); 603 604@Entry(localStorage1) 605@Component 606struct Index { 607 // PropA is in two-way synchronization with PropA in localStorage1. 608 @LocalStorageLink('PropA') PropA: string = 'Hello World'; 609 @State count: number = 0; 610 611 build() { 612 Row() { 613 Column() { 614 Text(this.PropA) 615 .fontSize(50) 616 .fontWeight(FontWeight.Bold) 617 // Use the LocalStorage instance localStorage2. 618 Child(localStorage2) 619 } 620 .width('100%') 621 } 622 .height('100%') 623 } 624} 625 626 627@Component 628struct Child { 629 build() { 630 Text("hello") 631 .fontSize(50) 632 .fontWeight(FontWeight.Bold) 633 } 634} 635``` 636 6372. If the defined attribute does not need to be initialized from the parent component, {} must be passed in as the first parameter. 638 639```ts 640let localStorage1: LocalStorage = new LocalStorage(); 641localStorage1.setOrCreate('PropA', 'PropA'); 642 643let localStorage2: LocalStorage = new LocalStorage(); 644localStorage2.setOrCreate('PropB', 'PropB'); 645 646@Entry(localStorage1) 647@Component 648struct Index { 649 // PropA is in two-way synchronization with PropA in localStorage1. 650 @LocalStorageLink('PropA') PropA: string = 'Hello World'; 651 @State count: number = 0; 652 653 build() { 654 Row() { 655 Column() { 656 Text(this.PropA) 657 .fontSize(50) 658 .fontWeight(FontWeight.Bold) 659 // Use the LocalStorage instance localStorage2. 660 Child({}, localStorage2) 661 } 662 .width('100%') 663 } 664 .height('100%') 665 } 666} 667 668 669@Component 670struct Child { 671 @State count: number = 5; 672 // 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. 673 @LocalStorageLink('PropB') PropB: string = 'Hello World'; 674 675 build() { 676 Text(this.PropB) 677 .fontSize(50) 678 .fontWeight(FontWeight.Bold) 679 } 680} 681``` 682 683 684### Using LocalStorage with a Navigation Component 685 686You 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. 687 688This example uses \@LocalStorageLink to implement the following: 689 690- 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**. 691 692- 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**. 693 694- 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**. 695 696- 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**. 697 698- The **Text** component in the **NavigationContentMsgStack** custom component shares the value of **PropA** bound to the LocalStorage instance in the custom component tree. 699 700 701```ts 702let localStorageA: LocalStorage = new LocalStorage(); 703localStorageA.setOrCreate('PropA', 'PropA'); 704 705let localStorageB: LocalStorage = new LocalStorage(); 706localStorageB.setOrCreate('PropB', 'PropB'); 707 708let localStorageC: LocalStorage = new LocalStorage(); 709localStorageC.setOrCreate('PropC', 'PropC'); 710 711@Entry 712@Component 713struct MyNavigationTestStack { 714 @Provide('pageInfo') pageInfo: NavPathStack = new NavPathStack(); 715 716 @Builder 717 PageMap(name: string) { 718 if (name === 'pageOne') { 719 // Pass multiple LocalStorage instances. 720 pageOneStack({}, localStorageA) 721 } else if (name === 'pageTwo') { 722 pageTwoStack({}, localStorageB) 723 } else if (name === 'pageThree') { 724 pageThreeStack({}, localStorageC) 725 } 726 } 727 728 build() { 729 Column({ space: 5 }) { 730 Navigation(this.pageInfo) { 731 Column() { 732 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 733 .width('80%') 734 .height(40) 735 .margin(20) 736 .onClick(() => { 737 this.pageInfo.pushPath({ name: 'pageOne' }); // Push the navigation destination page specified by name to the navigation stack. 738 }) 739 } 740 }.title('NavIndex') 741 .navDestination(this.PageMap) 742 .mode(NavigationMode.Stack) 743 .borderWidth(1) 744 } 745 } 746} 747 748@Component 749struct pageOneStack { 750 @Consume('pageInfo') pageInfo: NavPathStack; 751 @LocalStorageLink('PropA') PropA: string = 'Hello World'; 752 753 build() { 754 NavDestination() { 755 Column() { 756 NavigationContentMsgStack() 757 // Display the value of PropA in the bound LocalStorage instance. 758 Text(`${this.PropA}`) 759 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 760 .width('80%') 761 .height(40) 762 .margin(20) 763 .onClick(() => { 764 this.pageInfo.pushPathByName('pageTwo', null); 765 }) 766 }.width('100%').height('100%') 767 }.title('pageOne') 768 .onBackPressed(() => { 769 this.pageInfo.pop(); 770 return true; 771 }) 772 } 773} 774 775@Component 776struct pageTwoStack { 777 @Consume('pageInfo') pageInfo: NavPathStack; 778 @LocalStorageLink('PropB') PropB: string = 'Hello World'; 779 780 build() { 781 NavDestination() { 782 Column() { 783 NavigationContentMsgStack() 784 // There is no PropA in the bound LocalStorage instance, and therefore the locally initialized value Hello World is displayed. 785 Text(`${this.PropB}`) 786 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 787 .width('80%') 788 .height(40) 789 .margin(20) 790 .onClick(() => { 791 this.pageInfo.pushPathByName('pageThree', null); 792 }) 793 794 }.width('100%').height('100%') 795 }.title('pageTwo') 796 .onBackPressed(() => { 797 this.pageInfo.pop(); 798 return true; 799 }) 800 } 801} 802 803@Component 804struct pageThreeStack { 805 @Consume('pageInfo') pageInfo: NavPathStack; 806 @LocalStorageLink('PropC') PropC: string = 'pageThreeStack'; 807 808 build() { 809 NavDestination() { 810 Column() { 811 NavigationContentMsgStack() 812 813 // There is no PropA in the bound LocalStorage instance, and therefore the locally initialized value pageThreeStack is displayed. 814 Text(`${this.PropC}`) 815 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 816 .width('80%') 817 .height(40) 818 .margin(20) 819 .onClick(() => { 820 this.pageInfo.pushPathByName('pageOne', null); 821 }) 822 823 }.width('100%').height('100%') 824 }.title('pageThree') 825 .onBackPressed(() => { 826 this.pageInfo.pop(); 827 return true; 828 }) 829 } 830} 831 832@Component 833struct NavigationContentMsgStack { 834 @LocalStorageLink('PropA') PropA: string = 'Hello'; 835 836 build() { 837 Column() { 838 Text(`${this.PropA}`) 839 .fontSize(30) 840 .fontWeight(FontWeight.Bold) 841 } 842 } 843} 844``` 845 846 847### Union Type 848 849In 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. 850 851```ts 852@Component 853struct LocalStorLink { 854 @LocalStorageLink("AA") A: number | null = null; 855 @LocalStorageLink("BB") B: number | undefined = undefined; 856 857 build() { 858 Column() { 859 Text("@LocalStorageLink initialization, @LocalStorageLink value") 860 Text(this.A + "").fontSize(20).onClick(() => { 861 this.A ? this.A = null : this.A = 1; 862 }) 863 Text(this.B + "").fontSize(20).onClick(() => { 864 this.B ? this.B = undefined : this.B = 1; 865 }) 866 } 867 .borderWidth(3).borderColor(Color.Green) 868 869 } 870} 871 872@Component 873struct LocalStorProp { 874 @LocalStorageProp("AAA") A: number | null = null; 875 @LocalStorageProp("BBB") B: number | undefined = undefined; 876 877 build() { 878 Column() { 879 Text("@LocalStorageProp initialization, @LocalStorageProp value") 880 Text(this.A + "").fontSize(20).onClick(() => { 881 this.A ? this.A = null : this.A = 1; 882 }) 883 Text(this.B + "").fontSize(20).onClick(() => { 884 this.B ? this.B = undefined : this.B = 1; 885 }) 886 } 887 .borderWidth(3).borderColor(Color.Yellow) 888 889 } 890} 891 892let storage1: LocalStorage = new LocalStorage(); 893 894@Entry(storage1) 895@Component 896struct TestCase3 { 897 build() { 898 Row() { 899 Column() { 900 LocalStorLink() 901 LocalStorProp() 902 } 903 .width('100%') 904 } 905 .height('100%') 906 } 907} 908``` 909 910 911### Decorating Variables of the Date Type 912 913> **NOTE** 914> 915> LocalStorage supports the Date type since API version 12. 916 917In 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. 918 919```ts 920@Entry 921@Component 922struct LocalDateSample { 923 @LocalStorageLink("date") selectedDate: Date = new Date('2021-08-08'); 924 925 build() { 926 Column() { 927 Button('set selectedDate to 2023-07-08') 928 .margin(10) 929 .onClick(() => { 930 this.selectedDate = new Date('2023-07-08'); 931 }) 932 Button('increase the year by 1') 933 .margin(10) 934 .onClick(() => { 935 this.selectedDate.setFullYear(this.selectedDate.getFullYear() + 1); 936 }) 937 Button('increase the month by 1') 938 .margin(10) 939 .onClick(() => { 940 this.selectedDate.setMonth(this.selectedDate.getMonth() + 1); 941 }) 942 Button('increase the day by 1') 943 .margin(10) 944 .onClick(() => { 945 this.selectedDate.setDate(this.selectedDate.getDate() + 1); 946 }) 947 DatePicker({ 948 start: new Date('1970-1-1'), 949 end: new Date('2100-1-1'), 950 selected: $$this.selectedDate 951 }) 952 }.width('100%') 953 } 954} 955``` 956 957 958### Decorating Variables of the Map Type 959 960> **NOTE** 961> 962> LocalStorage supports the Map type since API version 12. 963 964In 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. 965 966```ts 967@Entry 968@Component 969struct LocalMapSample { 970 @LocalStorageLink("map") message: Map<number, string> = new Map([[0, "a"], [1, "b"], [3, "c"]]); 971 972 build() { 973 Row() { 974 Column() { 975 ForEach(Array.from(this.message.entries()), (item: [number, string]) => { 976 Text(`${item[0]}`).fontSize(30) 977 Text(`${item[1]}`).fontSize(30) 978 Divider() 979 }) 980 Button('init map').onClick(() => { 981 this.message = new Map([[0, "a"], [1, "b"], [3, "c"]]); 982 }) 983 Button('set new one').onClick(() => { 984 this.message.set(4, "d"); 985 }) 986 Button('clear').onClick(() => { 987 this.message.clear(); 988 }) 989 Button('replace the existing one').onClick(() => { 990 this.message.set(0, "aa"); 991 }) 992 Button('delete the existing one').onClick(() => { 993 this.message.delete(0); 994 }) 995 } 996 .width('100%') 997 } 998 .height('100%') 999 } 1000} 1001``` 1002 1003 1004### Decorating Variables of the Set Type 1005 1006> **NOTE** 1007> 1008> LocalStorage supports the Set type since API version 12. 1009 1010In 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. 1011 1012```ts 1013@Entry 1014@Component 1015struct LocalSetSample { 1016 @LocalStorageLink("set") memberSet: Set<number> = new Set([0, 1, 2, 3, 4]); 1017 1018 build() { 1019 Row() { 1020 Column() { 1021 ForEach(Array.from(this.memberSet.entries()), (item: [number, string]) => { 1022 Text(`${item[0]}`) 1023 .fontSize(30) 1024 Divider() 1025 }) 1026 Button('init set') 1027 .onClick(() => { 1028 this.memberSet = new Set([0, 1, 2, 3, 4]); 1029 }) 1030 Button('set new one') 1031 .onClick(() => { 1032 this.memberSet.add(5); 1033 }) 1034 Button('clear') 1035 .onClick(() => { 1036 this.memberSet.clear(); 1037 }) 1038 Button('delete the first one') 1039 .onClick(() => { 1040 this.memberSet.delete(0); 1041 }) 1042 } 1043 .width('100%') 1044 } 1045 .height('100%') 1046 } 1047} 1048``` 1049 1050<!--no_check--> 1051