Fundamental Concepts
Data Binding in NativeScript
Data Binding refers to a connection (binding) and data flow between ViewModel (Model) and User Interface (UI).
It gets activated through three steps:
- Create a ViewModel(let's call it DataModel) class extending the
Observable
class - Make DataModel available to the UI by setting
page.bindingContext
=new DataModel()
- Using the mustach syntax , bind the UI components properties to the members of the
DataModel
instance.
When you look at a new project, you see an example of those steps applied.
There are various ways to manage data flow, often referred to as data bindings:
- One-way (to UI) data binding - This is the most common form of data binding. An example would be binding a string property in the Model to a Label component.
<Labeltext="{{ name }}" />
exportclassHelloWorldModelextendsObservable {
name='John Doe'
}
- One-way (to Model) data binding - Binding which updates the model in relation to some action in the UI. The best example for this is a tap event. Static and data bound arguments can be passed using function currying. Here's a working example.
<Buttontext="Submit"tap="{{ onTap }}"/>
<Buttontext="Button 1"tap="{{ onTap2('Button 1') }}" />
<Buttontext="Button 2"tap="{{ onTap2('Button 2') }}" />
<Buttontext="Button 3"tap="{{ onTap3('Button 3', counter) }}" />
exportclassHelloWorldModelextendsObservable {
publiccounter:number=42
onTap(args:EventData) {
//
}
onTap2(source:string) {
returnfunctionfnOnTap2(args:EventData) {
console.log('onTap2.source =', source)
}
}
onTap3(source:string, num:number) {
return (args:EventData) => {
console.log('onTap3.source =', source)
console.log('onTap3.num =', num)
this.counter--
}
}
}
- Two-way data binding - The data flows from the Model to the UI and vice versa. A typical example is a
TextField
that reads its value from a Model, and also changes the Model based on user input.
<TextFieldtext="{{ name }}"/>
exportclassHelloWorldModelextendsObservable {
name='John Doe'
}
Accessing parent bindingContext β
A parent and a child UI components can have different binding contexts and the child component might need to bind its property to a source property in its parent's bindingContext.
Generally, the binding context is inheritable, but not when the components are created dynamically based on some data source. For example, ListView creates its child items based on an itemΠ’emplate
, which describes what the ListView component will look like. When this component is added to the visual tree, for binding context it gets an element from a ListView
items array (with the corresponding index).
This process creates a new binding context chain for the child item and its inner UI components. So, the inner UI component cannot access the binding context of the ListView
. In order to solve this problem, NativeScript binding infrastructure has two special keywords:
$parent
: denotes the binding context of the direct parent visual component$parents
: can be used as an array (with a number or string index). This gives you the option to choose either N levels of UI nesting or get a parent UI component with a given type.
Note
If the value of the items
property of the ListView
is an array of plain elements(numbers,string, dates) as in the preceeding example, you use the $value
variable to access the current item of the array.
If it is an array of objects,you use the current object property name as the variable name.
Using binding expressions β
NativeScript supports the following polymorphic binding expressions:
Property access β
To access a value stored in an object property of the bindingContext, use the propert access expression:
user = {
names: {
first: 'Sara',
},
}
<Labeltext="{{ user.name.first }}"textWrap="true" />
array access β
<Labeltext="{{ fruits[0] }}"textWrap="true" />
logical operators β
You can use the not(!
) operator to reverse the logical state of a binding context property.
<Labeltext="{{ !isUserLoggedIn }}"textWrap="true" />
Supported operators: &&
, ||
and !
.
unary operators β
<Labeltext="{{ +age }}"textWrap="true" />
age ='33'
Converts a property value to a number
. To convert a property to a number
and negate it, use the -
operator.
comparison operators β
<Labeltext="{{ prop1 > prop2 }}"textWrap="true" />
Supported operators: >
,<
, <=
, >=
, ==
, !=
, ===
, !==
.
ternary operator β
<Labeltext="{{ prop1 ? prop2 : prop3 }}"textWrap="true" />
grouping parenthesis β
<Labeltext="{{ prop1*(prop2 + prop3) }}"textWrap="true" />
function calls β
<Labeltext="{{ someMethod(p1,p2,...,pN) }}"textWrap="true" />
comparison operators β
<Labeltext="{{ property1 > property2 }}"textWrap="true" />
Other supported operators are: <
, <=
, >=
, ==
, !=
, ===
, !==
.
Note
Special characters need to be escaped as follows:
- double quotes:
"
β"
- single quote:
'
β'
- less than:
<
β<
- greater than:
>
β>
- ampersand:
&
β&
Using data converters β
Often data within the Model is stored in a way that is optimized for best performance regarding tasks such as search, replace etc. Unfortunately, the way computers store data differs significantly from human-readable formats. One fitting example is the Date object. In JavaScript, Date actually is a very big number that represents milliseconds from 01.01.1970
which does not speak much to any human. This is where data converters come into play. Data converters are essentially functions that format the data from the Model into a human-readable format for display in the UI.
<StackLayoutclass="p-20 bg">
<Labeltext="{{ date | dateConverter('dd-mm-yyyy') }}"textWrap="true" />
<Labeltext="{{ name | toUpperCaseConverter }}"textWrap="true" />
<Labeltext="{{ title | toTitle }}"textWrap="true" />
</StackLayout>
exportclassHelloWorldModelextendsObservable {
name='nandee'
title='hello world!'
date= Date.now() // number
dateConverter=this.formatDate()
toUpperCaseConverter=this.toUpperCase()
toTitle=this.convertToTitle()
formatDate() {
return {
toView(value:number, format:string) {
constdate=newDate(value)
constday= date.getDate().toString().padStart(2, '0')
constmonth= (date.getMonth() +1).toString().padStart(2, '0') // months are zero based in JS.
constyear= date.getFullYear().toString()
return format
.replace('dd', day)
.replace('mm', month)
.replace('yyyy', year)
},
}
}
toUpperCase() {
return {
toView(value:string) {
return value.toUpperCase()
},
}
}
convertToTitle() {
return {
toView(str:string) {
return str.replace(/(^|\s)\S/g, function (t) {
return t.toUpperCase()
})
},
}
}
}
Stop binding β
Generally there is no need to stop binding explicitly since the Observable object uses weak references, which prevents any memory leaks. However, if you need to data unbind a view, call the unbind() method with the target property name as the argument.
targetTextField.unbind('text')
- Previous
- XmlParser
- Next
- Error Handling
Contributors
Last updated: