I release a lot of content and build a lot of mobile applications using the NativeScript mobile framework, most of which includes Angular. Lately I’ve been getting many requests for information on using a side drawer within the application. These side drawer components can improve the user experience significantly so I figured I would explore the topic.
We’re going to see how to include a side drawer in our NativeScript Android and iOS application, built with Angular. To take things to the next level, we’re also going to include a feature rich list view as our core content.
This tutorial will be a little different because it includes official NativeScript components that aren’t part of the core framework, meaning they will need to be included through a separate package. The RadSideDrawer
and RadListView
components are available through what is called NativeScript UI, which includes a free and a paid version. Everything we see here is available in the free version.
As a disclaimer, some of the code from this article was taken from the NativeScript UI documentation. You’ll notice some of the component text and class names might look familiar. I’ve taken my own spin on it and given them a much needed explanation.
To be successful with this project there are a few requirements, which are as follows:
As of right now, NativeScript 2.4 is the latest version available. I have no doubt that future versions will be the same or similar, but for reference it is a good idea to know what I’m using. To be able to build Android applications, you’ll need the Android SDK and to be able to build iOS applications you’ll need Xcode which is only available on Mac.
To keep things simple and easy to understand, we’re going to create a fresh NativeScript project and work our way up. From the Command Prompt (Windows) or Terminal (Mac and Linux), execute the following:
tns create AwesomeProject --ng
cd AwesomeProject
tns platform add ios
tns platform add android
In the above you’ll notice the --ng
tags. This is an indicator that we’re building an Angular project with TypeScript. While I’m adding the iOS platform, you can’t actually do this unless you’re using a Mac with Xcode installed.
Information on installing and configuring NativeScript on your machine can be found here.
This project will show simple Toast notifications at some point. A Toast notification plugin can be installed by executing the following:
tns plugin add nativescript-toast
More information on Toast notifications can be seen in a previous article I wrote on the subject.
So what are we going to attempt to build?
In the above example, we don’t have much in terms of functionality, but plenty in terms of user experience. We are going to create an application with core content and a side drawer that becomes visible based on swipe. This side drawer will have a lit of items that can be clicked. The core content is a feature rich list that includes swipe actions and pull-to-refresh functionality.
To make use of the RadSideDrawer
and RadListView
components, we need to install the NativeScript UI package. From the Terminal or Command Prompt, execute the following:
tns plugin add nativescript-telerik-ui
More information on the UI components can be found here.
With the package installed, it needs to be loaded into the project. This can be done via a few imports and injections in the project’s app/app.module.ts file.
Open the project’s app/app.module.ts file and include the following:
import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
import { SIDEDRAWER_DIRECTIVES } from "nativescript-telerik-ui/sidedrawer/angular";
import { LISTVIEW_DIRECTIVES } from 'nativescript-telerik-ui/listview/angular';
import { AppComponent } from "./app.component";
@NgModule({
declarations: [
AppComponent,
LISTVIEW_DIRECTIVES,
SIDEDRAWER_DIRECTIVES
],
bootstrap: [AppComponent],
imports: [NativeScriptModule],
schemas: [NO_ERRORS_SCHEMA]
})
export class AppModule { }
You’ll notice that we’ve imported two NativeScript UI directives and added them to the declarations
array of the @NgModule
block. The same will apply to any other NativeScript UI component you wish to use.
We’re ready to start the development of our application now!
Going forward, most magic will happen in through project HTML and not so much TypeScript code. This is because we’re putting emphasis on user experience rather than functionality in this tutorial.
That said, open the project’s app/app.component.ts file and include the following TypeScript code:
import { Component, ViewChild, OnInit } from "@angular/core";
import { ListViewEventData, RadListView } from "nativescript-telerik-ui/listview";
import { RadSideDrawerComponent, SideDrawerType } from "nativescript-telerik-ui/sidedrawer/angular";
import { View } from 'ui/core/view';
import * as Utils from "utils/utils";
import * as FrameModule from "ui/frame";
import * as Toast from 'nativescript-toast';
@Component({
selector: "my-app",
templateUrl: "app.component.html",
})
export class AppComponent implements OnInit {
public emails: Array<string>;
public selected: number;
private drawer: SideDrawerType;
@ViewChild(RadSideDrawerComponent)
public drawerComponent: RadSideDrawerComponent;
public constructor() {
this.emails = [
"Welcome to The Polyglot Developer Newsletter!",
"Raspberry Pi Zero's Available!",
];
}
public ngOnInit() {
this.drawer = this.drawerComponent.sideDrawer;
}
public onPullToRefreshInitiated(args: any) { }
public onSwipeCellStarted(args: ListViewEventData) { }
public onDelete() { }
public onArchive() { }
public onMenuTapped(value: any) {
Toast.makeText(value + " menu item selected").show();
this.drawer.closeDrawer();
}
}
We have many variables and methods above, so we’re going to break them down. Anything related to our RadListView
will be saved until later.
In the AppComponent
class we have a few public and private variables. The emails
variable will hold a list of strings that will be displayed in our RadListView
. The selected
variable will tell us which RadListView
row was interacted with so we can provide specific functionality. The drawer
variable will hold reference to our RadSideDrawer
so that way we can do things like open and close it. Finally we have drawerComponent
which we annotate as a @ViewChild
to our UI.
Because our functionality is to remain simple, we are initializing some strings to represent emails within our constructor
method. After the constructor
has triggered, the ngOnInit
will trigger, where we set the @ViewChild
to our drawer
variable.
We haven’t seen the UI yet, but when items in the side drawer are clicked, we want to execute a method. The onMenuTapped
method will take any value and just display it in a Toast notification. After the notification is presented, the drawer will close.
So what does the UI look like? Let’s build it!
Open the project’s app/app.component.html file and include the following:
<ActionBar title="{N} UI Example"></ActionBar>
<RadSideDrawer>
<StackLayout tkDrawerContent class="sideStackLayout">
<StackLayout class="sideTitleStackLayout">
<Label text="Menu"></Label>
</StackLayout>
<ScrollView>
<StackLayout class="sideStackLayout">
<Label text="Primary" class="sideLabel" (tap)="onMenuTapped('Primary')"></Label>
<Label text="Social" class="sideLabel" (tap)="onMenuTapped('Social')"></Label>
<Label text="Promotions" class="sideLabel" (tap)="onMenuTapped('Promotions')"></Label>
<Label text="Labels" class="sideLabel" (tap)="onMenuTapped('Labels')"></Label>
<Label text="Important" class="sideLabel" (tap)="onMenuTapped('Important')"></Label>
<Label text="Starred" class="sideLabel" (tap)="onMenuTapped('Starred')"></Label>
<Label text="Sent Mail" class="sideLabel" (tap)="onMenuTapped('Sent Mail')"></Label>
<Label text="Drafts" class="sideLabel" (tap)="onMenuTapped('Drafts')"></Label>
</StackLayout>
</ScrollView>
</StackLayout>
<StackLayout tkMainContent class="mainContent">
<GridLayout></GridLayout>
</StackLayout>
</RadSideDrawer>
Ignoring all the class
tags for now, we have an <ActionBar>
and <RadSideDrawer>
. The <RadSideDrawer>
has two very important components defined by the tkDrawerContent
and tkMainContent
directives. It is how we differentiate what is part of the drawer and what is part of the core content.
Within the tkDrawerContent
which represents our drawer, we have a list of <Label>
components. When each are clicked, our onMenuTapped
method will trigger, showing a Toast and closing the drawer.
Simple right?
Now what goes in the core content found in the tkMainContent
section? We’re going to find out.
Before we start adding UI for the core content, we are going to skip back into our TypeScript logic. Remember, there are a few methods that we had created previously that are currently empty.
Open the project’s app/app.component.ts file. We’re going to fill in some blanks with our methods.
public onPullToRefreshInitiated(args: any) {
var radListView = args.object;
setTimeout(() => {
this.emails.push("NativeScript for the Angular Developer");
radListView.notifyPullToRefreshFinished();
}, 500);
}
If you’re not familiar with pull-to-refresh, it is functionality that occurs when you swipe a list downwards. Usually it is to load or refresh data. In the onPullToRefreshInitiated
method we capture reference to our RadListView
and set a timeout. We are setting a timeout because in our scenario the refresh is instant. We want to demonstrate what happens during refresh so we don’t want to dismiss it right away. In our scenario we are only adding static content to our list, but it can easily come from a database.
Swipe events on the RadListView
can be managed in numerous ways. Take for example that we want sticky buttons. In other words, when we swipe a list row, a button will show on either side and stick until dismissed.
public onSwipeCellStarted(args: ListViewEventData) {
var swipeLimits = args.data.swipeLimits;
swipeLimits.threshold = 60 * Utils.layout.getDisplayDensity();
swipeLimits.left = 120 * Utils.layout.getDisplayDensity();
swipeLimits.right = 120 * Utils.layout.getDisplayDensity();
this.selected = args.itemIndex;
}
In the above, we are defining how far the swipe can happen on either side which is also dependent on the device display density. Remember, swiping on one device may have smaller distance than another.
You’ll see it shortly, but the list row is not completely bound to the swipe buttons. For this reason, we have to keep track of which row was swiped. This information becomes useful when clicking one of the buttons.
public onDelete() {
let radListView = <RadListView> FrameModule.topmost().currentPage.getViewById("radlistview");
Toast.makeText("Deleted").show();
this.emails.splice(this.selected, 1);
radListView.notifySwipeToExecuteFinished();
}
In the onDelete
method we get the RadListView
associated to the UI. We know the item index we are working with, so we can use it to delete the item and dismiss the swipe action. The same applies to the onArchive
method:
public onArchive() {
let radListView = <RadListView> FrameModule.topmost().currentPage.getViewById("radlistview");
Toast.makeText("Archived").show();
this.emails.splice(this.selected, 1);
radListView.notifySwipeToExecuteFinished();
}
So what does our UI look like? Open the project’s app/app.component.html and check out the completed HTML:
<ActionBar title="{N} UI Example"></ActionBar>
<RadSideDrawer tkExampleTitle tkToggleNavButton>
<StackLayout tkDrawerContent class="sideStackLayout">
<StackLayout class="sideTitleStackLayout">
<Label text="Menu"></Label>
</StackLayout>
<ScrollView>
<StackLayout class="sideStackLayout">
<Label text="Primary" class="sideLabel" (tap)="onMenuTapped('Primary')"></Label>
<Label text="Social" class="sideLabel" (tap)="onMenuTapped('Social')"></Label>
<Label text="Promotions" class="sideLabel" (tap)="onMenuTapped('Promotions')"></Label>
<Label text="Labels" class="sideLabel" (tap)="onMenuTapped('Labels')"></Label>
<Label text="Important" class="sideLabel" (tap)="onMenuTapped('Important')"></Label>
<Label text="Starred" class="sideLabel" (tap)="onMenuTapped('Starred')"></Label>
<Label text="Sent Mail" class="sideLabel" (tap)="onMenuTapped('Sent Mail')"></Label>
<Label text="Drafts" class="sideLabel" (tap)="onMenuTapped('Drafts')"></Label>
</StackLayout>
</ScrollView>
</StackLayout>
<StackLayout tkMainContent class="mainContent">
<GridLayout>
<RadListView
id="radlistview"
[items]="emails"
itemSwipe="true"
pullToRefresh="true"
(pullToRefreshInitiated)="onPullToRefreshInitiated($event)"
(itemSwipeProgressStarted)="onSwipeCellStarted($event)">
<Template tkListItemTemplate let-email="item">
<StackLayout class="listItemStackLayout">
<Label text="{{ email }}"></Label>
</StackLayout>
</Template>
<GridLayout *tkListItemSwipeTemplate columns="auto, *, auto" class="listItemSwipeGridLayout">
<StackLayout class="archiveViewStackLayout" col="0" (tap)="onArchive()">
<Label text="ARCHIVE" verticalAlignment="center" horizontalAlignment="center"></Label>
</StackLayout>
<StackLayout class="deleteViewStackLayout" col="2" (tap)="onDelete()">
<Label text="DELETE" verticalAlignment="center" horizontalAlignment="center"></Label>
</StackLayout>
</GridLayout>
</RadListView>
</GridLayout>
</StackLayout>
</RadSideDrawer>
Inside the RadListView
we have an id
attribute so our onDelete
and onArchive
methods can obtain the component reference. The list is populated from our emails
array and the various events are bound to methods in the TypeScript file.
The strings found in our list variable are simply presented in a <Label>
found in the list template. The real magic happens in what comes next.
<GridLayout *tkListItemSwipeTemplate columns="auto, *, auto" class="listItemSwipeGridLayout">
<StackLayout class="archiveViewStackLayout" col="0" (tap)="onArchive()">
<Label text="ARCHIVE" verticalAlignment="center" horizontalAlignment="center"></Label>
</StackLayout>
<StackLayout class="deleteViewStackLayout" col="2" (tap)="onDelete()">
<Label text="DELETE" verticalAlignment="center" horizontalAlignment="center"></Label>
</StackLayout>
</GridLayout>
The tkListItemSwipeTemplate
indicates that this layout is to be shown on swipe. It is a three column grid layout with buttons on each corner. More on NativeScript <GridLayout>
layouts can be seen in a previous article that I wrote.
Each of the two buttons will be centered in the layout and have methods that are called in the TypeScript file.
There are a few class
tags used throughout the HTML. Let’s see what those styles look like.
For simplicity, we’re going to use global styles in this example. Open the project’s app/app.css file and include the following:
.sideStackLayout {
background-color: #555555;
color: #FFFFFF;
}
.sideTitleStackLayout {
padding: 16;
font-weight: bold;
background-color: #333333;
}
.sideLabel {
padding: 16;
}
.listItemStackLayout {
padding: 16;
background-color: #FFFFFF;
}
.archiveViewStackLayout {
padding: 16;
background-color: #387EF5;
color: #FFFFFF;
}
.deleteViewStackLayout {
padding: 16;
background-color: #EF473A;
color: #FFFFFF;
}
@import 'nativescript-theme-core/css/core.light.css';
The above CSS isn’t really more than background colors and padding, but it will make a difference in how our UI looks.
At this point, executing tns run [platform]
will build and run the application on your desired Android or iOS platform.
You just saw how to add two NativeScript UI components to your NativeScript Angular application. The <ListView>
that ships with NativeScript is great, but there is much to be desired which is where the <RadListView>
comes in. Having a side drawer in the application also adds some potential benefit to the overall user experience of your NativeScript application as well.