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