Value converters in Aurelia are powerful tools that transform data between your view-model and view. They're especially useful for formatting model data for display, but can also handle input conversion in two-way binding scenarios.
If you're familiar with value converters in other frameworks like Xaml, you'll find Aurelia's approach similar but with some neat improvements:
Clear method naming: 'toView' and 'fromView' explicitly indicate data flow direction.
Multi-parameter support: Converters can accept multiple arguments for complex transformations.
Composition: Chain multiple converters using the pipe (|) symbol.
External trigger support: Use the 'signals' field to update views based on external changes like language or locale.
Simple converters
Let's enhance our previous example with some basic value converters. We'll leverage Aurelia's capabilities alongside the popular and libraries to streamline our code. Here's how we can efficiently implement these improvements:
currency-format.js
import numeral from 'numeral';
export class CurrencyFormatValueConverter {
toView(value) {
return numeral(value).format('($0,0.00)');
}
}
date-format.js
import moment from 'moment';
export class DateFormatValueConverter {
toView(value) {
return moment(value).format('M/D/YYYY h:mm:ss a');
}
}
We created two value converters: DateFormatValueConverter and CurrencyFormatValueConverter. These converters have a toView method that Aurelia applies to model values before displaying them in the view. We leveraged MomentJS and NumeralJS libraries for date and currency formatting, respectively.
Then, we updated the view to include these converters. To use a resource like a value converter, add it to the require element with the appropriate path in the from attribute.
Aurelia processes resources by examining class metadata to determine their type (e.g., custom element, attribute, or value converter). While metadata isn't always necessary, Aurelia uses conventions to simplify development. For instance, export names ending with "ValueConverter" are automatically recognized as value converters. The registration uses the camel-cased export name, minus the "ValueConverter" suffix.
The name that a resource is referenced by in a view derives from its export name. For Value Converters and Binding Behaviors, the export name is converted to camel case (think of it as a variable name). For Custom Elements and Custom Attributes, the export name is lower-cased and hyphenated (to comply with HTML element and attribute specifications).
Converter parameters
While the previous converters work well, they're limited to a single format. What if we need to display dates and numbers in various ways? Rather than creating a converter for each format, let's make our converters more flexible by accepting a format parameter. This approach allows us to specify the desired format directly in the binding, maximizing the reusability of our converters.
numeral-format.js
import numeral from 'numeral';
export class NumberFormatValueConverter {
toView(value, format) {
return numeral(value).format(format);
}
}
date-format.js
import moment from 'moment';
export class DateFormatValueConverter {
toView(value, format) {
return moment(value).format(format);
}
}
With the format parameter added to the toView methods, we can specify the format in the binding using the [expression] | [converterName]:[parameterExpression] syntax:
${currentDate | dateFormat:'MMMM Mo YYYY'} <br>
${netWorth | numberFormat:'$0.0a'} <br>
Binding converter parameters
While you can use literal values for converter parameters, binding them offers more flexibility. This approach allows for dynamic results that can change based on your application's state or user input.
number-format.js
import numeral from "numeral";
export class NumberFormatValueConverter {
toView(value, format) {
return numeral(value).format(format);
}
}
Value converters in Aurelia are versatile, accepting multiple parameters and allowing composition within a single binding expression. This design promotes flexibility and reusability.
Consider this example:
Our view-model contains an array of Aurelia repositories. The view uses a repeat binding to display these repos in a table. We apply two value converters:
SortValueConverter: Sorts the array based on 'propertyName' and 'direction'.
TakeValueConverter: Limits the number of displayed repositories.
This demonstrates how multiple converters can work together to transform data efficiently.
sort.js
export class SortValueConverter {
toView(array, propertyName, direction) {
let factor = direction === 'ascending' ? 1 : -1;
return array.slice(0).sort((a, b) => {
return (a[propertyName] - b[propertyName]) * factor;
});
}
}
Aurelia supports object converter parameters. An alternate implementation of the SortValueConverter using a single config parameter would look like this:
This approach has a couple of advantages: you don't need to remember the order of the converter parameter arguments, and anyone reading the markup can easily tell what each converter parameter represents.
Bi-directional value converters
Converters aren't just for displaying data but also crucial when handling user input. While to-view bindings use a converter's toView method, two-way bindings on input elements need the fromView method as well. This method transforms user input into the format your view-model expects.
Let's look at a practical example: binding a color object to an HTML5 color input. Our view-model stores colors as objects with red, green, and blue properties, but the color input requires a hex format. An RgbToHexValueConverter bridges this gap, ensuring smooth data flow in both directions.
We've been using the require element to import converters in our views, but there's a simpler approach. For frequently used value converters, Aurelia's globalResources function allows you to register them globally. This eliminates the need to add require elements at the top of each view, streamlining your code.
Let's make the rgb-to-hex value converter globally accessible:
Then in our app.js file where we bootstrap Aurelia, we use globalResources to register our value converter:
import {Aurelia} from 'aurelia-framework';
import {PLATFORM} from 'aurelia-pal';
export function configure(aurelia: Aurelia) {
aurelia.use
.standardConfiguration()
.developmentLogging();
// Register global resources (value converters, custom elements, etc.)
aurelia.use.globalResources([
PLATFORM.moduleName('resources/value-converters/rgb-to-hex'),
]);
aurelia.start().then(() => aurelia.setRoot(PLATFORM.moduleName('app')));
}
Signalable value converters
Value converters sometimes rely on global parameters that Aurelia can't directly observe, like timezones or hardware changes. In other cases, you might need to simultaneously update all bindings using a specific converter, such as when changing the application's language.
Aurelia provides a signaling mechanism for value converters to address these scenarios. By declaring a 'signals' property on a converter, you can trigger updates for all bindings using that converter.
Consider a flight information display system. The view-model contains a list of flights, and the view shows each flight's time using a clock format. The display format depends on a global currentLocale variable. Using signals, you can update all flight displays when the locale changes.
To implement this, use the framework's signalBindings export to trigger the value converter's signal, refreshing all related bindings.
import {signalBindings} from 'aurelia-framework';
signalBindings('locale-changed');
flight-time-value-converter.js
export class FlightTimeValueConverter {
signals = ["locale-changed"];
toView(val) {
let newVal =
val instanceof Date
? val.toLocaleString(window.currentLocale)
: val === null
? ""
: val; // eslint-disable-line
return newVal;
}
}