nav-title | title | description | position |
---|---|---|---|
Events in NativeScript |
App: Events |
How to handle events in NativeScript. |
9 |
This article contents:
- Overview
- How Do Events Work
- Adding an Event Listener
- Removing an Event Listener
- PropertyChange Event
- Creating a Custom Event
- Avoiding Memory Leaks
- Working with Weak Events
An event is a message sent from an event emitter to signify the occurrence of a specific action. This action can be generated by a user action (such as a tap) or by program logic (for instance to indicate that downloading an image from a server has completed). The object that raises the event is called event sender or simply sender. The object that consumes the event is called event listener or simply listener.
Being a TypeScript (JavaScript) framework, NativeScript cannot benefit from a built in the language event handling mechanism. But because of the events' critical importance for modern mobile application development, NativeScript provides a class Observable
that powers the process of working with events. Find more information about it in the API Reference.
In general, events are very similar to a radio station: the sender plays music (fires an event) and the listener dances (executes an action).
To repeat, NativeScript streamlines handling events by providing a class called Observable
. Because it is one of the base classes within the NativeScript framework, almost every NativeScript object (component) has an option for dealing with events.
Adding an event listener means setting a function (method) that executes when the event is raised.
The examples below show how to set a function that prints a "Hello World!" message in the console to be executed when a button is tapped. You can choose between the shorthand syntax and the full syntax or you can declare the event handler in XML.
Use the shorthand syntax to write an in-line function to be executed on an event (tapping a testButton
in this example):
var buttonModule = require("ui/button");
var testButton = new buttonModule.Button();
testButton.text = "Test";
testButton.on(buttonModule.knownEvents.tap, function (eventData) {
console.log("Hello World!");
});
import buttonModule = require("ui/button");
var testButton = new buttonModule.Button();
testButton.text = "Test";
testButton.on(buttonModule.knownEvents.tap, function (eventData) {
console.log("Hello World!");
});
Even though the shorthand syntax is very handy when assigning simple functions, it is missing some important features available in the full syntax. For example, the following code adds a third optional parameter that represents the this
argument. You use this
as event handler context (closure). In other words, if you need the this
argument in your event handler function you have to use the full syntax, otherwise the sender object is used as this
.
var buttonModule = require("ui/button");
var testButton = new buttonModule.Button();
testButton.text = "Test";
var onTap = function (eventData) {
console.log("Hello World!");
};
testButton.addEventListener(buttonModule.knownEvents.tap, onTap, this);
import buttonModule = require("ui/button");
var testButton = new buttonModule.Button();
testButton.text = "Test";
var onTap = function (eventData) {
console.log("Hello World!");
};
testButton.addEventListener(buttonModule.knownEvents.tap, onTap, this);
Another option to set an event handler is to use an XML declaration like this:
<Page>
<StackLayout>
<Button tap="onTap" />
</StackLayout>
</Page>
Naturally, you need a code behind file to write the function body (the code behind file has the same file name but a different extension: .js or .ts depending on the language you are using). It could look like this:
function onTap(eventData) {
console.log("Hello World!");
}
exports.listViewItemTap = listViewItemTap;
export function onTap(eventData) {
console.log("Hello World!");
}
Under most circumstances you don't need to remove the event listener. If you do, however, for example if you want to receive the event just once or want to remove the event listener to free up resources, you can resort to the methods described in the sections below.
There is no syntax to remove an event listener through an XML declaration.
The following example uses the shorthand syntax to remove all listeners for the tap event of the testButton instance. If there are more than one event listener objects, you can set a second parameter with the name of the callback function. This way only the referenced event listener is removed.
testButton.off(buttonModule.knownEvents.tap);
testButton.off(buttonModule.knownEvents.tap);
The full syntax builds on top of the shorthand syntax by adding the this
argument similarly to the full syntax for adding an event listener. It is useful when multiple event listeners with different this
arguments are available.
testButton.removeEventListener(buttonModule.knownEvents.tap, onTap);
testButton.removeEventListener(buttonModule.knownEvents.tap, onTap);
The Observable
class provides a built-in event called propertyChange
which is called when a property is changed. This is how to subscribe to this event:
var observableModule = require("data/observable");
var observableObject = new observableModule.Observable();
observableObject.on(observableModule.knownEvents.propertyChange, function(propertyChangeData){
console.log(propertyChangeData.propertyName + " has been changed and the new value is: " + propertyChangeData.value);
});
import observableModule = require("data/observable");
var observableObject = new observableModule.Observable();
observableObject.on(observableModule.knownEvents.propertyChange, function(propertyChangeData){
console.log(propertyChangeData.propertyName + " has been changed and the new value is: " + propertyChangeData.value);
});
It is important to note that this event is critical for the entire data binding system. To take advantage of the data binding mechanism, all you have to do is make your business object inherit the Observable
class. The following example demonstrates how to do that:
var observableModule = require("data/observable");
var MyClass = (function (_super) {
__extends(MyClass, _super);
function MyClass() {
_super.apply(this, arguments);
}
Object.defineProperty(MyClass.prototype, "myProperty", {
get: function () {
return this.get("myProperty");
},
set: function (value) {
this.set("myProperty", value);
},
enumerable: true,
configurable: true
});
return MyClass;
})(observableModule.Observable);
exports.MyClass = MyClass;
import observableModule = require("data/observable");
export class MyClass extends observableModule.Observable {
public get myProperty(): number {
return this.get("myProperty");
}
public set myProperty(value: number) {
this.set("myProperty", value);
}
}
The pervious code snippet fires the propertyChange
event when the property value is changed.
If your business logic demands it, you may want to fire (raise or emit) a custom event on a particular action. To do that, call the Observable.notify()
method when the action is completed. This method takes any implementer of the EventData interface as event data. It includes basic information about an event—its name as eventName
and an instance of the event sender as object
).
var eventData = {
eventName: "myCustomEventName",
object: this
};
this.notify(eventData);
var eventData: observableModule.EventData = {
eventName: "myCustomEventName",
object: this
}
this.notify(eventData);
The minimum information needed to raise an event is the eventName
—it will be used to execute all event handlers associated with this event.
The next step is to hook to this event:
var myCustomObject = new MyClass();
myCustomObject.on("myCustomEventName", function(eventData){
console.log(eventData.eventName + " has been raised! by: " + eventData.object);
})
A similar logic is implemented for the propertyChange
event, so if your business logic requires that, propertyChange
can be emitted manually through the notify()
method (without using the Observable.set()
method that also fires the propertyChange
event).
Though the radio station comparison is convenient for understanding the concept, events are a bit more complicated on the inside. To be able to notify the listener, the sender contains a pointer to the listener. Even if you set the listener object to null
or undefined
, it is not eligible for garbage collection, because the sender is alive and has a live reference to the listener object. This could result in a memory leak when the object lifetimes of the sender and the listener differ significantly.
Consider this scenario: A UI element creates a lot of child controls, each of which hooks to an event of the parent. Then a child control is released (during a list view scrolling for instance), causing a memory leak.
To prevent these memory leaks, it is good practice to remove your event listener handler before releasing the listener object. Unfortunately, sometimes you can't determine the exact time to call the off
or removeEventListener
function. In such cases use another option of the NativeScript framework: Week Events.
A weak event, as its name suggests, creates an weak reference to the listener object, which helps you release the listener object without removing the event listener pointer.
Using weak event listeners is very similar to normal events. This is how to add a weak event listener (code comments are included for clarity):
var weakEventListenerModule = require("ui/core/weakEventListener");
var buttonModule = require("ui/button");
var observableModule = require("data/observable");
var testButton = new buttonModule.Button();
testButton.text = "Test";
testButton.on(buttonModule.knownEvents.tap, function () {
source.set("testProperty", "change" + counter);
});
var source = new observableModule.Observable();
var counter = 0;
var handlePropertyChange = function () {
counter++;
this.text = counter + "";
};
var weakEL = weakEventListenerModule.WeakEventListener;
var weakEventListenerOptions: weakEventListenerModule.WeakEventListenerOptions = {
// create a weak reference to the event listener object
targetWeakRef: new WeakRef(this),
// create a weak reference to the event sender object
sourceWeakRef: new WeakRef(this.source),
// set the name of the event
eventName: observable.knownEvents.propertyChange,
// set the event handler
handler: handlePropertyChange,
// (optional) set the context in which to execute the handler
handlerContext: testButton,
// (optional) set a specialized property used for extra event recognition
key: this.options.targetProperty
}
weakEL.addWeakEventListener(this.weakEventListenerOptions);
import weakEventListenerModule = require("ui/core/weakEventListener");
import buttonModule = require("ui/button");
import observableModule = require("data/observable");
var testButton = new buttonModule.Button();
testButton.text = "Test";
testButton.on(buttonModule.knownEvents.tap, function () {
source.set("testProperty", "change" + counter);
});
var source = new observableModule.Observable();
var counter = 0;
var handlePropertyChange = function () {
counter++;
this.text = counter + "";
};
var weakEL = weakEventListenerModule.WeakEventListener;
var weakEventListenerOptions: weakEventListenerModule.WeakEventListenerOptions = {
// create a weak reference to the event listener object
targetWeakRef: new WeakRef(this),
// create a weak reference to the event sender object
sourceWeakRef: new WeakRef(this.source),
// set the name of the event
eventName: observable.knownEvents.propertyChange,
// set the event handler
handler: handlePropertyChange,
// (optional) set the context in which to execute the handler
handlerContext: testButton,
// (optional) set a specialized property used for extra event recognition
key: this.options.targetProperty
}
weakEL.addWeakEventListener(this.weakEventListenerOptions);
The previous example shows how to attach a weak event listener to an observable object instance. A closer look to the handlePropertyChange
function shows that text
property of the this
object is changed when the propertyChange
event is raised (via the button tap event). It demonstrates how to use the handlerContext
property—its value is taken as an argument to this
inside the event handler function.
The targetWeakRef
and key
properties are optional when invoking a function on an event. However, they allow for removing an event listener. The properties are used as keys for a key-value pair that stores weak event listeners.
weakEL.removeWeakEventListener(this.weakEventListenerOptions);
weakEL.removeWeakEventListener(this.weakEventListenerOptions);