Skip to content

Aurelia Value Converters — Transforming Data for Display

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Aurelia Value Converters. We cover key concepts, practical examples, and best practices to help you master this topic.

Aurelia value converters transform data between the ViewModel and the View. They format dates, currencies, strings, and complex objects for display. Converters use a toView method for display transformation and a fromView method for input transformation.

What You'll Learn

You will learn how to create value converters, use parameters, chain converters together, and implement two-way conversion for form inputs.

Why It Matters

Converters keep templates clean by moving formatting logic out of the ViewModel. A date formatting converter used in 50 templates can be updated in one place. Converters are pure functions that are easy to test.

Real-World Use

A financial dashboard uses converters for currency formatting, percentage display, file size formatting, date localization, and status labeling. Each converter is a pure function with unit tests.

flowchart LR
    A[ViewModel] --> B[toView]
    B --> C[View]
    C --> D[fromView]
    D --> A
    B --> E[Parameters]
    B --> F[Chained converters]

Basic Converter

// src/resources/value-converters/uppercase.ts
export class UppercaseValueConverter {
  toView(value) {
    if (!value) return '';
    return value.toUpperCase();
  }
}
<template>
  <p>${message | uppercase}</p>
</template>

Date Format Converter

// src/resources/value-converters/date-format.ts
export class DateFormatValueConverter {
  toView(value, format = 'medium') {
    if (!value) return '';

    const date = new Date(value);
    const options = {
      short: { month: 'numeric', day: 'numeric', year: '2-digit' },
      medium: { month: 'short', day: 'numeric', year: 'numeric' },
      long: { month: 'long', day: 'numeric', year: 'numeric' },
      full: { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' },
      time: { hour: '2-digit', minute: '2-digit' },
      datetime: {
        month: 'short', day: 'numeric', year: 'numeric',
        hour: '2-digit', minute: '2-digit'
      }
    };

    return date.toLocaleDateString('en-US', options[format] || options.medium);
  }
}
<template>
  <p>Published: ${post.date | dateFormat}</p>
  <p>Published: ${post.date | dateFormat:'long'}</p>
  <p>Due: ${task.dueDate | dateFormat:'datetime'}</p>
</template>

Currency Converter

// src/resources/value-converters/currency.ts
export class CurrencyValueConverter {
  toView(value, symbol = '$', decimals = 2) {
    if (value == null || isNaN(value)) return `${symbol}0.00`;

    const formatted = Number(value).toFixed(decimals);
    return `${symbol}${Number(formatted).toLocaleString('en-US', {
      minimumFractionDigits: decimals,
      maximumFractionDigits: decimals
    })}`;
  }

  fromView(value, symbol = '$') {
    if (!value) return 0;
    return parseFloat(value.replace(symbol, '').replace(/,/g, ''));
  }
}
<template>
  <p>Price: ${product.price | currency}</p>
  <p>Price: ${product.price | currency:'€':2}</p>

  <!-- Two-way binding with fromView -->
  <input value.bind="amount | currency" />
</template>

Chaining Converters

<template>
  <!-- Apply multiple converters in sequence -->
  <p>${message | lowercase | capitalize}</p>
  <p>${post.body | truncate:100 | uppercase}</p>
  <p>${data.rawDate | dateFormat:'short' | uppercase}</p>
</template>
// lowercase.ts
export class LowercaseValueConverter {
  toView(value) { return value ? value.toLowerCase() : ''; }
}

// capitalize.ts
export class CapitalizeValueConverter {
  toView(value) {
    if (!value) return '';
    return value.charAt(0).toUpperCase() + value.slice(1);
  }
}

Truncate Converter

// src/resources/value-converters/truncate.ts
export class TruncateValueConverter {
  toView(value, maxLength = 100, suffix = '...') {
    if (!value) return '';
    if (value.length <= maxLength) return value;
    return value.substring(0, maxLength).replace(/\s+\S*$/, '') + suffix;
  }

  // Reverse truncation? Just return the full value
  fromView(value) {
    return value;
  }
}

File Size Converter

// src/resources/value-converters/file-size.ts
export class FileSizeValueConverter {
  toView(bytes, decimals = 2) {
    if (bytes === 0) return '0 Bytes';
    if (!bytes) return '';

    const k = 1024;
    const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));

