1# makeObserved API: Changing Unobservable Data to Observable Data 2 3To change the unobservable data to observable data, you can use the [makeObserved](../reference/apis-arkui/js-apis-StateManagement.md#makeobserved12) API. 4 5 6**makeObserved** can be used when \@Trace cannot be used. Before reading this topic, you are advised to read [\@Trace](./arkts-new-observedV2-and-trace.md). 7 8>**NOTE** 9> 10>The **makeObserved** API in UIUtils is supported since API version 12. 11 12## Overview 13 14- The state management framework provides [@ObservedV2 and @Trace](./arkts-new-observedV2-and-trace.md) decorators to observe class property changes. The **makeObserved** API is mainly used in scenarios where @ObservedV2 or @Trace cannot be used. For example: 15 16 - Object in a third-party package defined by class is unobservable. You cannot manually add the @Trace tag to the attributes to be observed in the class, so **makeObserved** can be used to make this object observable. 17 18 - The member property of the current class cannot be modified. This is because @Trace will dynamically change the class property when it observes. But this behavior is not allowed in the @Sendable decorated class, therefore, you can use **makeObserved** instead. 19 20 - Anonymous object returned by API or JSON.parse does not have a class declaration. In this scenario, you cannot use @Trace to mark that the current attribute, therefore, **makeObserved** can be used instead. 21 22 23- To use the **makeObserved** API, you need to import UIUtils. 24 ```ts 25 import { UIUtils } from '@kit.ArkUI'; 26 ``` 27 28## Constraints 29 30- The parameters of **makeObserved** support only non-null object types. 31 - Undefined and null: not supported. The parameters itself is returned and no processing is performed. 32 - Non-object type: An error is reported during compilation. 33 34 ```ts 35 import { UIUtils } from '@kit.ArkUI'; 36 let res1 = UIUtils.makeObserved(2); // Invalid input parameter. An error is reported during compilation. 37 let res2 = UIUtils.makeObserved(undefined); // Invalid input parameter. The parameter itself is returned, that is, res2 = = = undefined. 38 let res3 = UIUtils.makeObserved(null); // Invalid input parameter. The parameter itself is returned, that is, res3 === null. 39 40 class Info { 41 id: number = 0; 42 } 43 let rawInfo: Info = UIUtils.makeObserved(new Info()); // Correct usage. 44 ``` 45 46- Instances of classes decorated by [@ObservedV2](./arkts-new-observedV2-and-trace.md) and [@Observed](./arkts-observed-and-objectlink.md) and proxy data that has been encapsulated by **makeObserved** cannot be passed in and are directly returned without processing to prevent dual proxies. 47 ```ts 48 import { UIUtils } from '@kit.ArkUI'; 49 @ObservedV2 50 class Info { 51 @Trace id: number = 0; 52 } 53 // Incorrect usage: If makeObserved finds that the input instance is an instance of a class decorated by @ObservedV2, makeObserved returns the input object itself. 54 let observedInfo: Info = UIUtils.makeObserved(new Info()); 55 56 class Info2 { 57 id: number = 0; 58 } 59 // Correct usage. The input object is neither an instance of the class decorated by @ObservedV2 or @Observed nor the proxy data encapsulated by makeObserved. 60 // Return observable data. 61 let observedInfo1: Info2 = UIUtils.makeObserved(new Info2()); 62 // Incorrect usage. The input object is the proxy data encapsulated by makeObserved, which is not processed this time. 63 let observedInfo2: Info2 = UIUtils.makeObserved(observedInfo1); 64 ``` 65- **makeObserved** can be used in custom components decorated by @Component, but cannot be used together with the state variable decorator in state management V1. If they are used together, a runtime exception is thrown. 66 ```ts 67 // Incorrect usage. An exception occurs during running. 68 @State message: Info = UIUtils.makeObserved(new Info(20)); 69 ``` 70 The following **message2** does not throw an exception because **this.message** is decorated by @State and its implementation is the same as @Observed, while the input parameter of **UIUtils.makeObserved** is the @Observed decorated class, which is returned directly. Therefore, the initial value of **message2** is not the return value of **makeObserved**, but a variable decorated by @State. 71 ```ts 72 import { UIUtils } from '@kit.ArkUI'; 73 class Person { 74 age: number = 10; 75 } 76 class Info { 77 id: number = 0; 78 person: Person = new Person(); 79 } 80 @Entry 81 @Component 82 struct Index { 83 @State message: Info = new Info(); 84 @State message2: Info = UIUtils.makeObserved(this.message); // An exception does not throw. 85 build() { 86 Column() { 87 Text(`${this.message2.person.age}`) 88 .onClick(() => { 89 // The UI is not re-rendered because only the changes at the first layer can be observed by the @State. 90 this.message2.person.age++; 91 }) 92 } 93 } 94 } 95 ``` 96### makeObserved Is Used Only for Input Parameters and Return Value Still Has Observation Capability 97 98 - **message** is decorated by @Local and has the capability of observing its own value changes. Its initial value is the return value of **makeObserved**, which supports in-depth observation. 99 - Click **change id** to re-render the UI. 100 - Click **change Info** to set **this.message** to unobservable data. Click **change id** again, UI cannot be re-rendered. 101 - Click **change Info1** to set **this.message** to observable data. Click **change id** again, UI can be re-rendered. 102 103 ```ts 104 import { UIUtils } from '@kit.ArkUI'; 105 class Info { 106 id: number = 0; 107 constructor(id: number) { 108 this.id = id; 109 } 110 } 111 @Entry 112 @ComponentV2 113 struct Index { 114 @Local message: Info = UIUtils.makeObserved(new Info(20)); 115 build() { 116 Column() { 117 Button(`change id`).onClick(() => { 118 this.message.id++; 119 }) 120 Button(`change Info ${this.message.id}`).onClick(() => { 121 this.message = new Info(30); 122 }) 123 Button(`change Info1 ${this.message.id}`).onClick(() => { 124 this.message = UIUtils.makeObserved(new Info(30)); 125 }) 126 } 127 } 128 } 129 ``` 130 131## Supported Types and Observed Changes 132 133### Supported Types 134 135- Classes that are not decorated by @Observed or @ObserveV2. 136- Array, Map, Set, and Date types. 137- collections.Array, collections.Set, and collections.Map. 138- Object returned by JSON.parse. 139- @Sendable decorated class. 140 141### Observed Changes 142 143- When an instance of the built-in type or collections type is passed by **makeObserved**, you can observe the changes. 144 145 | Type | APIs that Can Observe Changes | 146 | ----- | ------------------------------------------------------------ | 147 | Array | push, pop, shift, unshift, splice, copyWithin, fill, reverse, sort| 148 | collections.Array | push, pop, shift, unshift, splice, fill, reverse, sort, shrinkTo, extendTo| 149 | Map/collections.Map | set, clear, delete | 150 | Set/collections.Set | add, clear, delete | 151 | Date | setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds| 152 153## Use Scenarios 154 155### Using makeObserved and @Sendable Decorated Class Together 156 157[@Sendable](../arkts-utils/arkts-sendable.md) is used to process concurrent tasks in application scenarios. The **makeObserved** and @Sendable are used together to meet the requirements of big data processing in the sub-thread and **ViewModel** display and data observation in the UI thread in common application development. For details about @Sendable, see [Multithreaded Concurrency Overview (TaskPool and Worker)](../arkts-utils/multi-thread-concurrency-overview.md). 158 159This section describes the following scenarios: 160- **makeObserved** has the observation capability after data of the @Sendable type is passed in, and the change of the data can trigger UI re-rendering. 161- Obtain an entire data from the subthread and replace the entire observable data of the UI thread. 162- Re-execute **makeObserved** from the data obtained from the subthread to change the data to observable data. 163- When data is passed from the main thread to a subthread, only unobservable data is passed. The return value of **makeObserved** cannot be directly passed to a subthread. 164 165Example: 166 167```ts 168// SendableData.ets 169@Sendable 170export class SendableData { 171 name: string = 'Tom'; 172 age: number = 20; 173 gender: number = 1; 174 // Other attributes are omitted here. 175 likes: number = 1; 176 follow: boolean = false; 177} 178``` 179 180```ts 181import { taskpool } from '@kit.ArkTS'; 182import { SendableData } from './SendableData'; 183import { UIUtils } from '@kit.ArkUI'; 184 185 186@Concurrent 187function threadGetData(param: string): SendableData { 188 // Process data in the subthread. 189 let ret = new SendableData(); 190 console.info(`Concurrent threadGetData, param ${param}`); 191 ret.name = param + "-o"; 192 ret.age = Math.floor(Math.random() * 40); 193 ret.likes = Math.floor(Math.random() * 100); 194 return ret; 195} 196 197@Entry 198@ComponentV2 199struct ObservedSendableTest { 200 // Use makeObserved to add the observation capability to a common object or a @Sendable decorated object. 201 @Local send: SendableData = UIUtils.makeObserved(new SendableData()); 202 build() { 203 Column() { 204 Text(this.send.name) 205 Button("change name").onClick(() => { 206 // Change of the attribute can be observed. 207 this.send.name += "0"; 208 }) 209 210 Button("task").onClick(() => { 211 // Places a function to be executed in the internal queue of the task pool. The function will be distributed to the worker thread for execution. 212 taskpool.execute(threadGetData, this.send.name).then(val => { 213 // Used together with @Local to observe the change of this.send. 214 this.send = UIUtils.makeObserved(val as SendableData); 215 }) 216 }) 217 } 218 } 219} 220``` 221**NOTE**<br>Data can be constructed and processed in subthreads. However, observable data can be processed only in the main thread. Therefore, in the preceding example, only the **name** attribute of **this.send** is passed to the subthread. 222 223### Using makeObserved and collections.Array/collections.Set/collections.Map Together 224**collections** provide ArkTS container sets for high-performance data passing in concurrent scenarios. For details, see [@arkts.collections (ArkTS Collections)](../reference/apis-arkts/js-apis-arkts-collections.md). 225makeObserved can be used to import an observable **colletions** container to ArkUI. However, makeObserved cannot be used together with the state variable decorators of V1, such as @State and @Prop. Otherwise, a runtime exception will be thrown. 226 227#### collections.Array 228The following APIs can trigger UI re-rendering: 229- Changing the array length: push, pop, shift, unshift, splice, shrinkTo and extendTo 230- Changing the array items: sort and fill. 231 232Other APIs do not change the original array. Therefore, the UI re-rendering is not triggered. 233 234```ts 235import { collections } from '@kit.ArkTS'; 236import { UIUtils } from '@kit.ArkUI'; 237 238@Sendable 239class Info { 240 id: number = 0; 241 name: string = 'cc'; 242 243 constructor(id: number) { 244 this.id = id; 245 } 246} 247 248 249@Entry 250@ComponentV2 251struct Index { 252 scroller: Scroller = new Scroller(); 253 @Local arrCollect: collections.Array<Info> = 254 UIUtils.makeObserved(new collections.Array<Info>(new Info(1), new Info(2))); 255 256 build() { 257 Column() { 258 // The ForEach API supports only Array<any>. collections.Array<any> is not supported. 259 // However, the array APIs used for ForEach implementation are provided in collections.Array. Therefore, you can assert an Array type using the as keyword. 260 // The assertion does not change the original data type. 261 ForEach(this.arrCollect as object as Array<Info>, (item: Info) => { 262 Text(`${item.id}`).onClick(() => { 263 item.id++; 264 }) 265 }, (item: Info, index) => item.id.toString() + index.toString()) 266 Divider() 267 .color('blue') 268 if (this.arrCollect.length > 0) { 269 Text(`the first one ${this.arrCollect[this.arrCollect.length - this.arrCollect.length].id}`) 270 Text(`the last one ${this.arrCollect[this.arrCollect.length - 1].id}`) 271 } 272 Divider() 273 .color('blue') 274 275 /****************************APIs for Changing the Data Length**************************/ 276 Scroll(this.scroller) { 277 Column({space: 10}) { 278 // push: adds a new element. 279 Button('push').onClick(() => { 280 this.arrCollect.push(new Info(30)); 281 }) 282 // pop: deletes the last element. 283 Button('pop').onClick(() => { 284 this.arrCollect.pop(); 285 }) 286 // shift: deletes the first element. 287 Button('shift').onClick(() => { 288 this.arrCollect.shift(); 289 }) 290 // unshift: inserts a new item at the beginning of the array. 291 Button('unshift').onClick(() => { 292 this.arrCollect.unshift(new Info(50)); 293 }) 294 // splice: deletes an element from the specified position of the array. 295 Button('splice').onClick(() => { 296 this.arrCollect.splice(1); 297 }) 298 299 // shrinkTo: shrinks the array length to a specified length. 300 Button('shrinkTo').onClick(() => { 301 this.arrCollect.shrinkTo(1); 302 }) 303 // extendTo: extends the array length to a specified length. 304 Button('extendTo').onClick(() => { 305 this.arrCollect.extendTo(6, new Info(20)); 306 }) 307 308 Divider() 309 .color('blue') 310 311 /****************************************APIs for Changing the Array Item*****************/ 312 // sort: arranging the Array item in descending order. 313 Button('sort').onClick(() => { 314 this.arrCollect.sort((a: Info, b: Info) => b.id - a.id); 315 }) 316 // fill: fills in the specified part with a value. 317 Button('fill').onClick(() => { 318 this.arrCollect.fill(new Info(5), 0, 2); 319 }) 320 321 /*****************************APIs for Not Changing the Array Item***************************/ 322 // slice: returns a new array. The original array is copied using Array.slice(start,end), which does not change the original array. Therefore, directly invoking slice does not trigger UI re-rendering. 323 // You can construct a case to assign the return data of the shallow copy to this.arrCollect. Note that makeObserved must be called here. Otherwise, the observation capability will be lost after this.arr is assigned a value by a common variable. 324 Button('slice').onClick(() => { 325 this.arrCollect = UIUtils.makeObserved(this.arrCollect.slice(0, 1)); 326 }) 327 // map: The principle is the same as above. 328 Button('map').onClick(() => { 329 this.arrCollect = UIUtils.makeObserved(this.arrCollect.map((value) => { 330 value.id += 10; 331 return value; 332 })) 333 }) 334 // filter: The principle is the same as above. 335 Button('filter').onClick(() => { 336 this.arrCollect = UIUtils.makeObserved(this.arrCollect.filter((value: Info) => value.id % 2 === 0)); 337 }) 338 339 // concat: The principle is the same as above. 340 Button('concat').onClick(() => { 341 let array1 = new collections.Array(new Info(100)) 342 this.arrCollect = UIUtils.makeObserved(this.arrCollect.concat(array1)); 343 }) 344 }.height('200%') 345 }.height('60%') 346 } 347 .height('100%') 348 .width('100%') 349 } 350} 351``` 352#### collections.Map 353 354The following APIs can trigger UI re-rendering: set, clear, and delete. 355```ts 356import { collections } from '@kit.ArkTS'; 357import { UIUtils } from '@kit.ArkUI'; 358 359@Sendable 360class Info { 361 id: number = 0; 362 363 constructor(id: number) { 364 this.id = id; 365 } 366} 367 368 369@Entry 370@ComponentV2 371struct CollectionMap { 372 mapCollect: collections.Map<string, Info> = UIUtils.makeObserved(new collections.Map<string, Info>([['a', new Info(10)], ['b', new Info(20)]])); 373 374 build() { 375 Column() { 376 // this.mapCollect.keys() returns an iterator which is not supported by Foreach. Therefore, Array.from is used to generate data in shallow copy mode. 377 ForEach(Array.from(this.mapCollect.keys()), (item: string) => { 378 Text(`${this.mapCollect.get(item)?.id}`).onClick(() => { 379 let value: Info|undefined = this.mapCollect.get(item); 380 if (value) { 381 value.id++; 382 } 383 }) 384 }, (item: string, index) => item + index.toString()) 385 386 // set c 387 Button('set c').onClick(() => { 388 this.mapCollect.set('c', new Info(30)); 389 }) 390 // delete c 391 Button('delete c').onClick(() => { 392 if (this.mapCollect.has('c')) { 393 this.mapCollect.delete('c'); 394 } 395 }) 396 // clear 397 Button('clear').onClick(() => { 398 this.mapCollect.clear(); 399 }) 400 } 401 .height('100%') 402 .width('100%') 403 } 404} 405``` 406 407#### collections.Set 408The following APIs can trigger UI re-rendering: add, clear, and delete. 409 410```ts 411import { collections } from '@kit.ArkTS'; 412import { UIUtils } from '@kit.ArkUI'; 413@Sendable 414class Info { 415 id: number = 0; 416 417 constructor(id: number) { 418 this.id = id; 419 } 420} 421 422 423@Entry 424@ComponentV2 425struct Index { 426 set: collections.Set<Info> = UIUtils.makeObserved(new collections.Set<Info>([new Info(10), new Info(20)])); 427 428 build() { 429 Column() { 430 // ForEach does not support iterators. Therefore, Array.from is used to generate data in shallow copy mode. 431 // However, the new array generated by shallow copy does not have the observation capability. To ensure that the data can be observed when the ForEach component accesses the item, makeObserved needs to be called again. 432 ForEach((UIUtils.makeObserved(Array.from(this.set.values()))), (item: Info) => { 433 Text(`${item.id}`).onClick(() => { 434 item.id++; 435 }) 436 }, (item: Info, index) => item.id + index.toString()) 437 438 // add 439 Button('add').onClick(() => { 440 this.set.add(new Info(30)); 441 console.log('size:' + this.set.size); 442 }) 443 // delete 444 Button('delete').onClick(() => { 445 let iterator = this.set.keys(); 446 this.set.delete(iterator.next().value); 447 }) 448 // clear 449 Button('clear').onClick(() => { 450 this.set.clear(); 451 }) 452 } 453 .height('100%') 454 .width('100%') 455 } 456} 457``` 458 459### Input Parameter of makeObserved Is the Return Value of JSON.parse 460JSON.parse returns an object which cannot be decorated by @Trace. You can use makeObserved to make it observable. 461 462```ts 463import { JSON } from '@kit.ArkTS'; 464import { UIUtils } from '@kit.ArkUI'; 465 466class Info { 467 id: number = 0; 468 469 constructor(id: number) { 470 this.id = id; 471 } 472} 473 474let test: Record<string, number> = { "a": 123 }; 475let testJsonStr :string = JSON.stringify(test); 476let test2: Record<string, Info> = { "a": new Info(20) }; 477let test2JsonStr: string = JSON.stringify(test2); 478 479@Entry 480@ComponentV2 481struct Index { 482 message: Record<string, number> = UIUtils.makeObserved<Record<string, number>>(JSON.parse(testJsonStr) as Record<string, number>); 483 message2: Record<string, Info> = UIUtils.makeObserved<Record<string, Info>>(JSON.parse(test2JsonStr) as Record<string, Info>); 484 485 build() { 486 Column() { 487 Text(`${this.message.a}`) 488 .fontSize(50) 489 .onClick(() => { 490 this.message.a++; 491 }) 492 Text(`${this.message2.a.id}`) 493 .fontSize(50) 494 .onClick(() => { 495 this.message2.a.id++; 496 }) 497 } 498 .height('100%') 499 .width('100%') 500 } 501} 502``` 503 504### Using makeObserved and Decorators of V2 Together 505**makeObserved** can be used with the decorators of V2. For @Monitor and @Computed, the class instance decorated by @Observed or ObservedV2 passed by makeObserved returns itself. Therefore, @Monitor or @Computed cannot be defined in a class but in a custom component. 506 507Example: 508```ts 509import { UIUtils } from '@kit.ArkUI'; 510 511class Info { 512 id: number = 0; 513 age: number = 20; 514 515 constructor(id: number) { 516 this.id = id; 517 } 518} 519 520@Entry 521@ComponentV2 522struct Index { 523 @Local message: Info = UIUtils.makeObserved(new Info(20)); 524 525 @Monitor('message.id') 526 onStrChange(monitor: IMonitor) { 527 console.log(`name change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 528 } 529 530 @Computed 531 get ageId() { 532 console.info("---------Computed----------"); 533 return this.message.id + ' ' + this.message.age; 534 } 535 536 build() { 537 Column() { 538 Text(`id: ${this.message.id}`) 539 .fontSize(50) 540 .onClick(() => { 541 this.message.id++; 542 }) 543 544 Text(`age: ${this.message.age}`) 545 .fontSize(50) 546 .onClick(() => { 547 this.message.age++; 548 }) 549 550 Text(`Computed age+id: ${this.ageId}`) 551 .fontSize(50) 552 553 Button('change Info').onClick(() => { 554 this.message = UIUtils.makeObserved(new Info(200)); 555 }) 556 557 Child({message: this.message}) 558 } 559 .height('100%') 560 .width('100%') 561 } 562} 563 564@ComponentV2 565struct Child { 566 @Param @Require message: Info; 567 build() { 568 Text(`Child id: ${this.message.id}`) 569 } 570} 571``` 572 573### Using makeObserved in @Component 574**makeObserved** cannot be used with the state variable decorator of V1, but can be used in custom components decorated by @Component. 575 576```ts 577import { UIUtils } from '@kit.ArkUI'; 578class Info { 579 id: number = 0; 580 581 constructor(id: number) { 582 this.id = id; 583 } 584} 585 586 587@Entry 588@Component 589struct Index { 590 // Using makeObserved together with @State, a runtime exception is thrown. 591 message: Info = UIUtils.makeObserved(new Info(20)); 592 593 build() { 594 RelativeContainer() { 595 Text(`${this.message.id}`) 596 .onClick(() => { 597 this.message.id++; 598 }) 599 } 600 .height('100%') 601 .width('100%') 602 } 603} 604``` 605 606## FAQs 607### Original Object Can Be Assigned Value Using getTarget but Fails to Trigger UI Re-render 608[getTarget](./arkts-new-getTarget.md) can be used to obtain the original object before adding a proxy in the state management. 609 610The observation object encapsulated by **makeObserved** can obtain its original object through **getTarget**. The value changes to the original object do not trigger UI re-rendering. 611 612Example: 6131. Click the first **Text** component and obtain its original object through **getTarget**. In this case, modifying the attributes of the original object does not trigger UI re-rendering, but a value is assigned to the data. 6142. Click the second **Text** component. If the **this.observedObj** attribute is modified, the UI is re-rendered and the value of **Text** is **21**. 615 616```ts 617import { UIUtils } from '@kit.ArkUI'; 618class Info { 619 id: number = 0; 620} 621 622@Entry 623@Component 624struct Index { 625 observedObj: Info = UIUtils.makeObserved(new Info()); 626 build() { 627 Column() { 628 Text(`${this.observedObj.id}`) 629 .fontSize(50) 630 .onClick(() => { 631 // Use getTarget to obtain the original object and assign this.observedObj to unobservable data. 632 let rawObj: Info= UIUtils.getTarget(this.observedObj); 633 // The UI is not re-rendered, but a value is assigned to the data. 634 rawObj.id = 20; 635 }) 636 637 Text(`${this.observedObj.id}`) 638 .fontSize(50) 639 .onClick(() => { 640 // Triggers UI re-rendering. The value of Text is 21. 641 this.observedObj.id++; 642 }) 643 } 644 } 645} 646``` 647