Aurelia 1 Docs
Website
  • Introduction
  • Introduction
    • Quick Start
    • Aurelia for new developers
    • Contact manager tutorial
  • Components
    • Component basics
    • Component lifecycles
    • Styling components
    • Component options
    • Bindable properties
    • Slotted content
    • Template compilation using processContent
  • Templates
    • Template syntax
      • Attribute binding
      • Event binding
      • Text interpolation
      • Element content
      • Function references
      • Element references
      • Contextual properties
      • Expression syntax
      • Binding
    • Custom attributes
    • Value converters (pipes)
    • Binding behaviors
    • Form inputs
    • CSS classes and styling
    • List rendering (repeaters)
    • Conditional rendering
    • HTML & SVG attributes
    • The as-element (element aliasing)
    • View Resource pipeline
    • View and Compilation Spies
  • Getting to know Aurelia
    • App configuration and startup
    • Routing
      • Getting started
      • Router configuration
      • Configuring routes
      • Viewports
      • Layouts
      • Redirecting routes
      • View Swapping and Animation
      • Internationalizing Titles
      • Fallback Routes
      • Navigating
      • Lifecycle hooks
      • Router pipelines (hooks)
      • Reusing an Existing View Model
      • Router events
    • Enhance
    • Dynamic composition
    • Observation
    • Dependency injection (DI)
      • Overview
      • Injection
      • Resolvers
    • Task queue
    • Event aggregator
    • Server side rendering
      • Caveats
      • How it works
      • Isomorphic code
      • Memory management
      • Different entry points
      • 404 pages
    • CLI
      • Machine setup
      • Creating a new project
      • Running your app
      • Environments
      • Building your app
      • Generators
      • Configuration with aurelia.json
      • Webpack vs built-in bundler
      • Gulp tasks
      • Webpack
      • Built-in bundler
      • Updating & migrating
      • Recipes & known issues
      • Getting help
  • Developer guides
    • Animation
    • Testing
      • Unit Testing
        • Testing Custom Elements
        • Testing Custom Attributes
        • Testing Routing and Navigation
        • Advanced Testing
        • Helpful Properties, Functions, and Advanced Querying
        • Dependency Injection Testing
        • Testing Custom Value Converters and Binding Behaviors
        • Internationalization (i18n) Testing
      • End-to-end testing
        • Getting started with Playwright
        • Writing E2E tests
        • Advanced Techniques
    • Logging
    • Building plugins
      • Plugin Development
      • Building and Publishing
      • Advanced Plugin Techniques
    • UI Virtualization
    • Integrations
    • Cheat sheet
  • Aurelia packages
    • Validation
    • i18n Internationalization
    • Dialogs
    • Fetch client
    • HTTP client
    • Store
      • Configuring your app
      • What is the State?
      • Reasons for state management
      • Subscribing to the stream of states
      • Subscribing with the connectTo decorator
      • What are actions?
      • Async actions
      • Dispatching actions
      • Resetting the store to a specific state
      • Two-way binding and state management
      • Recording a navigable history of the stream of states
      • Making our app history-aware
      • Navigating through history
      • Handling side-effects with middleware
      • Accessing the original (unmodified) state in a middleware
      • Defining settings for middlewares
      • Error Propagation with Middlewares
      • Default middlewares
      • Tracking overall performance
      • Defining custom devToolsOptions
      • Defining custom loglevels
      • Debugging with the Redux devtools extension
      • Comparison to other state management libraries
Powered by GitBook
On this page
  • Simple converters
  • Converter parameters
  • Binding converter parameters
  • Multiple parameters & chaining
  • Object parameters
  • Bi-directional value converters
  • Globally accessible value converters
  • Signalable value converters

Was this helpful?

Export as PDF
  1. Templates

Value converters (pipes)

PreviousCustom attributesNextBinding behaviors

Last updated 8 months ago

Was this helpful?

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:

  1. Clear method naming: 'toView' and 'fromView' explicitly indicate data flow direction.

  2. Data-bound parameters: Unlike Xaml, Aurelia allows dynamic converter parameters.

  3. Multi-parameter support: Converters can accept multiple arguments for complex transformations.

  4. Composition: Chain multiple converters using the pipe (|) symbol.

  5. 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');
  }
}
net-worth.js
export class NetWorth {
  constructor() {
    this.update();
    setInterval(() => this.update(), 1000);
  }

  update() {
    this.currentDate = new Date();
    this.netWorth = Math.random() * 1000000000;
  }
}
net-worth.html
<template>
  <require from="./date-format"></require>
  <require from="./currency-format"></require>

  ${currentDate | dateFormat} <br>
  ${netWorth | currencyFormat}
</template>

So, how did we achieve this improved result?

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.

<require from="./date-format"></require>
<require from="./currency-format"></require>

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.

Examples:

  • DateFormatValueConverter becomes dateFormat

  • CurrencyFormatValueConverter becomes currencyFormat

Use the pipe (|) syntax to apply a converter in bindings.

${currentDate | dateFormat} <br>
${netWorth | currencyFormat}

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);
  }
}
net-worth.js
export class NetWorth {
  constructor() {
    this.update();
    setInterval(() => this.update(), 1000);
  }

  update() {
    this.currentDate = new Date();
    this.netWorth = Math.random() * 1000000000;
  }
}
<template>
  <require from="./date-format"></require>
  <require from="./number-format"></require>

  ${currentDate | dateFormat:'M/D/YYYY h:mm:ss a'} <br>
  ${currentDate | dateFormat:'MMMM Mo YYYY'} <br>
  ${currentDate | dateFormat:'h:mm:ss a'} <br>
  ${netWorth | numberFormat:'$0,0.00'} <br>
  ${netWorth | numberFormat:'$0.0a'} <br>
  ${netWorth | numberFormat:'0.00000)'}
</template>

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);
  }
}
net-worth.js
export class NetWorth {
  constructor() {
    this.update();
    setInterval(() => this.update(), 1000);
  }

  update() {
    this.netWorth = Math.random() * 1000000000;
  }
}
net-worth.html
<template>
  <require from="./number-format"></require>

  <label for="formatSelect">Select Format:</label>
  <select id="formatSelect" ref="formatSelect">
    <option value="$0,0.00">$0,0.00</option>
    <option value="$0.0a">$0.0a</option>
    <option value="0.00000">0.00000</option>
  </select>

  ${netWorth | numberFormat:formatSelect.value}
</template>

Multiple parameters & chaining

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:

  1. SortValueConverter: Sorts the array based on 'propertyName' and 'direction'.

  2. 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;
    });
  }
}
take.js
export class TakeValueConverter {
  toView(array, count) {
    return array.slice(0, count);
  }
}
app.js
import {HttpClient} from 'aurelia-http-client';

export class App {
  repos = [];

  activate() {
    return new HttpClient()
      .get('https://api.github.com/orgs/aurelia/repos')
      .then(response => this.repos = response.content);
  }
}
app.html
<template>
  <require from="./sort"></require>
  <require from="./take"></require>

  <label for="column">Sort By:</label>
  <select id="column" ref="column">
    <option value="stargazers_count">Stars</option>
    <option value="forks_count">Forks</option>
    <option value="open_issues">Issues</option>
  </select>

  <select ref="direction">
    <option value="descending">Descending</option>
    <option value="ascending">Ascending</option>
  </select>

  <table class="table table-striped">
    <thead>
      <tr>
        <th>Name</th>
        <th>Stars</th>
        <th>Forks</th>
        <th>Issues</th>
      </tr>
    </thead>
    <tbody>
      <tr repeat.for="repo of repos | sort:column.value:direction.value | take:10">
        <td>${repo.name}</td>
        <td>${repo.stargazers_count}</td>
        <td>${repo.forks_count}</td>
        <td>${repo.open_issues}</td>
      </tr>
    </tbody>
  </table>
</template>

Object parameters

Aurelia supports object converter parameters. An alternate implementation of the SortValueConverter using a single config parameter would look like this:

sort.js
export class SortValueConverter {
  toView(array, config) {
    let factor = (config.direction || 'ascending') === 'ascending' ? 1 : -1;
    return array.sort((a, b) => {
      return (a[config.propertyName] - b[config.propertyName]) * factor;
    });
  }
}
app.js
import {HttpClient} from 'aurelia-http-client';

export class App {
  repos = [];

  activate() {
    return new HttpClient()
      .get('https://api.github.com/orgs/aurelia/repos')
      .then(response => this.repos = response.content);
  }
}
app.html
<template>
  <require from="./sort"></require>

  <div class="row">
    <div class="col-sm-3"
          repeat.for="repo of repos | sort: { propertyName: 'open_issues', direction: 'descending' }">
      <a href="${repo.html_url}/issues" target="_blank">
        ${repo.name} (${repo.open_issues})
      </a>
    </div>
  </div>
</template>

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.

rgb-to-hex.js
export class RgbToHexValueConverter {
  toView(rgb) {
    return "#" + (
      (1 << 24) + (rgb.r << 16) + (rgb.g << 8) + rgb.b
    ).toString(16).slice(1);
  }

  fromView(hex) {
    let exp = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i,
        result = exp.exec(hex);
    return {
      r: parseInt(result[1], 16),
      g: parseInt(result[2], 16),
      b: parseInt(result[3], 16)
    };
  }
}
app.js
export class App {
  rgb = { r: 146, g: 39, b: 143 };
}
app.html
<template>
  <require from="./rgb-to-hex"></require>

  <label for="color">Select Color:</label>
  <input id="color" type="color" value.bind="rgb | rgbToHex">
  <br> r: ${rgb.r}, g:${rgb.g}, b:${rgb.b}
</template>

Globally accessible value converters

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:

rgb-to-hex.js
export class RgbToHexValueConverter {
  toView(rgb) {
    return "#" + (
      (1 << 24) + (rgb.r << 16) + (rgb.g << 8) + rgb.b
    ).toString(16).slice(1);r
  }

  fromView(hex) {
    let exp = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i,
        result = exp.exec(hex);
    return {
      r: parseInt(result[1], 16),
      g: parseInt(result[2], 16),
      b: parseInt(result[3], 16)
    };
  }
}

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;
  }
}
app.js
import { signalBindings } from "aurelia-binding";

export class App {
  flights;
  locales = [
    { locale: "en-US", name: "US" },
    { locale: "en-GB", name: "UK" },
    { locale: "ko-KR", name: "Korean" },
    { locale: "ar-EG", name: "Arabic" },
    { locale: "ja-JP-u-ca-japanese", name: "Japan" },
    { locale: "de-DE", name: "Germany" },
    { locale: "pt-BR", name: "Brazil" },
    { locale: "ru-RU", name: "Russia" },
    { locale: "es-ES", name: "Spain" },
    { locale: "it-IT", name: "Italy" },
    { locale: "zh-CN", name: "China" },
    { locale: "zh-HK", name: "Hong Kong" },
    { locale: "zh-TW", name: "Taiwan" }
  ];

  changeLocale(locale) {
    window.currentLocale = locale;
    signalBindings("locale-changed");
  }

  constructor() {
    this.flights = [
      {
        from: "Los Angeles",
        to: "San Fran",
        depart: new Date("2017-10-09"),
        arrive: new Date("2017-10-10")
      },
      {
        from: "Melbourne",
        to: "Sydney",
        depart: new Date("2017-10-11"),
        arrive: new Date("2017-10-12")
      },
      {
        from: "Hawaii",
        to: "Crescent",
        depart: new Date("2017-10-13"),
        arrive: new Date("2017-10-14")
      }
    ];
  }
}
clock.html
<template
  bindable="time"
  style="display: inline-block;
  width: 200px;
  padding: 4px 6px;
  border-radius: 10px;
  border: 2px solid #1e1e1e;
  font-size: 16px;
  text-align: center;"
>
  <require from="./flight-time-value-converter"></require> ${time | flightTime}
</template>
app.html
<template>
  <require from="./clock.html"></require>

  <div>
    Change locale to
    <div>
      <button
        repeat.for="locale of locales"
        click.delegate="changeLocale(locale.locale)"
        style="margin-right: 5px"
      >
        ${locale.name}
      </button>
    </div>
  </div>

  <div>
    <h2>Flights</h2>
    <table
      repeat.for="flight of flights"
      style="margin-bottom: 15px; color: #5B5B5B"
    >
      <tr>
        <th>From ${flight.from}</th>
        <th style="width: 10px"></th>
        <th>To ${flight.to}</th>
      </tr>
      <tr>
        <td><clock time.bind="flight.depart"></clock></td>
        <td style="width: 10px"></td>
        <td><clock time.bind="flight.arrive"></clock></td>
      </tr>
    </table>
  </div>
</template>

A working demo of the above code can be found .

A working demo of the above code can be found .

A working demo of the above code can be found .

A working demo of the above code can be found .

A working demo of the above code can be found .

A working demo of the above code can be found .

A working demo of the above code can be found .

Moment
Numeral
here
here
here
here
here
here
here