Our website is made possible by displaying online advertisements to our visitors. Please consider supporting us by disabling your ad blocker.

Use A Side Drawer And Feature Rich List View In A NativeScript Angular App

TwitterFacebookRedditLinkedInHacker News

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.

The Requirements

To be successful with this project there are a few requirements, which are as follows:

  • NativeScript 2.5+
  • Android SDK and / or Xcode

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.

Creating a New NativeScript with Angular Project

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?

NativeScript Side Bar and RadListView Example

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.

Including and Bootstrapping the NativeScript UI Component

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!

Developing the Swipe-Powered Side Drawer

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.

Adding a Feature Rich List View with Swipe Actions and Pull-to-Refresh

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.

Making the UI Attractive with CSS

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.

Conclusion

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.

Nic Raboy

Nic Raboy

Nic Raboy is an advocate of modern web and mobile development technologies. He has experience in C#, JavaScript, Golang and a variety of frameworks such as Angular, NativeScript, and Unity. Nic writes about his development experiences related to making web and mobile development easier to understand.