1e41f4b71Sopenharmony_ci# AttributeModifier 2e41f4b71Sopenharmony_ci 3e41f4b71Sopenharmony_ci## Overview 4e41f4b71Sopenharmony_ciThe introduction of the @Styles and @Extend decorators in declarative syntax helps address some reuse issues, but there are limitations in certain scenarios: 5e41f4b71Sopenharmony_ci- Both @Styles and @Extend are processed at compile time and do not support cross-file exports for reuse. 6e41f4b71Sopenharmony_ci- @Styles only supports universal attributes and events, not component-specific attributes. 7e41f4b71Sopenharmony_ci- Although @Styles supports the use of polymorphic styles, it does not support parameter passing, which means it cannot expose certain properties externally. 8e41f4b71Sopenharmony_ci- @Extend supports private attributes and events of specific components, but it does not support cross-file exports for reuse either. 9e41f4b71Sopenharmony_ci- 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. 10e41f4b71Sopenharmony_ci 11e41f4b71Sopenharmony_ci 12e41f4b71Sopenharmony_ciTo 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. 13e41f4b71Sopenharmony_ci| Capability | @Styles | @Extend | AttributeModifier | 14e41f4b71Sopenharmony_ci| :-----: | :-----: | :-----: | :-----: | 15e41f4b71Sopenharmony_ci| Cross-file export | Not supported | Not supported | Supported | 16e41f4b71Sopenharmony_ci| Universal attribute setting | Supported | Supported | Supported | 17e41f4b71Sopenharmony_ci| Universal event setting | Supported | Supported | Partially supported | 18e41f4b71Sopenharmony_ci| Component-specific attribute setting | Not supported | Supported | Partially supported | 19e41f4b71Sopenharmony_ci| Component-specific event setting | Not supported | Supported | Partially supported | 20e41f4b71Sopenharmony_ci| Parameter passing | Not supported | Supported | Supported | 21e41f4b71Sopenharmony_ci| Polymorphic styles | Supported | Not supported | Supported | 22e41f4b71Sopenharmony_ci| Service logic | Not supported | Not supported | Supported | 23e41f4b71Sopenharmony_ci 24e41f4b71Sopenharmony_ciIt is evident that **AttributeModifier** offers enhanced capabilities and flexibility compared to @Styles and @Extend. Currently, efforts are underway to expand its functionality to cover all aspects of attribute and event settings. Looking ahead, **AttributeModifier** is expected to offer most, if not all, of the functionalities provided by @Styles and @Extend. In light of its superior adaptability and the ongoing development to support a comprehensive range of features, it is recommended that you use **AttributeModifier** for the aforementioned scenarios. 25e41f4b71Sopenharmony_ci 26e41f4b71Sopenharmony_ci## API 27e41f4b71Sopenharmony_ci 28e41f4b71Sopenharmony_ci```ts 29e41f4b71Sopenharmony_cideclare interface AttributeModifier<T> { 30e41f4b71Sopenharmony_ci 31e41f4b71Sopenharmony_ci applyNormalAttribute?(instance: T): void; 32e41f4b71Sopenharmony_ci 33e41f4b71Sopenharmony_ci applyPressedAttribute?(instance: T): void; 34e41f4b71Sopenharmony_ci 35e41f4b71Sopenharmony_ci applyFocusedAttribute?(instance: T): void; 36e41f4b71Sopenharmony_ci 37e41f4b71Sopenharmony_ci applyDisabledAttribute?(instance: T): void; 38e41f4b71Sopenharmony_ci 39e41f4b71Sopenharmony_ci applySelectedAttribute?(instance: T): void; 40e41f4b71Sopenharmony_ci 41e41f4b71Sopenharmony_ci} 42e41f4b71Sopenharmony_ci``` 43e41f4b71Sopenharmony_ci 44e41f4b71Sopenharmony_ci**AttributeModifier** is an API that requires you to implement methods in the form of ApplyXxxAttribute. *Xxx* signifies various states of polymorphism, including the default state, pressed state, focused state, disabled state, and selected state. **T** represents the attribute type of the component. Within the callback, you can access the attribute object and use it to set the attributes. 45e41f4b71Sopenharmony_ci 46e41f4b71Sopenharmony_ci```ts 47e41f4b71Sopenharmony_cideclare class CommonMethod<T> { 48e41f4b71Sopenharmony_ci attributeModifier(modifier: AttributeModifier<T>): T; 49e41f4b71Sopenharmony_ci} 50e41f4b71Sopenharmony_ci``` 51e41f4b71Sopenharmony_ci 52e41f4b71Sopenharmony_ci**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**. 53e41f4b71Sopenharmony_ci 54e41f4b71Sopenharmony_ci## Behavior Specifications 55e41f4b71Sopenharmony_ci 56e41f4b71Sopenharmony_ci- 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**. 57e41f4b71Sopenharmony_ci- 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. 58e41f4b71Sopenharmony_ci- 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. 59e41f4b71Sopenharmony_ci- If an attempt is made to execute attributes or events that are not yet supported, an exception will be thrown during execution. 60e41f4b71Sopenharmony_ci- 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. 61e41f4b71Sopenharmony_ci- 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. 62e41f4b71Sopenharmony_ci- 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. 63e41f4b71Sopenharmony_ci- A single **Modifier** instance object can be used across multiple components. 64e41f4b71Sopenharmony_ci- 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. 65e41f4b71Sopenharmony_ci 66e41f4b71Sopenharmony_ci## Attribute Setting and Modification 67e41f4b71Sopenharmony_ci 68e41f4b71Sopenharmony_ci**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. 69e41f4b71Sopenharmony_ci 70e41f4b71Sopenharmony_ci ```ts 71e41f4b71Sopenharmony_ci // button_modifier.ets 72e41f4b71Sopenharmony_ci export class MyButtonModifier implements AttributeModifier<ButtonAttribute> { 73e41f4b71Sopenharmony_ci // A private member variable that can be dynamically modified externally 74e41f4b71Sopenharmony_ci isDark: boolean = false 75e41f4b71Sopenharmony_ci 76e41f4b71Sopenharmony_ci // The constructor allows for parameter passing when creating an instance. 77e41f4b71Sopenharmony_ci constructor(dark?: boolean) { 78e41f4b71Sopenharmony_ci this.isDark = dark ? dark : false 79e41f4b71Sopenharmony_ci } 80e41f4b71Sopenharmony_ci 81e41f4b71Sopenharmony_ci applyNormalAttribute(instance: ButtonAttribute): void { 82e41f4b71Sopenharmony_ci // instance is the attribute object for the Button, which can be modified here. 83e41f4b71Sopenharmony_ci if (this.isDark) {// Service logic can be written here. 84e41f4b71Sopenharmony_ci // After attribute changes trigger the apply function, attributes that were set before but not included in the change will revert to their default values. 85e41f4b71Sopenharmony_ci instance.backgroundColor(Color.Black) 86e41f4b71Sopenharmony_ci } else { 87e41f4b71Sopenharmony_ci // Chaining of attribute methods is supported. 88e41f4b71Sopenharmony_ci instance.backgroundColor(Color.Red) 89e41f4b71Sopenharmony_ci .borderColor(Color.Black) 90e41f4b71Sopenharmony_ci .borderWidth(2) 91e41f4b71Sopenharmony_ci } 92e41f4b71Sopenharmony_ci } 93e41f4b71Sopenharmony_ci } 94e41f4b71Sopenharmony_ci ``` 95e41f4b71Sopenharmony_ci ```ts 96e41f4b71Sopenharmony_ci // demo.ets 97e41f4b71Sopenharmony_ci import { MyButtonModifier } from './button_modifier' 98e41f4b71Sopenharmony_ci 99e41f4b71Sopenharmony_ci @Entry 100e41f4b71Sopenharmony_ci @Component 101e41f4b71Sopenharmony_ci struct attributeDemo { 102e41f4b71Sopenharmony_ci // The modifier is decorated with @State, with behavior consistent with that of a regular object. 103e41f4b71Sopenharmony_ci @State modifier: MyButtonModifier = new MyButtonModifier(true); 104e41f4b71Sopenharmony_ci 105e41f4b71Sopenharmony_ci build() { 106e41f4b71Sopenharmony_ci Row() { 107e41f4b71Sopenharmony_ci Column() { 108e41f4b71Sopenharmony_ci Button("Button") 109e41f4b71Sopenharmony_ci .attributeModifier(this.modifier) 110e41f4b71Sopenharmony_ci .onClick(() => { 111e41f4b71Sopenharmony_ci // When the level-1 attribute of the modifier is changed, a UI update is triggered, causing applyNormalAttribute to be executed again. 112e41f4b71Sopenharmony_ci this.modifier.isDark = !this.modifier.isDark 113e41f4b71Sopenharmony_ci }) 114e41f4b71Sopenharmony_ci } 115e41f4b71Sopenharmony_ci .width('100%') 116e41f4b71Sopenharmony_ci } 117e41f4b71Sopenharmony_ci .height('100%') 118e41f4b71Sopenharmony_ci } 119e41f4b71Sopenharmony_ci } 120e41f4b71Sopenharmony_ci ``` 121e41f4b71Sopenharmony_ci  122e41f4b71Sopenharmony_ci 123e41f4b71Sopenharmony_ciIf 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. 124e41f4b71Sopenharmony_ci 125e41f4b71Sopenharmony_ci ```ts 126e41f4b71Sopenharmony_ci // button_modifier.ets 127e41f4b71Sopenharmony_ci export class MyButtonModifier implements AttributeModifier<ButtonAttribute> { 128e41f4b71Sopenharmony_ci isDark: boolean = false 129e41f4b71Sopenharmony_ci 130e41f4b71Sopenharmony_ci constructor(dark?: boolean) { 131e41f4b71Sopenharmony_ci this.isDark = dark ? dark : false 132e41f4b71Sopenharmony_ci } 133e41f4b71Sopenharmony_ci 134e41f4b71Sopenharmony_ci applyNormalAttribute(instance: ButtonAttribute): void { 135e41f4b71Sopenharmony_ci if (this.isDark) { 136e41f4b71Sopenharmony_ci instance.backgroundColor(Color.Black) 137e41f4b71Sopenharmony_ci } else { 138e41f4b71Sopenharmony_ci instance.backgroundColor(Color.Red) 139e41f4b71Sopenharmony_ci .borderColor(Color.Black) 140e41f4b71Sopenharmony_ci .borderWidth(2) 141e41f4b71Sopenharmony_ci } 142e41f4b71Sopenharmony_ci } 143e41f4b71Sopenharmony_ci } 144e41f4b71Sopenharmony_ci ``` 145e41f4b71Sopenharmony_ci ```ts 146e41f4b71Sopenharmony_ci // demo.ets 147e41f4b71Sopenharmony_ci import { MyButtonModifier } from './button_modifier'; 148e41f4b71Sopenharmony_ci 149e41f4b71Sopenharmony_ci @Entry 150e41f4b71Sopenharmony_ci @Component 151e41f4b71Sopenharmony_ci struct attributeDemo { 152e41f4b71Sopenharmony_ci @State modifier: MyButtonModifier = new MyButtonModifier(true); 153e41f4b71Sopenharmony_ci 154e41f4b71Sopenharmony_ci build() { 155e41f4b71Sopenharmony_ci Row() { 156e41f4b71Sopenharmony_ci Column() { 157e41f4b71Sopenharmony_ci // As the attribute is set before the modifier, the button's color changes in accordance with the value of the modifier. 158e41f4b71Sopenharmony_ci Button("Button") 159e41f4b71Sopenharmony_ci .backgroundColor(Color.Blue) 160e41f4b71Sopenharmony_ci .attributeModifier(this.modifier) 161e41f4b71Sopenharmony_ci .onClick(() => { 162e41f4b71Sopenharmony_ci this.modifier.isDark = !this.modifier.isDark 163e41f4b71Sopenharmony_ci }) 164e41f4b71Sopenharmony_ci } 165e41f4b71Sopenharmony_ci .width('100%') 166e41f4b71Sopenharmony_ci } 167e41f4b71Sopenharmony_ci .height('100%') 168e41f4b71Sopenharmony_ci } 169e41f4b71Sopenharmony_ci } 170e41f4b71Sopenharmony_ci ``` 171e41f4b71Sopenharmony_ci  172e41f4b71Sopenharmony_ci 173e41f4b71Sopenharmony_ciIf **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. 174e41f4b71Sopenharmony_ci 175e41f4b71Sopenharmony_ci ```ts 176e41f4b71Sopenharmony_ci // button_modifier.ets 177e41f4b71Sopenharmony_ci export class MyButtonModifier implements AttributeModifier<ButtonAttribute> { 178e41f4b71Sopenharmony_ci isDark: boolean = false 179e41f4b71Sopenharmony_ci 180e41f4b71Sopenharmony_ci constructor(dark?: boolean) { 181e41f4b71Sopenharmony_ci this.isDark = dark ? dark : false 182e41f4b71Sopenharmony_ci } 183e41f4b71Sopenharmony_ci 184e41f4b71Sopenharmony_ci applyNormalAttribute(instance: ButtonAttribute): void { 185e41f4b71Sopenharmony_ci if (this.isDark) { 186e41f4b71Sopenharmony_ci instance.backgroundColor(Color.Black) 187e41f4b71Sopenharmony_ci .width(200) 188e41f4b71Sopenharmony_ci } else { 189e41f4b71Sopenharmony_ci instance.backgroundColor(Color.Red) 190e41f4b71Sopenharmony_ci .width(100) 191e41f4b71Sopenharmony_ci } 192e41f4b71Sopenharmony_ci } 193e41f4b71Sopenharmony_ci } 194e41f4b71Sopenharmony_ci ``` 195e41f4b71Sopenharmony_ci ```ts 196e41f4b71Sopenharmony_ci // button_modifier2.ets 197e41f4b71Sopenharmony_ci export class MyButtonModifier2 implements AttributeModifier<ButtonAttribute> { 198e41f4b71Sopenharmony_ci isDark2: boolean = false 199e41f4b71Sopenharmony_ci 200e41f4b71Sopenharmony_ci constructor(dark?: boolean) { 201e41f4b71Sopenharmony_ci this.isDark2 = dark ? dark : false 202e41f4b71Sopenharmony_ci } 203e41f4b71Sopenharmony_ci 204e41f4b71Sopenharmony_ci applyNormalAttribute(instance: ButtonAttribute): void { 205e41f4b71Sopenharmony_ci if (this.isDark2) { 206e41f4b71Sopenharmony_ci instance.backgroundColor('#2787D9') 207e41f4b71Sopenharmony_ci } else { 208e41f4b71Sopenharmony_ci instance.backgroundColor('#707070') 209e41f4b71Sopenharmony_ci } 210e41f4b71Sopenharmony_ci } 211e41f4b71Sopenharmony_ci } 212e41f4b71Sopenharmony_ci ``` 213e41f4b71Sopenharmony_ci ```ts 214e41f4b71Sopenharmony_ci // demo.ets 215e41f4b71Sopenharmony_ci import { MyButtonModifier } from './button_modifier'; 216e41f4b71Sopenharmony_ci import { MyButtonModifier2 } from './button_modifier2'; 217e41f4b71Sopenharmony_ci 218e41f4b71Sopenharmony_ci @Entry 219e41f4b71Sopenharmony_ci @Component 220e41f4b71Sopenharmony_ci struct attributeDemo { 221e41f4b71Sopenharmony_ci @State modifier: MyButtonModifier = new MyButtonModifier(true); 222e41f4b71Sopenharmony_ci @State modifier2: MyButtonModifier2 = new MyButtonModifier2(true); 223e41f4b71Sopenharmony_ci 224e41f4b71Sopenharmony_ci build() { 225e41f4b71Sopenharmony_ci Row() { 226e41f4b71Sopenharmony_ci Column() { 227e41f4b71Sopenharmony_ci Button("Button") 228e41f4b71Sopenharmony_ci .attributeModifier(this.modifier) 229e41f4b71Sopenharmony_ci .attributeModifier(this.modifier2) 230e41f4b71Sopenharmony_ci .onClick(() => { 231e41f4b71Sopenharmony_ci this.modifier.isDark = !this.modifier.isDark 232e41f4b71Sopenharmony_ci this.modifier2.isDark2 = !this.modifier2.isDark2 233e41f4b71Sopenharmony_ci }) 234e41f4b71Sopenharmony_ci } 235e41f4b71Sopenharmony_ci .width('100%') 236e41f4b71Sopenharmony_ci } 237e41f4b71Sopenharmony_ci .height('100%') 238e41f4b71Sopenharmony_ci } 239e41f4b71Sopenharmony_ci } 240e41f4b71Sopenharmony_ci ``` 241e41f4b71Sopenharmony_ci  242e41f4b71Sopenharmony_ci 243e41f4b71Sopenharmony_ci## Polymorphic Style and Event Setting 244e41f4b71Sopenharmony_ci 245e41f4b71Sopenharmony_ciYou can use **AttributeModifier** to set polymorphic styles and events, which enables the reuse of event logic and supports various states such as default, 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. 246e41f4b71Sopenharmony_ci 247e41f4b71Sopenharmony_ci ```ts 248e41f4b71Sopenharmony_ci // button_modifier.ets 249e41f4b71Sopenharmony_ci export class MyButtonModifier implements AttributeModifier<ButtonAttribute> { 250e41f4b71Sopenharmony_ci applyNormalAttribute(instance: ButtonAttribute): void { 251e41f4b71Sopenharmony_ci // instance is the attribute object for the Button, used to set attributes for the normal state. 252e41f4b71Sopenharmony_ci instance.backgroundColor(Color.Red) 253e41f4b71Sopenharmony_ci .borderColor(Color.Black) 254e41f4b71Sopenharmony_ci .borderWidth(2) 255e41f4b71Sopenharmony_ci } 256e41f4b71Sopenharmony_ci 257e41f4b71Sopenharmony_ci applyPressedAttribute(instance: ButtonAttribute): void { 258e41f4b71Sopenharmony_ci // instance is the attribute object for the Button, used to set attributes for the pressed state. 259e41f4b71Sopenharmony_ci instance.backgroundColor(Color.Green) 260e41f4b71Sopenharmony_ci .borderColor(Color.Orange) 261e41f4b71Sopenharmony_ci .borderWidth(5) 262e41f4b71Sopenharmony_ci } 263e41f4b71Sopenharmony_ci } 264e41f4b71Sopenharmony_ci ``` 265e41f4b71Sopenharmony_ci ```ts 266e41f4b71Sopenharmony_ci // demo.ets 267e41f4b71Sopenharmony_ci import { MyButtonModifier } from './button_modifier' 268e41f4b71Sopenharmony_ci 269e41f4b71Sopenharmony_ci @Entry 270e41f4b71Sopenharmony_ci @Component 271e41f4b71Sopenharmony_ci struct attributeDemo { 272e41f4b71Sopenharmony_ci @State modifier: MyButtonModifier = new MyButtonModifier(); 273e41f4b71Sopenharmony_ci 274e41f4b71Sopenharmony_ci build() { 275e41f4b71Sopenharmony_ci Row() { 276e41f4b71Sopenharmony_ci Column() { 277e41f4b71Sopenharmony_ci Button("Button") 278e41f4b71Sopenharmony_ci .attributeModifier(this.modifier) 279e41f4b71Sopenharmony_ci } 280e41f4b71Sopenharmony_ci .width('100%') 281e41f4b71Sopenharmony_ci } 282e41f4b71Sopenharmony_ci .height('100%') 283e41f4b71Sopenharmony_ci } 284e41f4b71Sopenharmony_ci } 285e41f4b71Sopenharmony_ci 286e41f4b71Sopenharmony_ci ``` 287e41f4b71Sopenharmony_ci  288