• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# \@Provider and \@Consumer Decorators: Synchronizing Across Component Levels in a Two-Way Manner
2
3\@Provider and \@Consumer are used for synchronizing data across the component levels in a two-way manner, so that you are free from the constraints of the component levels.
4\@Provider and \@Consumer are decorators in state management V2, so they can be used only in \@ComponentV2. A compilation error will be reported if they are used in \@Component.
5
6
7Before reading this topic, you are advised to read [\@ComponentV2](./arkts-new-componentV2.md).
8
9>**NOTE**
10>
11>\@Provider and \@Consumer decorators are supported since API version 12.
12>
13
14## Overview
15
16\@Provider provides data. Its child components can use the \@Consumer to obtain the data by binding the same **key**.
17\@Consumer obtains data. It can obtain the \@Provider data of the nearest parent node by binding the same **key**. If the \@Provider data cannot be found, the local default value will be used.
18Data types decorated by \@Provider and \@Consumer must be the same.
19
20
21The following notes must be paid attention to when using \@Provider and \@Consumer:
22- \@Provider and \@Consumer strongly depend on the custom component levels. \@Consumer is initialized to different values because the parent component of the custom component is different.
23- Using \@Provider with \@Consumer is equivalent to bonding components together. From the perspective of independent component, usage of \@Provider and \@Consumer should be lessened.
24
25
26## Capability Comparison: \@Provider and \@Consumer Vs. \@Provide and \@Consume
27In state management V1, [\@Provide and \@Consume](./arkts-provide-and-consume.md) are the decorators which provide two-way synchronization across component levels. This topic introduces \@Provider and \@Consumer decorators in state management V2. Although the names and features of the two pairs are similar, there are still some differences.
28If you are not familiar with \@Provide and \@Consume in state management V1, please skip this section.
29
30| Capability| \@Provider and \@Consumer Decorators of V2                                            |\@Provide and \@Consume Decorators of V1|
31| ------------------ | ----------------------------------------------------- |----------------------------------------------------- |
32| \@Consume(r)         |Local initialization is allowed. Local default value will be used when \@Provider is not found.| Local initialization is forbidden. An exception will be thrown when the \@Provide is not found.|
33| Supported type          | **function** is supported.| **function** is not supported.|
34| Observation capability          | Only the value change of itself can be observed. To observe the nesting scenario, use this decorator together with [\@Trace](https://gitee.com/openharmony/docs/blob/master/en/application-dev/quick-start/arkts-new-observedV2-and-trace.md).| Changes at the first layer can be observed. To observe the nesting scenario, use this decorator together with [\@Observed and \@ObjectLink](https://gitee.com/openharmony/docs/blob/master/en/application-dev/quick-start/arkts-observed-and-objectlink.md).|
35| alias and attribute name        | **alias** is the unique matching **key** and the default attribute name.| If both the **alias** and attribute name are **key**, the former one is matched first. If no match is found, the attribute name can be matched.|
36| \@Provide(r) initialization from the parent component     | Forbidden.| Allowed.|
37| \@Provide(r) overloading support | Enabled by default. That is, \@Provider can have duplicate names and \@Consumer can search upwards for the nearest \@Provider.| Disabled by default. That is, \@Provide with duplicate names is not allowed in the component tree. If overloading is required, set **allowOverride**.|
38
39
40## Decorator Description
41
42### Basic Rules
43\@Provider syntax:
44@Provider(alias?: string) varName : varType = initValue
45
46| \@Provider Property Decorator| Description                                                 |
47| ------------------ | ----------------------------------------------------- |
48| Decorator parameters        | **aliasName?: string**: alias. The default value is the attribute name.|
49| Supported type          | Member variables in the custom component.<br>Property types include number, string, boolean, class, Array, Date, Map, and Set.<br>[Arrow function](#decorating-callback-by-using-provider-and-consumer-and-facilitating-behavior-abstraction-between-components). |
50| Initialization from the parent component     | Forbidden.|
51| Local initialization        | Required.|
52| Observation capability        | Be equivalent to \@Trace. Changes will be synchronized to the corresponding \@Consumer.|
53
54\@Consumer syntax:
55@Consumer(alias?: string) varName : varType = initValue
56
57
58| \@Consumer Property Decorator| Description                                                        |
59| --------------------- | ------------------------------------------------------------ |
60| Decorator parameters           | **aliasName?: string**: alias. The default value is the attribute name. The nearest \@Provider is searched upwards.   |
61| Supported type         | Member variables in the custom component.<br> Property types include number, string, boolean, class, Array, Date, Map, and Set.<br> Arrow function.|
62| Initialization from the parent component     | Forbidden.|
63| Local initialization        | Required.|
64| Observation capability        | Be equivalent to \@Trace. Changes will be synchronized to the corresponding \@Provider.|
65
66### aliasName and Attribute Name
67\@Provider and \@Consumer accept the optional parameter **aliasName**. If the parameter is not set, the attribute name will be used as the default **aliasName** Note that **aliasName** is the unique key used to match \@Provider and \@Consumer.
68
69The following three examples clearly describe how \@Provider and \@Consumer use **aliasName** for searching and matching.
70```ts
71@ComponentV2
72struct Parent {
73  // The aliasName is not defined. Use the property name "str" as the aliasName.
74  @Provider() str: string = 'hello';
75}
76
77@ComponentV2
78struct Child {
79  // Define aliasName as"str" and use it to search.
80  // If the value can be found on the Parent component, use the value "hello" of @Provider.
81  @Consumer('str') str: string = 'world';
82}
83```
84
85```ts
86@ComponentV2
87struct Parent {
88  // Define aliasName as "alias".
89  @Provider('alias') str: string = 'hello';
90}
91
92@ComponentV2 struct Child {
93  // Define aliasName as "alias", find out @Provider, and obtain the value "hello".
94  @Consumer('alias') str: string = 'world';
95}
96```
97
98```ts
99@ComponentV2
100struct Parent {
101  // Define aliasName as "alias".
102  @Provider('alias') str: string = 'hello';
103}
104
105@ComponentV2
106struct Child {
107  // The aliasName is not defined. Use the property name "str" as the aliasName.
108  // The corresponding @Provider is not found, use the local value "world".
109  @Consumer() str: string = 'world';
110}
111```
112
113## Variable Passing
114
115| Passing Rules      | Description                                                        |
116| -------------- | ------------------------------------------------------------ |
117| Initialization from the parent component| Variables decorated by \@Provider and \@Consumer can only be initialized locally.|
118| Child component initialization  | Variables decorated by \@Provider and \@Consumer can be used to initialize \@Param decorated variables in the child component.|
119
120## Constraints
121
1221. \@Provider and \@Consumer are property decorators for custom components. They can only decorate the attributes of custom components and cannot decorate the class properties.
1232. \@Provider and \@Consumer are decorators of the state management V2, which can be used only in \@ComponentV2 but not in \@Component.
1243. \@Provider and \@Consumer support only local initialization.
125
126## Use Scenarios
127
128### Synchronizing \@Provider and \@Consumer in a Two-Way Manner
129#### Establishing a Two-Way Binding
1301. Initialize the **Parent** and **Child** custom components:
131    - **@Consumer() str: string = 'world'** in the **Child** component searches upwards to find **@Provider() str: string = 'hello'** in the **Parent** component.
132    - **@Consumer() str: string = 'world'** is initialized to the value of **@Provider**, that is, **'hello'**.
133    - Both of them establish a two-way synchronization relationship.
1342. Click the button in **Parent** to change the \@Provider decorated **str** and notify the corresponding \@Consumer to re-render the UI.
1353. Click the button in **Child** to change the \@Consumer decorated **str**, and notify the corresponding \@Provider to re-render the UI.
136
137```ts
138@Entry
139@ComponentV2
140struct Parent {
141  @Provider() str: string = 'hello';
142
143  build() {
144    Column() {
145      Button(this.str)
146        .onClick(() => {
147          this.str += '0';
148        })
149      Child()
150    }
151  }
152}
153
154@ComponentV2
155struct Child {
156  @Consumer() str: string = 'world';
157
158  build() {
159    Column() {
160      Button(this.str)
161        .onClick(() => {
162          this.str += '0';
163        })
164    }
165  }
166}
167```
168#### Establishing a Two-Way Binding Failed
169
170In the following example, \@Provider and \@Consumer fail to establish a two-way synchronization relationship because of different **aliasName** value.
1711. Initialize the **Parent** and **Child** custom components:
172    - @Provider is not found when **@Consumer() str: string = 'world'** in the **Child** component searches upwards.
173    - **@Consumer() str: string = 'world'** uses the local default value 'world'.
174    - Both of them fail to establish a two-way synchronization relationship.
1752. Click the button in the **Parent** component to change @Provider decorated **str1** and re-render only the **Button** component associated with @Provider.
1763. Click the button in the **Child** component to change the @Consumer decorated **str** and re-render only the **Button** component associated with @Consumer.
177
178```ts
179@Entry
180@ComponentV2
181struct Parent {
182  @Provider() str1: string = 'hello';
183
184  build() {
185    Column() {
186      Button(this.str1)
187        .onClick(() => {
188          this.str1 += '0';
189        })
190      Child()
191    }
192  }
193}
194
195@ComponentV2
196struct Child {
197  @Consumer() str: string = 'world';
198
199  build() {
200    Column() {
201      Button(this.str)
202        .onClick(() => {
203          this.str += '0';
204        })
205    }
206  }
207}
208```
209
210### Decorating Callback by Using @Provider and @Consumer and Facilitating Behavior Abstraction Between Components
211
212To register a callback function for a child component in a parent component, you can use \@Provider and \@Consumer to decorate a callback.
213For example, when a drag event occurs, if you want to synchronize the start position of the child component to the parent component, see the example below.
214
215```ts
216@Entry
217@ComponentV2
218struct Parent {
219  @Local childX: number = 0;
220  @Local childY: number = 1;
221  @Provider() onDrag: (x: number, y: number) => void = (x: number, y: number) => {
222    console.log(`onDrag event at x=${x} y:${y}`);
223    this.childX = x;
224    this.childY = y;
225  }
226
227  build() {
228    Column() {
229      Text(`child position x: ${this.childX}, y: ${this.childY}`)
230      Child()
231    }
232  }
233}
234
235@ComponentV2
236struct Child {
237  @Consumer() onDrag: (x: number, y: number) => void = (x: number, y: number) => {};
238
239  build() {
240    Button("changed")
241      .draggable(true)
242      .onDragStart((event: DragEvent) => {
243        // Current Previewer does not support common drag events.
244        this.onDrag(event.getDisplayX(), event.getDisplayY());
245      })
246  }
247}
248```
249
250
251### Decorating Complex Types by \@Provider and \@Consumer and Using together with \@Trace
252
2531. \@Provider and \@Consumer can only observe the changes of the data. If they are used to decorate complex data types and you need to observe the changes of the properties, \@Trace is also required.
2542. When decorating built-in types, such as Array, Map, Set, and Date, you can observe the changes of some APIs. The observation capability is the same as that of [\@Trace](./arkts-new-observedV2-and-trace.md#observed-changes).
255
256```ts
257@ObservedV2
258class User {
259  @Trace name: string;
260  @Trace age: number;
261
262  constructor(name: string, age: number) {
263    this.name = name;
264    this.age = age;
265  }
266}
267const data: User[] = [new User('Json', 10), new User('Eric', 15)];
268@Entry
269@ComponentV2
270struct Parent {
271  @Provider('data') users: User[] = data;
272
273  build() {
274    Column() {
275      Child()
276      Button('add new user')
277        .onClick(() => {
278          this.users.push(new User('Molly', 18));
279        })
280      Button('age++')
281        .onClick(() => {
282          this.users[0].age++;
283        })
284      Button('change name')
285        .onClick(() => {
286          this.users[0].name = 'Shelly';
287        })
288    }
289  }
290}
291
292@ComponentV2
293struct Child {
294  @Consumer('data') users: User[] = [];
295
296  build() {
297    Column() {
298      ForEach(this.users, (item: User) => {
299        Column() {
300          Text(`name: ${item.name}`).fontSize(30)
301          Text(`age: ${item.age}`).fontSize(30)
302          Divider()
303        }
304      })
305    }
306  }
307}
308```
309
310### Searching Upwards by \@Consumer for the Nearest \@Provider
311If \@Provider has duplicate names in the component tree, \@Consumer will search upwards for the \@Provider data of the nearest parent node.
312```ts
313@Entry
314@ComponentV2
315struct Index {
316  @Provider() val: number = 10;
317
318  build() {
319    Column() {
320      Parent()
321    }
322  }
323}
324
325@ComponentV2
326struct Parent {
327  @Provider() val: number = 20;
328  @Consumer("val") val2: number = 0; // 10
329
330  build() {
331    Column() {
332      Text(`${this.val2}`)
333      Child()
334    }
335  }
336}
337
338@ComponentV2
339struct Child {
340  @Consumer() val: number = 0; // 20
341
342  build() {
343    Column() {
344      Text(`${this.val}`)
345    }
346  }
347}
348```
349
350In the preceding example:
351
352- In **Parent**, \@Consumer searches upwards to find **@Provider() val: number = 10** defined in **Index**. Therefore, the value is initialized to 10.
353- In **Child**, \@Consumer searches upwards to find **@Provider() val: number = 20** defined in **Parent** and stops searching when it is found. Therefore, the value is initialized to 20.
354
355### Initializing \@Param by \@Provider and \@Consumer
356
357- Click **Text(\`Parent @Consumer val: ${this.val}\`)** to trigger the change of **@Consumer() val**. This change will be synchronized to **@Provider() val** in **Index**, triggering the re-render of the **Text(Parent @Param val2: ${this.val2})** in the **Child** component.
358- The change of **Parent @Consumer() val** is also synchronized to **Child**, triggering the re-render of **Text(Child @Param val ${this.val})**.
359
360```ts
361@Entry
362@ComponentV2
363struct Index {
364  @Provider() val: number = 10;
365
366  build() {
367    Column() {
368      Parent({ val2: this.val })
369    }
370  }
371}
372
373@ComponentV2
374struct Parent {
375  @Consumer() val: number = 0;
376  @Param val2: number = 0;
377
378  build() {
379    Column() {
380      Text(`Parent @Consumer val: ${this.val}`).fontSize(30).onClick(() => {
381        this.val++;
382      })
383      Text(`Parent @Param val2: ${this.val2}`).fontSize(30)
384      Child({ val: this.val })
385    }.border({ width: 2, color: Color.Green })
386  }
387}
388
389@ComponentV2
390struct Child {
391  @Param val: number = 0;
392
393  build() {
394    Column() {
395      Text(`Child @Param val ${this.val}`).fontSize(30)
396    }.border({ width: 2, color: Color.Pink })
397  }
398}
399```