Binding behaviors
Binding behaviors are a powerful category of view resources in Aurelia 2, alongside value converters, custom attributes, and custom elements. They are most analogous to value converters in that you declaratively use them within binding expressions to modify binding behavior.
However, the crucial distinction lies in the scope of access. Binding behaviors have complete access to the binding instance throughout its entire lifecycle. This contrasts sharply with value converters, which are limited to intercepting and transforming values as they flow between the model and the view.
This broader access empowers binding behaviors to fundamentally alter the behavior of bindings, unlocking a wide array of capabilities as demonstrated in the examples below.
Throttle
Aurelia provides several built-in binding behaviors to address common scenarios. The throttle
behavior is designed to limit the rate at which updates propagate. This can apply to updates from the view-model to the view (in to-view
or one-way
bindings) or from the view to the view-model (in two-way
bindings).
By default, throttle
enforces a minimum time interval of 200ms between updates. You can easily customize this interval.
Here are some practical examples:
Limiting property updates to a maximum of once every 200ms
In this example, the searchQuery
property in your view model will update at most every 200ms, even if the user types more rapidly in the input field. This is especially useful for search inputs or other scenarios where frequent updates can be inefficient or overwhelming.
You'll notice the &
symbol, which is used to introduce binding behavior expressions. The syntax for binding behaviors mirrors that of value converters:
Arguments: Binding behaviors can accept arguments, separated by colons:
propertyName & behaviorName:arg1:arg2
.Chaining: Multiple binding behaviors can be chained together:
propertyName & behavior1 & behavior2:arg1
.Combined with Value Converters: Binding expressions can include both value converters and binding behaviors:
${data | valueConverter:arg & bindingBehavior:arg2}
.
Let's see how to customize the throttling interval:
Limiting property updates to a maximum of once every 850ms
The throttle
behavior is particularly valuable when used with event bindings, especially for events that fire frequently, such as mousemove
.
Handling mousemove
events at most every 200ms
In this case, the mouseMoveHandler
method in your view model will be invoked at most every 200ms, regardless of how frequently the mousemove
event is triggered as the user moves their mouse.
Flushing Pending Throttled Updates
In certain situations, you might need to immediately apply any pending throttled updates. Consider a form with throttled input fields. When a user tabs out of a field after typing, you might want to ensure the latest value is immediately processed, even if the throttle interval hasn't elapsed yet.
The throttle
binding behavior supports this via a "signal". You can specify a signal name as the second argument to throttle
. Then, using Aurelia's ISignaler
, you can dispatch this signal to force a flush of the throttled update.
In this example:
value.bind="formValue & throttle:200:'flushInput'"
: TheformValue
binding is throttled to 200ms and associated with the signal'flushInput'
.blur.trigger="signaler.dispatchSignal('flushInput')"
: When the input loses focus (blur
event),signaler.dispatchSignal('flushInput')
is called. This immediately triggers any pending throttled update associated with the'flushInput'
signal, ensuring theformValue
is updated in the view model right away.
You can also specify a list of signals:
Debounce
The debounce
binding behavior is another rate-limiting tool. debounce
delays updates until a specified time interval has passed without any further changes. This is ideal for scenarios where you want to react only after a user has paused interacting.
A classic use case is a search input that triggers an autocomplete or search operation. Making an API call with every keystroke is inefficient. debounce
ensures the search logic is invoked only after the user has stopped typing for a moment.
Updating a property after typing has stopped for 200ms
Updating a property after typing has stopped for 850ms
Similar to throttle
, debounce
is highly effective with event bindings.
Calling mouseMoveHandler
after the mouse stops moving for 500ms
Flushing Pending Debounced Calls
Like throttle
, debounce
also supports flushing pending updates using signals. This is useful in scenarios like form submission where you want to ensure the most recent debounced values are processed immediately, even if the debounce interval hasn't elapsed.
In this example, the validateInput
method (which could perform input validation or other actions) will be called when the input field loses focus, even if the 300ms debounce interval isn't fully over, ensuring timely validation.
As with throttle
, you can also provide a list of signal names to debounce
.
UpdateTrigger
The updateTrigger
binding behavior allows you to customize which DOM events trigger updates from the view to the view model for input elements. By default, Aurelia uses the change
and input
events for most input types.
However, you can override this default behavior. For example, you might want to update the view model only when an input field loses focus (blur
event).
Updating the view model only on blur
You can specify multiple events that should trigger updates:
Updating the view model on blur
or paste
events
This is useful in scenarios where you need fine-grained control over when view-model updates occur based on specific user interactions with input elements.
Signal
The signal
binding behavior provides a mechanism to explicitly tell a binding to refresh itself. This is particularly useful when a binding's result depends on external factors or global state changes that Aurelia's observation system might not automatically detect.
Consider a "translate" value converter that translates keys into localized strings, e.g., ${'greeting.key' | translate}
. If your application allows users to change the language dynamically, how do you refresh all the translation bindings to reflect the new language?
Another example is a value converter that displays a "time ago" string relative to the current time, e.g., Posted ${post.date | timeAgo}
. As time progresses, this binding needs to refresh periodically to show updated relative times like "5 minutes ago," "an hour ago," etc.
signal
binding behavior solves these refresh scenarios:
Using a Signal to Refresh Bindings
In this example, signal:'time-update'
assigns the signal name 'time-update'
to this binding. Multiple bindings can share the same signal name.
To trigger a refresh of all bindings with the signal name 'time-update'
, you use the ISignaler
:
Dispatching a Signal to Refresh Bindings
Every 5 seconds, the setInterval
function updates lastUpdated
and then calls signaler.dispatchSignal('time-update')
. This tells Aurelia to re-evaluate all bindings that are configured with & signal:'time-update'
, causing them to refresh and display the updated "time ago" value.
oneTime
The oneTime
binding behavior optimizes string interpolation bindings for scenarios where the bound value is not expected to change after the initial render. Applying oneTime
indicates to Aurelia that the binding should only be evaluated once.
One-time String Interpolation Binding
oneTime
bindings are the most efficient type of binding because they eliminate the overhead of property observation. Aurelia doesn't need to track changes to staticText
after the initial binding, leading to performance improvements, especially in large lists or complex views.
Aurelia also provides binding behaviors for explicitly specifying toView
and twoWay
binding modes, although these are less commonly used as binding behaviors since the binding commands (.to-view
, .two-way
, .bind
) are more direct.
toView
and twoWay
binding behaviors
Note the casing difference between binding mode commands and behaviors. Binding commands (e.g., .to-view
, .two-way
) use lowercase, dash-separated names due to HTML case-insensitivity. However, binding behaviors used in expressions (e.g., toView
, twoWay
) use camelCase as dashes are not valid in JavaScript variable names.
Self
The self
binding behavior is used in event bindings to ensure that the event handler only responds to events dispatched directly from the element the listener is attached to, and not from any of its descendant elements due to event bubbling.
Consider a scenario with a panel component:
Scenario without self
binding behavior
Without self
, the onMouseDown
handler will be invoked not only when the user mousedown on the <header>
element itself, but also on any element inside the header, such as the "Settings" and "Close" buttons, due to event bubbling. This might not be the desired behavior if you want the panel to react only to direct interactions with the header, not its contents.
You could handle this in your event handler by checking the event.target
:
Event Handler without self
binding behavior (manual check)
However, this mixes DOM event handling logic with component-specific behavior. The self
binding behavior offers a cleaner, more declarative solution:
Using self
binding behavior
Event Handler with self
binding behavior
By adding & self
to the event binding, Aurelia ensures that onMouseDown
is only called when the mousedown
event originates directly from the <header>
element, simplifying your event handler logic and separating concerns.
Custom Binding Behaviors
You can create your own custom binding behaviors to encapsulate reusable binding modifications. Like value converters, custom binding behaviors are view resources.
Instead of toView
and fromView
methods (like value converters), custom binding behaviors implement bind(binding, scope, [...args])
and unbind(binding, scope)
methods:
bind(binding, scope, [...args])
: This method is called when the binding is created and attached to the DOM. It's where you implement the behavior modification to thebinding
instance.binding
: The binding instance whose behavior you want to alter. It's an object implementing theIBinding
interface.scope
: The binding's scope, providing access to the view model (scope.bindingContext
) and override context (scope.overrideContext
).[...args]
: Any arguments passed to the binding behavior in the template (e.g.,& myBehavior:arg1:arg2
).
unbind(binding, scope)
: This method is called when the binding is detached from the DOM (e.g., when the view is unrendered). Here, you should clean up any changes made in thebind
method to restore the binding to its original state and prevent memory leaks.
Let's look at some practical examples of custom binding behaviors.
Log Binding Context Behavior
This behavior logs the current binding context to the browser's console every time the binding updates its target (view). This is invaluable for debugging and understanding data flow in your Aurelia application.
Usage in Template:
Now, whenever the userName
binding updates the input element, you'll see the current binding context logged to the console, helping you inspect the data available at that point.
Inspect Value Binding Behavior (Tooltip)
This behavior adds a temporary tooltip to the element displaying the binding's current value whenever it updates. This offers a quick way to inspect binding values directly in the UI without resorting to console logs.
Usage in Template:
As the itemName
binding updates, the input element will temporarily display a tooltip showing the current value, providing immediate visual feedback for debugging.
Highlight Updates Binding Behavior
This behavior visually highlights an element by briefly changing its background color whenever the binding updates the element's target property. This visual cue helps quickly identify which parts of the UI are reacting to data changes, particularly useful during development and debugging complex views.
Usage in Template:
Whenever the message
binding updates the textContent
of the div
, the div's background will briefly flash light blue for 1 second (1000ms), visually indicating the update. You can customize the highlight color and duration by passing arguments to the binding behavior in the template.
Last updated
Was this helpful?