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