Custom web components have revolutionised web development by enabling encapsulation, reusability, and dynamic functionality. Combined with Aurelia 2, a robust and extensible framework, the possibilities for building rich, sophisticated applications are endless. A key feature of Aurelia 2 is its extensible Attribute Mapper, which bridges HTML attributes and JavaScript properties, enabling significant customization to meet unique application requirements.
Scenario: Custom Web Components
Imagine developing an application that heavily utilizes custom web components, such as a custom slider element named <my-slider>
, a custom dropdown (<my-dropdown>
), and a custom date picker (<my-date-picker>
). Each component has unique attributes that need to be bound to properties on your view models.
One use case where you might want to do this is when working with a Web Component-based UI component library or existing project.
While Aurelia understands bindings for standard HTML elements, it needs guidance to handle these custom elements. By extending the Attribute Mapper, we can teach Aurelia to interpret specific bind commands as two-way bindings for our custom elements.
Extending the Attribute Mapper
The first step is to extend the Attribute Mapper. We need to tell Aurelia to interpret the bind
command as a two-way binding for the value
attribute of <my-slider>
, the selectedItem
and isOpen
attributes of <my-dropdown>
, and the selectedDate
and isEnabled
attributes of <my-date-picker>
.
In our MyAttributeMapperExtension
class, we define the extendAttributeMapper
method for this purpose:
private extendAttributeMapper() { this.attrMapper.useTwoWay((element, property) => { switch (element.tagName) { case 'MY-SLIDER': return property === 'value'; case 'MY-DROPDOWN': return property === 'selectedItem' || property === 'isOpen'; case 'MY-DATE-PICKER': return property === 'selectedDate' || property === 'isEnabled'; default: return false; } }); }
Configuring the Node Observer Locator
After extending the Attribute Mapper, we use the Node Observer Locator to tell Aurelia how to observe these properties for changes. For example, whenever the value
attribute of <my-slider>
changes, Aurelia should be notified to update the bound property on the view model.
We define the configureNodeObserverLocator
method in our MyAttributeMapperExtension
class for this purpose:
private configureNodeObserverLocator() { this.nodeObserverLocator.useConfig({ 'MY-SLIDER': { value: { events: ['valueChanged'] } }, 'MY-DROPDOWN': { selectedItem: { events: ['selectedItemChanged'] }, isOpen: { events: ['isOpenChanged'] } }, 'MY-DATE-PICKER': { selectedDate: { events: ['selectedDateChanged'] }, isEnabled: { events: ['isEnabledChanged'] } } }); }
Complete Code Example
Let’s put it all together. Here’s the complete TypeScript code:
import { IAttrMapper, INodeObserverLocator, AppTask, Aurelia } from 'aurelia'; export class MyAttributeMapperExtension { constructor( private attrMapper: IAttrMapper, private nodeObserverLocator: INodeObserverLocator ) { this.setup(); } private setup() { this.extendAttributeMapper(); this.configureNodeObserverLocator(); } private extendAttributeMapper() { this.attrMapper.useTwoWay((element, property) => { switch (element.tagName) { case 'MY-SLIDER': return property === 'value'; case 'MY-DROPDOWN': return property === 'selectedItem' || property === 'isOpen'; case 'MY-DATE-PICKER': return property === 'selectedDate' || property === 'isEnabled'; default: return false; } }); } private configureNodeObserverLocator() { this.nodeObserverLocator.useConfig({ 'MY-SLIDER': { value: { events: ['valueChanged'] } }, 'MY-DROPDOWN': { selectedItem: { events: ['selectedItemChanged'] }, isOpen: { events: ['isOpenChanged'] } }, 'MY-DATE-PICKER': { selectedDate: { events: ['selectedDateChanged'] }, isEnabled: { events: ['isEnabledChanged'] } } }); } } Aurelia .register( AppTask.creating(container => { const attrMapper = container.get(IAttrMapper); const nodeObserverLocator = container.get(INodeObserverLocator); new MyAttributeMapperExtension(attrMapper, nodeObserverLocator); }) ) .app(class MyApp {}) .start();
Conclusion
With Aurelia 2’s extensible Attribute Mapper and Node Observer Locator, we’ve empowered Aurelia to handle two-way data binding for custom elements. This leads to cleaner, more intuitive code, enhancing maintainability and readability in our Aurelia applications. We also facilitate better organisation and testing by encapsulating the logic for extending the Attribute Mapper and configuring the Node Observer Locator in a separate class. You are now equipped with the understanding and tools to adapt Aurelia to the unique needs of your applications.