Have you seen all of the free code samples available on our Marketplace?

NativeScript Core

Events and Event Handling

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 an event sender (simply sender) or event raiser. The object that consumes the event is called an event listener (simply listener) or event handler.

The NativeScript framework provides a class Observable that powers the process of working with events. 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

To add an event handler means setting a function (method) that executes when the event is raised.

Adding an Event Listener Using JavaScript/TypeScript

The example below shows how to add an event listener by using the short (using on) and the full syntax (using addEventListener). There is a third optional parameter that represents the this argument. The code shows how to set a function that prints a "Hello World!" message in the console 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.

TIP: All examples in this article are available for preview in NativeScript Playground. Run this example in JavaScript or TypeScript.

const Button = require("tns-core-modules/ui/button").Button;
const testButton = new Button();
testButton.text = "Test";

let onTap = function(args) {
  console.log("Hello World!");
};
// Adding a listener with the short syntax
testButton.on(buttonModule.Button.tapEvent, onTap, this);

// Adding a listener with the full syntax
testButton.addEventListener(buttonModule.Button.tapEvent, onTap, this);
import { Button } from "tns-core-modules/ui/button";
import { GestureEventData } from "tns-core-modules/ui/gestures";
const testButton = new Button();
testButton.text = "Test";

export function onTap(args: GestureEventData) {
  console.log("Tap arguments: ", args);
};

//Adding a listener with the short syntax
testButton.on(buttonModule.Button.tapEvent, onTap, this);

//Adding a lister with the full syntax
testButton.addEventListener(buttonModule.Button.tapEvent, onTap, this);

Adding an Event Listener Using an XML Declaration

Another option to set an event handler is to use an XML declaration. 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).

<!-- main-page.xml -->
<Page>
  <StackLayout>
    <Label touch="onTouch" />
  </StackLayout>
</Page>
// main-page.js
function onTouch(args) {
  console.log("Touch arguments: ", args);
}
exports.onTap = onTap;
// main-page.ts
export function onTouch(args: TouchGestureEventData) {
  console.log("Touch arguments", args);
}

Adding an Event Listener Using MVVM Pattern

Often in NativeScript, the MVVM pattern is used with a separate view model that provides the binding context for your views. In such cases, the event handlers must be provided via the binding context syntax.

JavaScript example

// main-view-model.js
var observableModule = require("data/observable");

function HomeViewModel() {
  var viewModel = observableModule.fromObject({
    onButtonTap: function (args) {
      console.log("Button was pressed");
    },
  });

  return viewModel;
}
module.exports = HomeViewModel;
//main-page.js
const HomeViewModel = require("./home-view-model");

function pageLoaded(args) {
  const page = args.object;

  let homeViewModel = new HomeViewModel();
  page.bindingContext = homeViewModel;
}
exports.pageLoaded = pageLoaded;
<StackLayout class="home-panel">
   <Button text="Button" tap="" />
</StackLayout>

TypeScript example

// main-view-model.ts
import { Observable } from 'tns-core-modules/data/observable';
import { GestureEventData } from "tns-core-modules/ui/gestures";

export class HomeViewModel extends Observable {
    onButtonTap(args: GestureEventData): void {
        console.log("Button was pressed");
    }

    constructor() {
        super();
    }
}    
// main-page.ts
import { EventData } from 'tns-core-modules/data/observable';
import { Page } from 'tns-core-modules/ui/page';
import { HomeViewModel } from './home-view-model';

export function pageLoaded(args: EventData) {
    let page = <Page>args.object;
    page.bindingContext = new HomeViewModel();
}
<StackLayout class="home-panel">
   <Button text="Button" tap="" />
</StackLayout>

Removing an Event Listener

Usually, you don't need to remove the event listener. However, in some cases, you might need to do it when you want to receive the event just once or to free up resources.

Note: There is no syntax to remove an event listener through an XML declaration.

Removing an Event Listener Using JavaScript/TypeScript

The below example uses the shorthand and full syntax to remove all listeners for the tap event of the testButton instance. If more than one object is listening for events, you can set the second parameter with the name of the callback function. This way only the referenced event listener is removed. When multiple event listeners with different this arguments are available, a third optional parameter is used.

Removing a button tap event listener

//Removing a listener with short syntax
testButton.off(buttonModule.Button.tapEvent);

//Removing a listener with short syntax
testButton2.removeEventListener(buttonModule.Button.tapEvent);
//Removing a listener with short syntax
testButton.off(buttonModule.Button.tapEvent);

//Removing a listener with short syntax
testButton2.removeEventListener(buttonModule.Button.tapEvent);

Event Data Types

The base type of the event's arguments is of type EventData. The EventData provides two common properties:

  • object - The Observable instance that has raised the event.
  • eventName - The name of the raised event.
// example for using EventData interface
exports.onPageLoaded = function(args) {
    let page = args.object;
}
// example for using EventData interface
export function onPageLoaded(args: EventData) {
    let page = <Page>args.object;
}
<Page loaded="onPageLoaded">

In NativeScript, there are a lot of specific interfaces that are extending the EventData interface to provide additional functionalities for specific events. For example, the TouchGestureEventData is interface provided for the Touch event which has additional properties like action, android, ios, type, andview. When working with specific events check the API reference for the specific arguments of the event data you are working with.

PropertyChange Event

The Observable class provides a built-in event called propertyChange that is called when a property is changed.

Handling the propertyChange Event

The demo below shows how to subscribe to the propertyChange event.

const Observable = require("tns-core-modules/data/observable").Observable;
const observableObject = new Observable();

observableObject.on(Observable.propertyChangeEvent, function(propertyChangeData){
  console.log(propertyChangeData.propertyName + " has been changed and the new value is: " + propertyChangeData.value);
});
import { Observable } from "tns-core-modules/data/observable";
const observableObject = new Observable();

observableObject.on(Observable.propertyChangeEvent, function(propertyChangeData: PropertyChangeData){
  console.log(propertyChangeData.propertyName + " has been changed and the new value is: " + propertyChangeData.value);
});

Creating a Custom Class and Inheriting the Observable Class

It is important to note that the propertyChange 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.

const observableModule = require("tns-core-modules/data/observable");
var MyClass = (function (_super) {
  __extends(MyClass, _super);
  function MyClass() {
    _super.apply(this, arguments);
  }
  Object.defineProperty(MyClass.prototype, "myProperty", {
    get: function () {
      return this._myProperty;
      },
      set: function (value) {
        this._myProperty = value;
      },
      enumerable: true,
      configurable: true
    });
    return MyClass;
  })(observableModule.Observable);
exports.MyClass = MyClass;
import { Observable } from "tns-core-modules/data/observable";

export class MyClass extends Observable {
  private _myProperty:number;

  get myProperty(): number {
    return this._myProperty;
  }
  set myProperty(value: number) {
    this._myProperty = value;
  }
}

The code snippet in Example 4 fires the propertyChange event when the property value is changed.

PropertyChangeData Interface

The arguments received after the propertyChange event is raised, are of type PropertyChangeData. The interface provides five common properties:

  • object - The Observable instance that has raised the event.
  • eventName - The name of the raised event.
  • oldValue - The previous value of the property.
  • propertyName - The name of the property that has changed.
  • value - The new value of the property.

Creating a Custom Event

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.

let eventData = {
  eventName: "myCustomEventName",
  object: this
};
this.notify(eventData);
let eventData: 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:

let 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).

Avoiding Memory Leaks

Although 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 a good practice to remove your event listener handler before releasing the listener object. Unfortunately, sometimes you cannot determine the exact time to call the off or removeEventListener function. In such cases, use another option of the NativeScript framework: weak events.

Working with Weak Events

A weak event, as its name suggests, creates a weak reference to the listener object, which helps you release the listener object without removing the event listener pointer.

Adding a Weak Event Listener

Using weak event listeners is very similar to normal events. The demo below shows how to add a weak event listener (code comments are included for clarity):

var weakEventListenerModule = require("tns-core-modules/ui/core/weak-event-listener");
var Button = require("tns-core-modules/ui/button").Button;
var Observable = require("tns-core-modules/data/observable").Observable;

var testButton = new Button();
testButton.text = "Test";
testButton.on(buttonModule.Button.tapEvent, function () {
  source.set("testProperty", "change" + counter);
});

const source = new Observable();

let counter = 0;
let handlePropertyChange = function () {
  counter++;
  this.text = counter + "";
};

let weakEL = weakEventListenerModule.WeakEventListener;
let 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.Observable.propertyChangeEvent,
  // 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 * as weakEventListenerModule from "tns-core-modules/ui/core/weak-event-listener";
import { Button } from "ui/button";
import { Observable } from "data/observable";

const testButton = new Button();
testButton.text = "Test";
testButton.on(buttonModule.Button.tapEvent, function () {
  source.set("testProperty", "change" + counter);
});

const source = new Observable();

let counter = 0;
let handlePropertyChange = function () {
  counter++;
  this.text = counter + "";
};

let weakEL = weakEventListenerModule.WeakEventListener;
let 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.Observable.propertyChangeEvent,
  // set the event handler
  handler: handlePropertyChange,
  // specialized property used for extra event recognition
  key: this.options.targetProperty,
  // (optional) set the context in which to execute the handler
  handlerContext: testButton
}
weakEL.addWeakEventListener(this.weakEventListenerOptions);

Example 6 shows how to attach a weak event listener to an observable object instance. A closer look at the handlePropertyChange function shows that text property of the this object is changed when the propertyChange event is raised (via the button tap event). The function demonstrates how to use the handlerContext property—its value is taken as an argument to this inside the event handler function.

Removing a Weak Event Listener

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);