## Bindings
A binding is a JavaScript expression in double curly braces, written as an attribute value.
It keeps the property up to date: whenever a property it reads changes, it runs again.
```html
<pacem-text text="{{ #cart.model.length + ' items' }}"></pacem-text>
<pacem-input-text value="{{ #user.model.name, twoway }}"></pacem-input-text>
```
### Properties and attributes
Bindable properties are the ones decorated with `@Watch`. Each one is an observed attribute too, kebab-cased: `textProperty` ↔ `text-property`.
Its converter turns the attribute's string into the property's type (`max-rows="8"` is the number `8`); a binding skips the string and assigns the value itself.
> Only watched properties can be bound. A plain attribute (`class`, `href`…) holding `{{ }}` stays literal text: bind the element's own property instead (`css-class`, or a proxy).
### What an expression can reach
id, in the same document (or shadow root)this works too)^^item is the outer repeater's@ViewChild, say)Pacem.Utils, say)
> Expressions refuse `new`, the members of `window` and `document`, and `eval(…)`: the binding fails, with an error in the console.
> Put that logic in a transformer.
### One-way, two-way, once
Bindings are **one-way** by default: source to property. Two modifiers change that:
```html
<!-- twoway: the field writes back to the model -->
<pacem-input-text value="{{ #user.model.name, twoway }}"></pacem-input-text>
<!-- once: evaluated a single time, then left alone -->
<pacem-data model="{{ Date.now(), once }}"></pacem-data>
```
> `twoway` needs a plain path to a single source (`#user.model.name`): there's nowhere to write `#a.x + #b.y` back to.
### When it re-runs
A binding listens to the watched property right after each reference: `model` in `#user.model.name`.
Assigning it (`#user.model = { … }`) re-runs the binding. So do a two-way write on a nested path and `push`/`splice` on an array assigned to a watched property.
Setting a nested field from code (`#user.model.name = 'x'`) doesn't.
> There's no guaranteed order among property changes, not even on the same element.
> Don't rely on two properties being in sync while they're changing.
## Examples
### Two-way, persisted Type in the field: the model follows, and so does the preview. `persist-as` keeps it in local storage: reload the page, the text is still there.
### Nested repeaters Scientists and their books: the inner repeater reaches the outer item with `^^item`, to tell each author's age at publication.
## Event handlers
`on-{event}` attributes are statements, run when the element fires that event: no curly braces.
`$event` is the event, `$this` the element; separate statements with `;`.
```html
<pacem-button on-click="#counter.model++; #log.model = 'clicked'">+1</pacem-button>
<pacem-fetch on-error="#status.model = 'failed'; console.warn($event.detail)"></pacem-fetch>
```
> Every Pacem event works this way, custom ones included: `on-itemcommand`, `on-swipeleft`, `on-propertychange`…
## Transformers
`$pacem` gathers functions for bindings: built-in ones format and help (`$pacem.date(at, 'time')`, `$pacem.cssvar('--pacem-breakpoint-md')`…), and you can add your own.
A static method decorated with `@Transformer` becomes `$pacem.{name}`:
```ts
import { Transformer } from '@pacem/pacem-core';
class Transformers {
/** Adds two numbers. */
@Transformer('sum')
static sum(a: number, b: number): number {
return a + b;
}
}
```
```html
<!-- yields '42' -->
<pacem-text text="{{ $pacem.sum(12, 30) }}"></pacem-text>
```
> Transformers are where logic beyond a one-liner goes: an expression stays readable, and the function is plain, testable TypeScript.
A bigger one:
To provide the best experiences, we use technologies like cookies to store and/or access device information. Consenting to these technologies will allow us to process data such as browsing behavior or unique IDs on this site. Not consenting or withdrawing consent, may adversely affect certain features and functions.