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