@bind

:point_right: Make sure to first read the introduction to data binding.

This decorator creates two-way or one-way bindings within a custom component. Changes to the decorated component property value are reflected on the target property of a target element (child) and the other way around.

@bind can be applied only to properties of a class decorated with @component. It behaves like @property in most regards and also supports its typeGuard and type options.

@bind can be combined with other property decorators, including other @bind decorators to create multiple bindings, as can be seen in this example. Note however that the same property can not receive data from multiple sources. This means that there can only be one @bind that establishes a two-way binding or to-component binding (using << prefix). Any additional bindings must be one-way bindings (with the >> prefix).

@bind(path)

Where path is a string in the format '<direction?><SelectorString>.<targetProperty>'.

:point_right: This a shorthand for @bind({path: string}). It can be used for bindings if no other options need to be set.

:point_right: See example apps “bind-two-way” (TypeScript) and “bind-two-way-jsx” (JavaScript/JSX).

Binds the decorated component property to the property <targetProperty> of the target element (a direct or indirect child element of the component) matching the selector String. Only id (starting with #) or type selectors (starting with an upper case letter) can be used, but not class selectors (starting with .). In addition the pseudo-selector :host can be used to select the component itself. The optional direction may be either >> for a one-way binding that copies the component property to the target element, or << for the reverse. Of omitted, a two-way binding is created. The direction may be separated from the selector with a space, e.g. >> #id.prop.

The example below establishes a two-way binding from the myNumber property to the property selection of the child with the id 'source'. The binding is established after append is called the first time on the component. At that time there needs to be exactly one descendant widget with the given id, and it has to have a property of the same type.

TypeScript example:

@component
class MyComponent extends Composite {

  @bind('#source.selection')
  myNumber: number = 50;


  constructor(properties: Properties<MyComponent>) {
    super();
    this.set(properties);
    this.append(
      <Slider id='source'/>
    );
  }

}

In JavaScript the only difference is how - if at all - the decorated property is typed. In JavaScript JSDoc comments may be used, but this is optional. This is true for all examples below, so only the TypeScript variant will be given.

/** @type {number} */
@bind('#source.selection')
myNumber;

@bind(path, converter)

Where path is a string in the format '<direction?><SelectorString>.<targetProperty>' and converter is a function of the BindingConverter type.

:point_right: See example app “bind-two-way-convert”.

:point_right: This a shorthand for @bind({path: string, convert: {binding: converter}}). It can be used for bindings if no other options needs to be set.

The binding converter function is used to convert values when the property is synchronized with the property given by path in either direction. This is distinct from the property converter function.

See also option config.convert and class “Conversion”.

@bind(config)

Like @bind(path) or @bindAll(bindings), but allows to give additional options as supported by @property.

The config object has the following options:

config.path

Where path is a string in the format '<direction?><SelectorString>.<targetProperty>'.

Example:

@bind({path: '#source.selection'})
myNumber: number = 50;

For details see @bind(path) above.

config.all

Where all is a plain object in the format of:

{
  <sourceProperty>: '#<targetElementId>.<targetProperty>'
}

Example:

@bind({all:{
  myText: '#input1.text',
  myNumber: '#input2.selection'
}})
model: Model;

For details see @bindAll.

config.type

A type may be given to enforce type checks in JavaScript.

config.typeGuard

A type guard may be given to perform value checks.

config.default

The default value of the property.

config.nullable

Whether or not the value is nullable. Is true by default.

config.equals

How the property determines whether a new value equals the current one.

config.convert

Given a function (or 'auto') this can convert values received by this property to the expected type.

May also be an object with one or two entries:

{
  property?: Converter<T>,
  binding?: BindingConverter<any>
}

A function given to convert or convert.property is the same convert function that @property accepts. These three are equivalent:

@bind({path})
@property({convert: myConverter}})
myPropertyA;

@bind({path, convert: myConverter})
myPropertyB;

@bind({path, {convert: {property: myConverter}}})
myPropertyC;

The BindingConverter given to convert.binding is a one or two-way converter function that is used to convert values when the property is synchronized with the property given by path in either direction. See @bind(path, converter) for details.

Difference between property converter and binding converter

Given the following decorated property:

@bind({
  path: '#foo.text',
  convert: {
    property: propConverter,
    binding: bindingConverter
  }
})
public bar;

Setting it a value a:

component.bar = a;
const b = component.bar;

Will convert a twice, once before it’s applied to bar (also returned to b) and once before it’s applied to #foo.text:

     |--component-----------------------------------------------------|
     |                                                                |
     |                                                |-Child-----|   |
a -> | propConverter -> "bar" <--bindingConverter --> | #foo.text |   |
b <- |  <----(getter)----/                            |-----------|   |
     |                                                                |
     |----------------------------------------------------------------|

Properties eligible for bindings

Any component property can be used for two-way bindings, but the target (child widget) property needs to generate change events for the two-way binding to work. This is already the case for all built-in properties of Tabris.js widgets.

See example apps “bind-two-way-change-events” (TypeScript) and “bind-two-way-change-events-jsx” (JavaScript/JSX).

If the target widget itself is a custom component the recommended way to implement change events is using @property. Note that there is no need to explicitly create an event API, @bind can ‘talk’ directly to @property. However, an explicit implementation is also possible.

Edge Cases

In a two-way binding setting the component property to undefined resets the target property to its initial value from when the binding was first established. The component property will also adopt that value, so both stay in sync.

If the component property converts or ignores the incoming value of the target property, the target property will follow and also bet set to the new component property value.

If a target property converts or ignores the incoming value of the component property, the component property will ignore that and keep its own value. The two properties are out-of-sync in this case.

If either property throws when set, the error will be propagated to the caller that originally caused the value change. In this case the two properties may end up out-of-sync.