1# Attribute Modifier (AttributeModifier)
2
3## Overview
4The introduction of the [@Styles](../quick-start/arkts-style.md) and [@Extend](../quick-start/arkts-extend.md) decorators in declarative syntax helps with reuse of custom styles, but they encounter limitations in certain scenarios:
5- Both @Styles and @Extend are processed at compile time and do not support cross-file exports for reuse.
6- @Styles only supports universal attributes and events, not component-specific attributes.
7- While @Styles allows for polymorphic styles, it does not support parameter passing, which means it cannot expose certain properties externally.
8- @Extend supports private attributes and events of specific components, but it does not support cross-file exports for reuse either.
9- Neither @Styles nor @Extend supports service logic for dynamically determining whether to set certain attributes. They only allow setting all possible attributes using ternary expressions, which is inefficient when dealing with a large number of attributes.
10
11
12To address the above issues, ArkUI introduces the **AttributeModifier** mechanism, which allows for dynamic modification of attributes through **Modifier** objects. The table below is a comparison of the capabilities between the **AttributeModifier** mechanism and the @Styles and @Extend decorators.
13|  Capability |  @Styles  |  @Extend  |  AttributeModifier  |
14| :-----: | :-----: | :-----: | :-----: |
15|  Cross-file export |  Not supported |  Not supported |  Supported |
16|  Universal attribute setting |  Supported |  Supported |  Supported |
17|  Universal event setting |  Supported |  Supported |  Partially supported |
18|  Component-specific attribute setting	 |  Not supported |  Supported |  Partially supported |
19|  Component-specific event setting |  Not supported |  Supported |  Partially supported |
20|  Parameter passing |  Not supported |  Supported |  Supported |
21|  Polymorphic styles |  Supported |  Not supported |  Supported |
22|  Service logic |  Not supported |  Not supported |  Supported |
23
24Clearly, when compared to @Styles and @Extend, **AttributeModifier** provides superior capabilities and flexibility. As it continues to evolve to encompass a full spectrum of attribute and event settings, **AttributeModifier** is the preferred choice for implementation.
25
26## API Definition
27
28```ts
29declare interface AttributeModifier<T> {
30
31  applyNormalAttribute?(instance: T): void;
32
33  applyPressedAttribute?(instance: T): void;
34
35  applyFocusedAttribute?(instance: T): void;
36
37  applyDisabledAttribute?(instance: T): void;
38
39  applySelectedAttribute?(instance: T): void;
40
41}
42```
43
44**AttributeModifier** is an API that requires you to implement methods in the form of **apply*Xxx*Attribute**. *Xxx* signifies various states of polymorphism and can be in the following: **Normal**, **Pressed**, **Focused**, **Disabled**, and **Selected**. **T** represents the attribute type of the component. Within the callback, you can access the attribute object and use it to set the attributes.
45
46```ts
47declare class CommonMethod<T> {
48  attributeModifier(modifier: AttributeModifier<T>): T;
49}
50```
51
52**attributeModifier** is a universal component method that allows you to pass in a custom modifier. Since the type **T** is explicitly defined when a component is instantiated, the type **T** passed to the method must be the corresponding attribute type for that component, or it must be **CommonAttribute**.
53
54## How to Use
55
56- The **attributeModifier** method accepts an instance that implements the **AttributeModifier\<T>** API. Here, **T** must be the specific attribute type corresponding to the component, or it must be **CommonAttribute**.
57- When a component is initialized for the first time or when its associated state variable changes, if the passed instance implements the corresponding API, the **applyNormalAttribute** callback will be invoked.
58- When the **applyNormalAttribute** callback is invoked, a component attribute object is passed in. Through this object, you can set the attributes and events of the current component.
59- If an attempt is made to execute attributes or events that are not yet supported, an exception will be thrown during execution.
60- When an attribute change triggers the **apply*Xxx*Attribute** API, any attributes that were previously set on the component but not included in the current change will revert to their default values.
61- The API can be used to leverage polymorphic styling capabilities. For example, if you need to set certain attributes when the component enters a pressed state, you can implement the **applyPressedAttribute** method to achieve this.
62- If the same attribute is set on a component using both attribute methods and **applyNormalAttribute**, the principle of property override is followed, which means that the last set attributes take effect.
63- A single **Modifier** instance object can be used across multiple components.
64- If **applyNormalAttribute** is used multiple times on a single component with different **Modifier** instances, each time the state variables are updated, the attribute settings of these instances will be executed in the order they were applied, which also follows the principle of property override.
65
66## Setting and Modifying Component Attributes
67
68**AttributeModifier** provides a powerful mechanism to separate the UI from styling. It enables the dynamic customization of component attributes with support for parameter passing and service logic writing, and triggers updates through state variables.
69
70  ```ts
71  // button_modifier.ets
72  export class MyButtonModifier implements AttributeModifier<ButtonAttribute> {
73    // A private member variable that can be dynamically modified externally
74    isDark: boolean = false
75
76    // The constructor allows for parameter passing when creating an instance.
77    constructor(dark?: boolean) {
78      this.isDark = dark ? dark : false
79    }
80
81    applyNormalAttribute(instance: ButtonAttribute): void {
82      // instance is the attribute object for the Button, which can be modified here.
83      if (this.isDark) {// Service logic can be written here.
84        // After attribute changes trigger the apply function, attributes that were set before but not included in the change will revert to their default values.
85        instance.backgroundColor('#707070')
86      } else {
87        // Chaining of attribute methods is supported.
88        instance.backgroundColor('#17A98D')
89          .borderColor('#707070')
90          .borderWidth(2)
91      }
92    }
93  }
94  ```
95  ```ts
96  // demo.ets
97  import { MyButtonModifier } from './button_modifier'
98
99  @Entry
100  @Component
101  struct attributeDemo {
102    // The modifier is decorated with @State, with behavior consistent with that of a regular object.
103    @State modifier: MyButtonModifier = new MyButtonModifier(true);
104
105    build() {
106      Row() {
107        Column() {
108          Button("Button")
109            .attributeModifier(this.modifier)
110            .onClick(() => {
111              // When the level-1 attribute of the modifier is changed, a UI update is triggered, causing applyNormalAttribute to be executed again.
112              this.modifier.isDark = !this.modifier.isDark
113            })
114        }
115        .width('100%')
116      }
117      .height('100%')
118    }
119  }
120  ```
121  ![AttributeModifier](figures/AttributeModifier01.gif)
122
123If the same attribute is set on a component using both attribute methods and **applyNormalAttribute**, the principle of property override is followed, which means that the last set attributes take effect.
124
125  ```ts
126  // button_modifier.ets
127  export class MyButtonModifier implements AttributeModifier<ButtonAttribute> {
128    isDark: boolean = false
129
130    constructor(dark?: boolean) {
131      this.isDark = dark ? dark : false
132    }
133
134    applyNormalAttribute(instance: ButtonAttribute): void {
135      if (this.isDark) {
136        instance.backgroundColor('#707070')
137      } else {
138        instance.backgroundColor('#17A98D')
139          .borderColor('#707070')
140          .borderWidth(2)
141      }
142    }
143  }
144  ```
145  ```ts
146  // demo.ets
147  import { MyButtonModifier } from './button_modifier';
148
149  @Entry
150  @Component
151  struct attributeDemo {
152    @State modifier: MyButtonModifier = new MyButtonModifier(true);
153
154    build() {
155      Row() {
156        Column() {
157          // As the attribute is set before the modifier, the button's color changes in accordance with the value of the modifier.
158          Button("Button")
159            .backgroundColor('#2787D9')
160            .attributeModifier(this.modifier)
161            .onClick(() => {
162              this.modifier.isDark = !this.modifier.isDark
163            })
164        }
165        .width('100%')
166      }
167      .height('100%')
168    }
169  }
170  ```
171  ![AttributeModifier](figures/AttributeModifier03.gif)
172
173If **applyNormalAttribute** is used multiple times on a single component with different **Modifier** instances, each time the state variables are updated, the attribute settings of these instances will be executed in the order they were applied, which also follows the principle of property override.
174
175  ```ts
176  // button_modifier.ets
177  export class MyButtonModifier implements AttributeModifier<ButtonAttribute> {
178    isDark: boolean = false
179
180    constructor(dark?: boolean) {
181      this.isDark = dark ? dark : false
182    }
183
184    applyNormalAttribute(instance: ButtonAttribute): void {
185      if (this.isDark) {
186        instance.backgroundColor(Color.Black)
187          .width(200)
188      } else {
189        instance.backgroundColor(Color.Red)
190          .width(100)
191      }
192    }
193  }
194  ```
195  ```ts
196  // button_modifier2.ets
197  export class MyButtonModifier2 implements AttributeModifier<ButtonAttribute> {
198    isDark2: boolean = false
199
200    constructor(dark?: boolean) {
201      this.isDark2 = dark ? dark : false
202    }
203
204    applyNormalAttribute(instance: ButtonAttribute): void {
205      if (this.isDark2) {
206        instance.backgroundColor('#2787D9')
207      } else {
208        instance.backgroundColor('#707070')
209      }
210    }
211  }
212  ```
213  ```ts
214  // demo.ets
215  import { MyButtonModifier } from './button_modifier';
216  import { MyButtonModifier2 } from './button_modifier2';
217
218  @Entry
219  @Component
220  struct attributeDemo {
221    @State modifier: MyButtonModifier = new MyButtonModifier(true);
222    @State modifier2: MyButtonModifier2 = new MyButtonModifier2(true);
223
224    build() {
225      Row() {
226        Column() {
227          Button("Button")
228            .attributeModifier(this.modifier)
229            .attributeModifier(this.modifier2)
230            .onClick(() => {
231              this.modifier.isDark = !this.modifier.isDark
232              this.modifier2.isDark2 = !this.modifier2.isDark2
233            })
234        }
235        .width('100%')
236      }
237      .height('100%')
238    }
239  }
240  ```
241  ![AttributeModifier](figures/AttributeModifier04.gif)
242
243## Setting Polymorphic Styles and Events
244
245You can use **AttributeModifier** to set polymorphic styles and events, which enables the reuse of event logic and supports various states such as **Normal**, **Pressed**, **Focused**, **Disabled**, and **Selected**. For example, if you need to set certain attributes when the component enters a pressed state, you can implement the **applyPressedAttribute** method to achieve this.
246
247  ```ts
248  // button_modifier.ets
249  export class MyButtonModifier implements AttributeModifier<ButtonAttribute> {
250    applyNormalAttribute(instance: ButtonAttribute): void {
251      // instance is the attribute object for the Button, used to set attributes for the normal state.
252      instance.backgroundColor('#17A98D')
253        .borderColor('#707070')
254        .borderWidth(2)
255    }
256
257    applyPressedAttribute(instance: ButtonAttribute): void {
258      // instance is the attribute object for the Button, used to set attributes for the pressed state.
259      instance.backgroundColor('#2787D9')
260        .borderColor('#FFC000')
261        .borderWidth(5)
262    }
263  }
264  ```
265  ```ts
266  // demo.ets
267  import { MyButtonModifier } from './button_modifier'
268
269  @Entry
270  @Component
271  struct attributeDemo {
272    @State modifier: MyButtonModifier = new MyButtonModifier();
273
274    build() {
275      Row() {
276        Column() {
277          Button("Button")
278            .attributeModifier(this.modifier)
279        }
280        .width('100%')
281      }
282      .height('100%')
283    }
284  }
285
286  ```
287  ![AttributeModifier](figures/AttributeModifier02.gif)
288