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-->