Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Tablet Support for NativeScript With Angular

DZone's Guide to

Tablet Support for NativeScript With Angular

Learn to add tablet support to Native Angular apps with NativeScript, providing features like a split screen and custom styles.

· Mobile Zone
Free Resource

Download this comprehensive Mobile Testing Reference Guide to help prioritize which mobile devices and OSs to test against, brought to you in partnership with Sauce Labs.

NativeScript has great tablet (or multi resolution) support baked in, but that's for apps not using Angular. When building an Angular Native app you currently have to roll your own solution as the approach mentioned in that blog linked above doesn't yet work in an Angular context.

For one of my apps I had two tablet requirements that needed to be satisfied within a Universal App (one binary):

  1. Split view, where 'master' and 'detail' are shown side by side.
  2. Custom styles (larger text, buttons, padding, etc).

Let me show you what my approach was for both of these and please don't hesitate to share your ideas in the comments!

1. Split View

Let's first look at the end result to see what we're after. Below on the left, we have a phone, on the right a tablet, both running the same POS (Point Of Sale) app.

The first page is an overview of all the products in your inventory, loaded in a ListView (with great performance!). The user (shop owner) can add a customer's articles to the shopping cart, and once done navigate to the cart and (for instance) apply a discount.

On phones, we've added a little cart bar to the bottom of the article page, for tablets we wanted the cart to always be visible.

Excuse the Dutch text all over the place, we're currently i18n-ing the app, but that's not quite ready yet.

Image title

iPhone 7 and iPad Pro 9,7

The split view example below is more of a traditional master-detail setup. On the first page we have orders that customers have previously created (either on the website or in the physical store, through this app), and upon selecting an order its details will be shown.

Note that on tablets we highlight the selected order slightly (the arrow changes color, it's perhaps too subtle), and when the order status changes (triggered by the big green button at the bottom) it's also reflected in the master list (text turns red).

Image title

You can see the animations at the original source.

Those animations are pretty neat but a little epilepsy inducing, so but let's break them down into static images so we can scroll that noise out of our viewport and reason about this split view in a more relaxed manner:

Image title

On a phone (the two images on the left) we have two physical views - labeled 'master' and 'detail.' The 'detail contents' area is a separate component that we've extracted so we can reuse it. On tablets, we're only using the 'master' view, and we add the 'detail contents' component to the right.

To conditionally render a second column in the 'master' view we first need to know whether or not we're currently running on a tablet. Luckily NativeScript has got our backs on that one:

// required imports:
import { DeviceType } from "ui/enums";
import { device } from "platform";

// then wherever you need it:
const isTablet: boolean = device.deviceType === DeviceType.Tablet;

With that knowledge we can construct a view that supports both master-detail navigation on phones and split view on tablets:

<!-- 'master' view -->
<ActionBar></ActionBar>
<GridLayout [columns]="isTablet ? '*, *' : '*'">
  <StackLayout>
    <!-- 'master' ListView -->
  </StackLayout>
  <StackLayout col="1" *ngIf="isTablet">
    <!-- 'detail-contents' component, only for tablets -->
    <detail-contents></detail-contents>
  </StackLayout>
</GridLayout>


<!-- 'detail' view (used on phones only) -->
<ActionBar></ActionBar>
<StackLayout>
  <detail-contents></detail-contents>
</StackLayout>

So basically we're conditionally rendering a second GridLayout column on tablets where the contents can be shown, and we distribute the screen space evenly by using "*, *". You can play with this if the left pane would require a different width: "240, *" or "*, auto".

Upon selecting an item in the 'master' view your controller would add the selected item to some shared class that both the 'master' component and the 'detail-contents' component have access to (typically an Angular service class. And only on phones, you would then navigate to the 'detail' view.

2. Custom Styles

Tablets are typically used a bit further from the eye than phones so you may want to beef up things like font sizes. So let's take a look at how you would make a Label appear a bit bigger on tablets.

Remember we need to do this dynamically at runtime since we're building one binary only, so we can't just add an additional stylesheet to our build and be done with it.

2.1 App-Wide Styles

First, add a file 'app.tablet.css' next to your existing 'app.css'. Then copy over any style classes from app.css that you want to appear differently on tablets. Note that app.css will still be applied first so this works just like in a browser where app.tablet.css is loaded after app.css, effectively overriding similar class definitions.

Now open "app.module.ts" and add this code to the constructor:

import { NgModule } from "@angular/core";
import { DeviceType } from "ui/enums";
import { device } from "platform";
import * as application from "application";

const fs = require("file-system");

@NgModule({
  // ..
})

export class AppModule {
  constructor() {
    if (device.deviceType === DeviceType.Tablet) {
      let cssFileName = fs.path.join(fs.knownFolders.currentApp().path, "app.tablet.css");
      fs.File.fromPath(cssFileName).readText().then((result: string) => {
        application.addCss(result);
      });
    }
  }
}

2.2 Component-Specific Styles

This is a bit harder since that browser-style override doesn't work for components. Add a file 'my-component.tablet.css' next to your existing 'my-component.css'. Then override any style in there by prefixing your tablet styles with ".tablet":

/*  my-component.css  */
Label.text {
  font-size: 15;
}


/*  my-component.tablet.css  */
.tablet Label.text {
  font-size: 19;
}

Now open "my-component.ts" and DO NOT add the file to your @ComponentstyleUrls declaration as that would apply those tablet styles unconditionally.

So instead, we need to conditionally add the "tablet" class to the root of the page, so the ".tablet" prefix shown above is picked up, and load the tablet-specific css file:

import { Component, OnInit } from "@angular/core";
import { DeviceType } from "ui/enums";
import { device } from "platform";
import { Page } from "ui/page";

@Component({
  moduleId: module.id,
  selector: "my-component",
  templateUrl: "my-component.html",
  styleUrls: ["my-component.css"]
})

export class MyComponent implements OnInit {
  constructor(private page: Page) {
  }

  ngOnInit(): void {
    if (device.deviceType === DeviceType.Tablet) {
      this.page.className = "tablet";
      this.page.addCssFile("~/pages/my-component/my-component.tablet.css");
    }
  }
}

While this all works great for my app and my use cases, I'm sure there are other clever ways to reuse application logic to support multiple device resolutions. For instance, if you need more granular control over when split views or styles are applied you could listen for screen rotation events and query the screen resolution at runtime.

As always, feel free to share your tips with the world in the comments!

Analysts agree that a mix of emulators/simulators and real devices are necessary to optimize your mobile app testing - learn more in this white paper, brought to you in partnership with Sauce Labs.

Topics:
mobile ,nativescript ,angular ,tablet

Published at DZone with permission of Eddy Verbruggen, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}