Nativescript-angular: UI bindings don't fire when orientation changes

Created on 30 Jan 2017  路  3Comments  路  Source: NativeScript/nativescript-angular

Can I make my Angular2 UI bindings affect the UI when the device orientation changes?

Because of the issue where layout qualifiers are not yet supported in Angular2 apps, I tried using this nativescript-orientation plugin and one-way bindings to change my UI when the device's orientation changes.

<DockLayout>
    <Label dock="{{isLandscape ? 'left' : 'top'}}" text="Controls" backgroundColor="red"></Label>
    <Label dock="{{isLandscape ? 'right' : 'bottom'}}" text="Results" backgroundColor="yellow"></Label>
</DockLayout>

and:

import { Component, OnInit, OnDestroy } from "@angular/core";
import * as application from "application";
import { DeviceOrientation } from "ui/enums";
//import * as orientation from "nativescript-orientation";
var orientation = require("nativescript-orientation");

@Component({
    templateUrl: "pages/dock-layout.html"
})
export class DockLayoutComponent implements OnInit, OnDestroy {

    isLandscape: boolean = false;

    constructor() {
    }

    ngOnInit() {
        this.isLandscape = orientation.getOrientation() == DeviceOrientation.landscape;
        application.on(application.orientationChangedEvent, this.onOrientationChanged);
    }

    ngOnDestroy() {
        application.off(application.orientationChangedEvent, this.onOrientationChanged);
    }

    onOrientationChanged = (args: application.OrientationChangedEventData) => {
        this.isLandscape = args.newValue == DeviceOrientation.landscape;
        console.log("onOrientationChanged():"+args.newValue+" isLandscape:"+this.isLandscape);
    };
}

The result is:
device-2017-01-30-131521

device-2017-01-30-131549

I expected the 'Controls' label to appear on the left, and the 'Results' label to appear on the right.

I initially thought that my bindings were written incorrectly. But when I added:

<ActionBar title="{{!isLandscape ? 'Portrait' : 'Landscape'}}">
    <ActionItem text="{{isLandscape ? 'Portrait' : 'Landscape'}}" (tap)="onRotateTapped($event)" ios.position="right"></ActionItem>
</ActionBar>

and

    onRotateTapped(event) {
        this.isLandscape = !this.isLandscape;
    }

By tapping the ActionItem, I found that the bindings did work properly and the UI elements moved to the left or right (ie. that the dock property of a child of a DockLayout is bindable), but the UI doesn't update despite the log saying isLandscape:true.

How can I update the UI during an orientation change, preferably without using setTimeout?

question

Most helpful comment

Magically, by toggling isLandscape within an NgZone resolved the issue:

import { Component, OnInit, OnDestroy, NgZone } from "@angular/core";
...
    constructor(private zone: NgZone) {
    }
    ...
    onOrientationChanged = (args: application.OrientationChangedEventData) => {
        this.zone.run(() => {
            this.isLandscape = args.newValue == DeviceOrientation.landscape;
            console.log("onOrientationChanged():"+args.newValue+" isLandscape:"+this.isLandscape);
        });
    };

Commit to project is here.

Thanks for the help, @tsonevn.

All 3 comments

Project is here.

I just tested your project, and it turns out to be an Ng zone issue. The nativescript-orientation plugin isn't angular-aware, and its callbacks do not execute in the zone. The resolution here is to wrap your event handler in our global.zonedCallback utility method:

application.on(application.orientationChangedEvent, global.zonedCallback(this.onOrientationChanged));

Ideally that would be done by the plugin and you shouldn't have to call that method yourself (global.zonedCallback is a noop when running without Angular). Details here:
http://www.nativescriptsnacks.com/videos/2016/06/13/zoned-callbacks.html

Magically, by toggling isLandscape within an NgZone resolved the issue:

import { Component, OnInit, OnDestroy, NgZone } from "@angular/core";
...
    constructor(private zone: NgZone) {
    }
    ...
    onOrientationChanged = (args: application.OrientationChangedEventData) => {
        this.zone.run(() => {
            this.isLandscape = args.newValue == DeviceOrientation.landscape;
            console.log("onOrientationChanged():"+args.newValue+" isLandscape:"+this.isLandscape);
        });
    };

Commit to project is here.

Thanks for the help, @tsonevn.

Was this page helpful?
0 / 5 - 0 ratings