    return parseFloat((bytes / Math.pow(k, i)).toFixed(decimals)) + ' ' + sizes[i];
  }
}
<template>
  <p>File size: ${file.size | fileSize}</p>
  <p>Exact: ${file.size | fileSize:3}</p>
</template>

Status Label Converter

// src/resources/value-converters/status-label.ts
export class StatusLabelValueConverter {
  toView(value) {
    const labels = {
      active: 'Active',
      inactive: 'Inactive',
      pending: 'Pending Approval',
      suspended: 'Suspended',
      deleted: 'Deleted'
    };
    return labels[value] || value;
  }
}
// src/resources/value-converters/status-class.ts
export class StatusClassValueConverter {
  toView(value) {
    const classes = {
      active: 'text-green bg-green-light',
      pending: 'text-yellow bg-yellow-light',
      suspended: 'text-red bg-red-light',
      deleted: 'text-gray bg-gray-light'
    };
    return classes[value] || 'text-gray';
  }
}
<template>
  <span class="badge ${user.status | statusClass}">
    ${user.status | statusLabel}
  </span>
</template>

Array Converters

// src/resources/value-converters/join.ts
export class JoinValueConverter {
  toView(array, separator = ', ') {
    if (!array) return '';
    return array.join(separator);
  }
}

// src/resources/value-converters/sort-by.ts
export class SortByValueConverter {
  toView(array, property, direction = 'asc') {
    if (!array) return [];
    return [...array].sort((a, b) => {
      let valA = a[property];
      let valB = b[property];
      if (valA < valB) return direction === 'asc' ? -1 : 1;
      if (valA > valB) return direction === 'asc' ? 1 : -1;
      return 0;
    });
  }
}
<template>
  <p>Tags: ${post.tags | join}</p>
  <p>Tags: ${post.tags | join:'; '}</p>

  <ul>
    <li repeat.for="user of users | sortBy:'name'">
      ${user.name}
    </li>
  </ul>
  <ul>
    <li repeat.for="user of users | sortBy:'createdAt':'desc'">
      ${user.name}
    </li>
  </ul>
</template>

Sanitize HTML Converter

// src/resources/value-converters/sanitize.ts
export class SanitizeValueConverter {
  toView(value) {
    if (!value) return '';
    // Strip dangerous tags
    return value
      .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
      .replace(/on\w+="[^"]*"/gi, '');
  }
}

Common Mistakes

  1. Not handling null/undefined input. Converters should return a safe default (empty string or zero) for null/undefined values.
  2. Mutating the input value. Converters should be pure functions. Create and return transformed copies, not mutated originals.
  3. Forgetting fromView for two-way bindings. If a converter is used with two-way binding, implement fromView to reverse the transformation.
  4. Over-chaining converters. Long chains indicate that a ViewModel property should have the computed value directly.
  5. Not registering converters globally. Converters used across multiple views should be globally registered to avoid repeated require statements.

Practice Questions

  1. What is the purpose of toView in a value converter?
  2. When would you implement fromView?
  3. How do you pass parameters to a converter?
  4. How do you chain multiple converters?
  5. Challenge: Create a converter that formats a number as a percentage with configurable decimal places. Create a converter that pluralizes words (e.g., 1 item, 2 items). Create a converter that highlights search terms in text. Chain all three in a template.

FAQ

Can converters be async?

No, converters are synchronous. For async transformations, compute the value in the ViewModel.

Are converters cached?

No. They run on every binding update. For expensive operations, use memoization inside the converter.

{{< faq "Can I use converters in repeat bindings?" "Yes. `repeat.for=\"item of items | sortBy:'name'\"` works." >}}
What happens if a converter throws?

The binding breaks and the error is logged. Handle errors gracefully with try-catch in the converter.

Can converters access services?

No, converters are pure functions. Perform service lookups in the ViewModel.

Mini Project

Create a converter library with: (1) timeAgo — relative time display (2 minutes ago, 3 hours ago), (2) highlight — highlights search terms in text with <mark> tags, (3) ellipsis — truncates text at word boundary, (4) initials — extracts initials from a name, (5) phone — formats phone numbers. Demonstrate each converter and chain at least two.

What's Next

Now that you understand value converters, learn Aurelia Binding Commands for advanced binding patterns. Then explore Aurelia Conditional Rendering for dynamic templates.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro