LogoLogo
HomeDiscourseBlogDiscord
  • Introduction
  • Introduction
    • Quick start
    • Aurelia for new developers
    • Hello world
      • Creating your first app
      • Your first component - part 1: the view model
      • Your first component - part 2: the view
      • Running our app
      • Next steps
  • Templates
    • Template Syntax
      • Attribute binding
      • Event binding
      • Text interpolation
      • Template promises
      • Template references
      • Template variables
      • Globals
    • Custom attributes
    • Value converters (pipes)
    • Binding behaviors
    • Form Inputs
    • CSS classes and styling
    • Conditional Rendering
    • List Rendering
    • Lambda Expressions
    • Local templates (inline templates)
    • SVG
  • Components
    • Component basics
    • Component lifecycles
    • Bindable properties
    • Styling components
    • Slotted content
    • Scope and context
    • CustomElement API
    • Template compilation
      • processContent
      • Extending templating syntax
      • Modifying template parsing with AttributePattern
      • Extending binding language
      • Using the template compiler
      • Attribute mapping
  • Getting to know Aurelia
    • Routing
      • @aurelia/router
        • Getting Started
        • Creating Routes
        • Routing Lifecycle
        • Viewports
        • Navigating
        • Route hooks
        • Router animation
        • Route Events
        • Router Tutorial
        • Router Recipes
      • @aurelia/router-lite
        • Getting started
        • Router configuration
        • Configuring routes
        • Viewports
        • Navigating
        • Lifecycle hooks
        • Router hooks
        • Router events
        • Navigation model
        • Transition plan
    • App configuration and startup
    • Enhance
    • Template controllers
    • Understanding synchronous binding
    • Dynamic composition
    • Portalling elements
    • Observation
      • Observing property changes with @observable
      • Effect observation
      • HTML observation
      • Using observerLocator
    • Watching data
    • Dependency injection (DI)
    • App Tasks
    • Task Queue
    • Event Aggregator
  • Developer Guides
    • Animation
    • Testing
      • Overview
      • Testing attributes
      • Testing components
      • Testing value converters
      • Working with the fluent API
      • Stubs, mocks & spies
    • Logging
    • Building plugins
    • Web Components
    • UI virtualization
    • Errors
      • 0001 to 0023
      • 0088 to 0723
      • 0901 to 0908
    • Bundlers
    • Recipes
      • Apollo GraphQL integration
      • Auth0 integration
      • Containerizing Aurelia apps with Docker
      • Cordova/Phonegap integration
      • CSS-in-JS with Emotion
      • DOM style injection
      • Firebase integration
      • Markdown integration
      • Multi root
      • Progress Web Apps (PWA's)
      • Securing an app
      • SignalR integration
      • Strongly-typed templates
      • TailwindCSS integration
      • WebSockets Integration
      • Web Workers Integration
    • Playground
      • Binding & Templating
      • Custom Attributes
        • Binding to Element Size
      • Integration
        • Microsoft FAST
        • Ionic
    • Migrating to Aurelia 2
      • For plugin authors
      • Side-by-side comparison
    • Cheat Sheet
  • Aurelia Packages
    • Validation
      • Validation Tutorial
      • Plugin Configuration
      • Defining & Customizing Rules
      • Architecture
      • Tagging Rules
      • Model Based Validation
      • Validation Controller
      • Validate Binding Behavior
      • Displaying Errors
      • I18n Internationalization
      • Migration Guide & Breaking Changes
    • i18n Internationalization
    • Fetch Client
      • Overview
      • Setup and Configuration
      • Response types
      • Working with forms
      • Intercepting responses & requests
      • Advanced
    • Event Aggregator
    • State
    • Store
      • Configuration and Setup
      • Middleware
    • Dialog
  • Tutorials
    • Building a ChatGPT inspired app
    • Building a realtime cryptocurrency price tracker
    • Building a todo application
    • Building a weather application
    • Building a widget-based dashboard
    • React inside Aurelia
    • Svelte inside Aurelia
    • Synthetic view
    • Vue inside Aurelia
  • Community Contribution
    • Joining the community
    • Code of conduct
    • Contributor guide
    • Building and testing aurelia
    • Writing documentation
    • Translating documentation
Powered by GitBook
On this page
  • Context
  • Examples
  • Using the Attribute Syntax Mapper
  • Combining the attribute syntax mapper with the node observer locator

Was this helpful?

Export as PDF
  1. Components
  2. Template compilation

Extending templating syntax

The Aurelia template compiler is powerful and developer-friendly, allowing you extend its syntax with great ease.

PreviousprocessContentNextModifying template parsing with AttributePattern

Last updated 1 year ago

Was this helpful?

Context

Sometimes you will see the following template in an Aurelia application:

<input value.bind="message">

Aurelia understands that value.bind="message" means value.two-way="message", and later creates a two way binding between view model message property, and input value property. How does Aurelia know this?

By default, Aurelia is taught how to interpret a bind binding command on a property of an element via a Attribute Syntax Mapper. Application can also tap into this class to teach Aurelia some extra knowledge so that it understands more than just value.bind on an <input/> element.

Examples

You may sometimes come across some custom input element in a component library, some examples are:

  • Microsoft FAST text-field element:

  • Ionic ion-input element:

  • Polymer paper-input element:

  • and many more...

Regardless of the lib choice an application takes, what is needed in common is the ability to have a concise syntax to describe the two way binding intention with those custom elements. Some examples for the above custom input elements:

<fast-text-field value.bind="message">
<ion-input value.bind="message">
<paper-input value.bind="message">

should be treated as:

<fast-text-field value.two-way="message">
<ion-input value.two-way="message">
<paper-input value.two-way="message">

In the next section, we will look into how to teach Aurelia such knowledge.

Using the Attribute Syntax Mapper

As mentioned earlier, the Attribute Syntax Mapper will be used to map value.bind into value.two-way. Every Aurelia application uses a single instance of this class. The instance can be retrieved via the injection of interface IAttrMapper, like the following example:

import { resolve } from 'aurelia';
import { inject, IAttrMapper } from '@aurelia/runtime-html';

export class MyCustomElement {
  constructor(attrMapper = resolve(IAttrMapper)) {
    // do something with the attr mapper
  }
}

After grabbing the IAttrMapper instance, we can use the method useTwoWay(fn) of it to extend its knowledge. Following is an example of teaching it that the bind command on value property of the custom input elements above should be mapped to two-way:

attrMapper.useTwoWay(function(element, property) {
  switch (element.tagName) {
    // <fast-text-field value.bind="message">
    case 'FAST-TEXT-FIELD': return property === 'value';
    // <ion-input value.bind="message">
    case 'ION-INPUT': return property === 'value';
    // <paper-input value.bind="message">
    case 'PAPER-INPUT': return property === 'value';
    // let other two way mapper check the validity
    default:
      return false;
  }
})

Combining the attribute syntax mapper with the node observer locator

Teaching Aurelia to map value.bind to value.two-way is the first half of the story. The second half is about how we can teach Aurelia to observe the value property for changes on those custom input elements. We can do this via the Node Observer Locator. Every Aurelia application uses a single instance of this class, and this instance can be retrieved via the injection of interface INodeObserverLocator like the following example:

import { resolve } from 'aurelia';
import { inject, INodeObserverLocator } from '@aurelia/runtime-html';

export class MyCustomElement {
  constructor(nodeObserverLocator = resolve(INodeObserverLocator)) {
    // do something with the locator
  }
}

After grabbing the INodeObserverLocator instance, we can use the method useConfig of it to extend its knowledge. Following is an example of teaching it that the value property, on a <fast-text-field> element could be observed using change event:

nodeObserverLocator.useConfig('FAST-TEXT-FIELD', 'value', { events: ['change' ] });

Similarly, examples for <ion-input> and <paper-input>:

nodeObserverLocator.useConfig('ION-INPUT', 'value', { events: ['change' ] });
nodeObserverLocator.useConfig('PAPER-INPUT', 'value', { events: ['change' ] });

If an object is passed to the .useConfig API of the Node Observer Locator, it will be used as a multi-registration call, as per following example, where we register <fast-text-field>, <ion-input>, <paper-input> all in a single call:

nodeObserverLocator.useConfig({
  'FAST-TEXT-FIELD': {
    value: { events: ['change'] }
  },
  'ION-INPUT': {
    value: { events: ['change'] }
  },
  'PAPER-INPUT': {
    value: { events: ['changer'] }
  }
})
import { inject, IContainer, IAttrMapper, INodeObserverLocator, AppTask, Aurelia } from 'aurelia';

Aurelia
  .register(
    AppTask.creating(IContainer, container => {
      const attrMapper = container.get(IAttrMapper);
      const nodeObserverLocator = container.get(INodeObserverLocator);
      attrMapper.useTwoWay((el, property) => {
        switch (el.tagName) {
          case 'FAST-TEXT-FIELD': return property === 'value';
          case 'FAST-TEXT-AREA': return property === 'value';
          case 'FAST-SLIDER': return property === 'value';
          // etc...
        }
      });
      nodeObserverLocator.useConfig({
        'FAST-TEXT-FIELD': {
          value: { events: ['change'] }
        },
        'FAST-TEXT-AREA': {
          value: { events: ['change'] }
        },
        'FAST-SLIDER': {
          value: { events: ['change'] }
        }
      });
    })
  )
  .app(class MyApp {})
  .start();

And with the above, your Aurelia application will get two way binding flow seamlessly:

<fast-text-field value.bind="message"></fast-text-field>
<fast-text-area value.bind="description"></fast-text-area>
<fast-slider value.bind="fontSize"></fast-slider>

Combining the examples in the two sections above into some more complete code block example, for :

https://explore.fast.design/components/fast-text-field
https://ionicframework.com/docs/api/input
https://www.webcomponents.org/element/@polymer/paper-input
Microsoft FAST components