1# Freezing a Custom Component
2
3When a custom component is inactive, it can be frozen so that its state variable does not respond to updates. That is, the @Watch decorated method is not called, and the node associated with the state variable is not re-rendered. You can use the **freezeWhenInactive** attribute to specify whether to freeze a custom component. If no parameter is passed in, the feature is disabled. This feature works in following scenarios: page routing, **\<TabContent>**, **LazyForEach**, and **\<Navigation>**.
4
5
6> **NOTE**
7>
8> Custom component freezing is supported since API version 11.
9
10## Use Scenarios
11
12### Page Routing
13
14- When page A calls the **router.pushUrl** API to jump to page B, page A is hidden and invisible. In this case, if the state variable on page A is updated, page A is not re-rendered.
15
16- The freezing feature does not work when the application is running in the background.
17
18Page A:
19
20```ts
21import { router } from '@kit.ArkUI';
22
23@Entry
24@Component({ freezeWhenInactive: true })
25struct FirstTest {
26  @StorageLink('PropA') @Watch("first") storageLink: number = 47;
27
28  first() {
29    console.info("first page " + `${this.storageLink}`)
30  }
31
32  build() {
33    Column() {
34      Text(`From fist Page ${this.storageLink}`).fontSize(50)
35      Button('first page storageLink + 1').fontSize(30)
36        .onClick(() => {
37          this.storageLink += 1
38        })
39      Button('go to next page').fontSize(30)
40        .onClick(() => {
41          router.pushUrl({ url: 'pages/second' })
42        })
43    }
44  }
45}
46```
47
48Page B:
49
50```ts
51import { router } from '@kit.ArkUI';
52
53@Entry
54@Component({ freezeWhenInactive: true })
55struct SecondTest {
56  @StorageLink('PropA') @Watch("second") storageLink2: number = 1;
57
58  second() {
59    console.info("second page: " + `${this.storageLink2}`)
60  }
61
62  build() {
63    Column() {
64
65      Text(`second Page ${this.storageLink2}`).fontSize(50)
66      Button('Change Divider.strokeWidth')
67        .onClick(() => {
68          router.back()
69        })
70
71      Button('second page storageLink2 + 2').fontSize(30)
72        .onClick(() => {
73          this.storageLink2 += 2
74        })
75
76    }
77  }
78}
79```
80
81In the preceding example:
82
831. When the button **first page storageLink + 1** on page A is clicked, the **storageLink** state variable is updated, and the @Watch decorated **first** method is called.
84
852. Through **router.pushUrl({url:'pages/second'})**, page B is displayed, and page A is hidden with its state changing from active to inactive.
86
873. When the button **this.storageLink2 += 2** on page B is clicked, only the @Watch decorated **second** method of page B is called, because page A has been frozen when inactive.
88
894. When the **back** button is clicked, page B is destroyed, and page A changes from inactive to active. At this time, if the state variable of page A is updated, the @Watch decorated **first** method of page A is called again.
90
91
92### TabContent
93
94- You can freeze invisible **TabContent** components in the **Tabs** container so that they do not trigger UI re-rendering.
95
96- During initial rendering, only the **TabContent** component that is being displayed is created. All **TabContent** components are created only after all of them have been switched to.
97
98```ts
99@Entry
100@Component
101struct TabContentTest {
102  @State @Watch("onMessageUpdated") message: number = 0;
103  private data: number[] = [0, 1]
104
105  onMessageUpdated() {
106    console.info(`TabContent message callback func ${this.message}`)
107  }
108
109  build() {
110    Row() {
111      Column() {
112        Button('change message').onClick(() => {
113          this.message++
114        })
115
116        Tabs() {
117          ForEach(this.data, (item: number) => {
118            TabContent() {
119              FreezeChild({ message: this.message, index: item })
120            }.tabBar(`tab${item}`)
121          }, (item: number) => item.toString())
122        }
123      }
124      .width('100%')
125    }
126    .height('100%')
127  }
128}
129
130@Component({ freezeWhenInactive: true })
131struct FreezeChild {
132  @Link @Watch("onMessageUpdated") message: number
133  private index: number = 0
134
135  onMessageUpdated() {
136    console.info(`FreezeChild message callback func ${this.message}, index: ${this.index}`)
137  }
138
139  build() {
140    Text("message" + `${this.message}, index: ${this.index}`)
141      .fontSize(50)
142      .fontWeight(FontWeight.Bold)
143  }
144}
145```
146
147In the preceding example:
148
1491. When **change message** is clicked, the value of **message** changes, and the @Watch decorated **onMessageUpdated** method of the **TabContent** component being displayed is called.
150
1512. When you click **two** to switch to another **TabContent** component, it switches from inactive to active, and the corresponding @Watch decorated **onMessageUpdated** method is called.
152
1533. When **change message** is clicked again, the value of **message** changes, and only the @Watch decorated **onMessageUpdated** method of the **TabContent** component being displayed is called.
154
155![TabContent.gif](figures/TabContent.gif)
156
157
158### LazyForEach
159
160- You can freeze custom components cached in **LazyForEach** so that they do not trigger UI re-rendering.
161
162```ts
163// Basic implementation of IDataSource to handle data listener
164class BasicDataSource implements IDataSource {
165  private listeners: DataChangeListener[] = [];
166  private originDataArray: string[] = [];
167
168  public totalCount(): number {
169    return 0;
170  }
171
172  public getData(index: number): string {
173    return this.originDataArray[index];
174  }
175
176  // This method is called by the framework to add a listener to the LazyForEach data source.
177  registerDataChangeListener(listener: DataChangeListener): void {
178    if (this.listeners.indexOf(listener) < 0) {
179      console.info('add listener');
180      this.listeners.push(listener);
181    }
182  }
183
184  // This method is called by the framework to remove the listener from the LazyForEach data source.
185  unregisterDataChangeListener(listener: DataChangeListener): void {
186    const pos = this.listeners.indexOf(listener);
187    if (pos >= 0) {
188      console.info('remove listener');
189      this.listeners.splice(pos, 1);
190    }
191  }
192
193  // Notify LazyForEach that all child components need to be reloaded.
194  notifyDataReload(): void {
195    this.listeners.forEach(listener => {
196      listener.onDataReloaded();
197    })
198  }
199
200  // Notify LazyForEach that a child component needs to be added for the data item with the specified index.
201  notifyDataAdd(index: number): void {
202    this.listeners.forEach(listener => {
203      listener.onDataAdd(index);
204    })
205  }
206
207  // Notify LazyForEach that the data item with the specified index has changed and the child component needs to be rebuilt.
208  notifyDataChange(index: number): void {
209    this.listeners.forEach(listener => {
210      listener.onDataChange(index);
211    })
212  }
213
214  // Notify LazyForEach that the child component that matches the specified index needs to be deleted.
215  notifyDataDelete(index: number): void {
216    this.listeners.forEach(listener => {
217      listener.onDataDelete(index);
218    })
219  }
220}
221
222class MyDataSource extends BasicDataSource {
223  private dataArray: string[] = [];
224
225  public totalCount(): number {
226    return this.dataArray.length;
227  }
228
229  public getData(index: number): string {
230    return this.dataArray[index];
231  }
232
233  public addData(index: number, data: string): void {
234    this.dataArray.splice(index, 0, data);
235    this.notifyDataAdd(index);
236  }
237
238  public pushData(data: string): void {
239    this.dataArray.push(data);
240    this.notifyDataAdd(this.dataArray.length - 1);
241  }
242}
243
244@Entry
245@Component
246struct LforEachTest {
247  private data: MyDataSource = new MyDataSource();
248  @State @Watch("onMessageUpdated") message: number = 0;
249
250  onMessageUpdated() {
251    console.info(`LazyforEach message callback func ${this.message}`)
252  }
253
254  aboutToAppear() {
255    for (let i = 0; i <= 20; i++) {
256      this.data.pushData(`Hello ${i}`)
257    }
258  }
259
260  build() {
261    Column() {
262      Button('change message').onClick(() => {
263        this.message++
264      })
265      List({ space: 3 }) {
266        LazyForEach(this.data, (item: string) => {
267          ListItem() {
268            FreezeChild({ message: this.message, index: item })
269          }
270        }, (item: string) => item)
271      }.cachedCount(5).height(500)
272    }
273
274  }
275}
276
277@Component({ freezeWhenInactive: true })
278struct FreezeChild {
279  @Link @Watch("onMessageUpdated") message: number;
280  private index: string = "";
281
282  aboutToAppear() {
283    console.info(`FreezeChild aboutToAppear index: ${this.index}`)
284  }
285
286  onMessageUpdated() {
287    console.info(`FreezeChild message callback func ${this.message}, index: ${this.index}`)
288  }
289
290  build() {
291    Text("message" + `${this.message}, index: ${this.index}`)
292      .width('90%')
293      .height(160)
294      .backgroundColor(0xAFEEEE)
295      .textAlign(TextAlign.Center)
296      .fontSize(30)
297      .fontWeight(FontWeight.Bold)
298  }
299}
300```
301
302In the preceding example:
303
3041. When **change message** is clicked, the value of **message** changes, the @Watch decorated **onMessageUpdated** method of the list items being displayed is called, and that of the cached list items is not called. (If the component is not frozen, the @Watch decorated **onMessageUpdated** method of both list items that are being displayed and cached list items is called.)
305
3062. When a list item moves from outside the list content area into the list content area, it switches from inactive to active, and the corresponding @Watch decorated **onMessageUpdated** method is called.
307
3083. When **change message** is clicked again, the value of **message** changes, and only the @Watch decorated **onMessageUpdated** method of the list items being displayed is called.
309
310![FrezzeLazyforEach.gif](figures/FrezzeLazyforEach.gif)
311
312### Navigation
313
314- When the navigation destination page is invisible, its child custom components are set to the inactive state and will not be re-rendered. When return to this page, its child custom components are restored to the active state and the @Watch callback is triggered to re-render the page.
315
316- In the following example, **NavigationContentMsgStack** is set to the inactive state, which does not respond to the change of the state variables, and does not trigger component re-rendering.
317
318```ts
319@Entry
320@Component
321struct MyNavigationTestStack {
322  @Provide('pageInfo') pageInfo: NavPathStack = new NavPathStack();
323  @State @Watch("info") message: number = 0;
324  @State logNumber: number = 0;
325
326  info() {
327    console.info(`freeze-test MyNavigation message callback ${this.message}`);
328  }
329
330  @Builder
331  PageMap(name: string) {
332    if (name === 'pageOne') {
333      pageOneStack({ message: this.message, logNumber: this.logNumber })
334    } else if (name === 'pageTwo') {
335      pageTwoStack({ message: this.message, logNumber: this.logNumber })
336    } else if (name === 'pageThree') {
337      pageThreeStack({ message: this.message, logNumber: this.logNumber })
338    }
339  }
340
341  build() {
342    Column() {
343      Button('change message')
344        .onClick(() => {
345          this.message++;
346        })
347      Navigation(this.pageInfo) {
348        Column() {
349          Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
350            .width('80%')
351            .height(40)
352            .margin(20)
353            .onClick(() => {
354              this.pageInfo.pushPath({ name: 'pageOne' }); // Push the navigation destination page specified by name to the navigation stack.
355            })
356        }
357      }.title('NavIndex')
358      .navDestination(this.PageMap)
359      .mode(NavigationMode.Stack)
360    }
361  }
362}
363
364@Component
365struct pageOneStack {
366  @Consume('pageInfo') pageInfo: NavPathStack;
367  @State index: number = 1;
368  @Link message: number;
369  @Link logNumber: number;
370
371  build() {
372    NavDestination() {
373      Column() {
374        NavigationContentMsgStack({ message: this.message, index: this.index, logNumber: this.logNumber })
375        Text("cur stack size:" + `${this.pageInfo.size()}`)
376          .fontSize(30)
377          .fontWeight(FontWeight.Bold)
378        Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
379          .width('80%')
380          .height(40)
381          .margin(20)
382          .onClick(() => {
383            this.pageInfo.pushPathByName('pageTwo', null);
384          })
385        Button('Back Page', { stateEffect: true, type: ButtonType.Capsule })
386          .width('80%')
387          .height(40)
388          .margin(20)
389          .onClick(() => {
390            this.pageInfo.pop();
391          })
392      }.width('100%').height('100%')
393    }.title('pageOne')
394    .onBackPressed(() => {
395      this.pageInfo.pop();
396      return true;
397    })
398  }
399}
400
401@Component
402struct pageTwoStack {
403  @Consume('pageInfo') pageInfo: NavPathStack;
404  @State index: number = 2;
405  @Link message: number;
406  @Link logNumber: number;
407
408  build() {
409    NavDestination() {
410      Column() {
411        NavigationContentMsgStack({ message: this.message, index: this.index, logNumber: this.logNumber })
412        Text("cur stack size:" + `${this.pageInfo.size()}`)
413          .fontSize(30)
414          .fontWeight(FontWeight.Bold)
415        Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
416          .width('80%')
417          .height(40)
418          .margin(20)
419          .onClick(() => {
420            this.pageInfo.pushPathByName('pageThree', null);
421          })
422        Button('Back Page', { stateEffect: true, type: ButtonType.Capsule })
423          .width('80%')
424          .height(40)
425          .margin(20)
426          .onClick(() => {
427            this.pageInfo.pop();
428          })
429      }.width('100%').height('100%')
430    }.title('pageTwo')
431    .onBackPressed(() => {
432      this.pageInfo.pop();
433      return true;
434    })
435  }
436}
437
438@Component
439struct pageThreeStack {
440  @Consume('pageInfo') pageInfo: NavPathStack;
441  @State index: number = 3;
442  @Link message: number;
443  @Link logNumber: number;
444
445  build() {
446    NavDestination() {
447      Column() {
448        NavigationContentMsgStack({ message: this.message, index: this.index, logNumber: this.logNumber })
449        Text("cur stack size:" + `${this.pageInfo.size()}`)
450          .fontSize(30)
451          .fontWeight(FontWeight.Bold)
452        Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
453          .width('80%')
454          .height(40)
455          .margin(20)
456          .onClick(() => {
457            this.pageInfo.pushPathByName('pageOne', null);
458          })
459        Button('Back Page', { stateEffect: true, type: ButtonType.Capsule })
460          .width('80%')
461          .height(40)
462          .margin(20)
463          .onClick(() => {
464            this.pageInfo.pop();
465          })
466      }.width('100%').height('100%')
467    }.title('pageThree')
468    .onBackPressed(() => {
469      this.pageInfo.pop();
470      return true;
471    })
472  }
473}
474
475@Component({ freezeWhenInactive: true })
476struct NavigationContentMsgStack {
477  @Link @Watch("info") message: number;
478  @Link index: number;
479  @Link logNumber: number;
480
481  info() {
482    console.info(`freeze-test NavigationContent message callback ${this.message}`);
483    console.info(`freeze-test ---- called by content ${this.index}`);
484    this.logNumber++;
485  }
486
487  build() {
488    Column() {
489      Text("msg:" + `${this.message}`)
490        .fontSize(30)
491        .fontWeight(FontWeight.Bold)
492      Text("log number:" + `${this.logNumber}`)
493        .fontSize(30)
494        .fontWeight(FontWeight.Bold)
495    }
496  }
497}
498```
499
500In the preceding example:
501
5021. When **change message** is clicked, the value of **message** changes, and the @Watch decorated **info** method of the **MyNavigationTestStack** component being displayed is called.
503
5042. When **Next Page** is clicked, **PageOne** is displayed, and the **PageOneStack** node is created.
505
5063. When **change message** is clicked again, the value of **message** changes, and only the @Watch decorated **info** method of the **NavigationContentMsgStack** child component in **pageOneStack** is called.
507
5084. When **Next Page** is clicked again, **PageTwo** is displayed, and the **pageTwoStack** node is created.
509
5105. When **change message** is clicked again, the value of **message** changes, and only the @Watch decorated **info** method of the **NavigationContentMsgStack** child component in **pageTwoStack** is called.
511
5126. When **Next Page** is clicked again, **PageThree** is displayed, and the **pageThreeStack** node is created.
513
5147. When **change message** is clicked again, the value of **message** changes, and only the @Watch decorated **info** method of the **NavigationContentMsgStack** child component in **pageThreeStack** is called.
515
5168. When **Back Page** is clicked, **PageTwo** is displayed, and only the @Watch decorated **info** method of the **NavigationContentMsgStack** child component in **pageTwoStack** is called.
517
5189. When **Back Page** is clicked again, **PageOne** is displayed, and only the @Watch decorated **info** method of the **NavigationContentMsgStack** child component in **PageOne** is called.
519
52010. When **Back Page** is clicked again, the initial page is displayed, and no method is called.
521
522![navigation-freeze.gif](figures/navigation-freeze.gif)
523