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

Converting Your Ionic 2 Mobile App To NativeScript

TwitterFacebookRedditLinkedInHacker News

As many of you know, I had been using Apache Cordova based frameworks such as Ionic Framework for a long time. They are convenient and easy to use when it comes to rapidly developing cross platform applications. The problem with using Apache Cordova frameworks such as PhoneGap, Ionic Framework and Onsen UI is the performance limitations that come with them, particularly because of their use of the platform web view.

NativeScript is a mobile development platform that I recently switched to because it eliminates the performance issues that people experience with web view based frameworks. This is because it doesn’t render your application in a web view. Instead the application gets compiled to native code giving the same performance you’d see in an app that was developed with Objective-C or Java.

Being that I spent a long time with Ionic Framework and Ionic 2, which uses AngularJS and Angular, I figured it would make sense to show how easy it is to convert your application to NativeScript, which also uses Angular. As an end result you’ll get a cross platform web application, built with a modern framework, that is native.

To make this tutorial as easy as possible to follow, we’re first going to create an application using Ionic 2. This application will allow you to add items to a list and navigate between pages. Then we’re going to take this same application and convert it to NativeScript.

Ionic 2 to NativeScript

Building a Mobile App with Ionic 2

We’re going to assume you already have the Ionic 2 CLI and other build tools installed on your computer. Let’s go ahead and start a new Ionic 2 project. Using your Command Prompt (Windows) or Terminal (Mac and Linux), execute the following:

ionic start IonicProject blank --v2 --typescript
cd IonicProject
ionic platform add ios
ionic platform add android

A few things to note above. We’re going to be creating an Ionic 2 project that uses TypeScript rather than vanilla JavaScript. Also note that if you’re not using a Mac you cannot build for the iOS platform.

This is going to be a two page application. In the default template there is one page found at app/pages/home that we are not going to use. Instead we’re going to create the following:

mkdir app/pages/list
mkdir app/pages/create
touch app/pages/list/list.html
touch app/pages/list/list.ts
touch app/pages/list/list.scss
touch app/pages/create/create.html
touch app/pages/create/create.ts
touch app/pages/create/create.scss

I’m using a Mac so I can make use of the mkdir and touch commands. If you’re using Windows, just go ahead and create those files and directories.

Before we start coding each of the pages, let’s add each of our scss files to the theme. Open the project’s app/theme/app.core.scss file and include the following two lines:

@import '../pages/list/list';
@import '../pages/create/create';

Remove any lines referring to the old and unused home page.

Creating the List Page

This application is going to have a simple list view for showing people that have been added. Let’s start by developing our application logic. Open the project’s app/pages/list/list.ts file and include the following code. Don’t worry, we’re going to break it down:

import {Page, NavController} from "ionic-angular";
import {CreatePage} from "../create/create";

@Page({
    templateUrl: "build/pages/list/list.html"
})
export class ListPage {

    private nav: NavController;
    public items: Array<Object>;

    constructor(nav: NavController) {
        this.nav = nav;
    }

    onPageDidEnter() {
        this.items = localStorage.getItem("items") != null ? JSON.parse(localStorage.getItem("items")) : [];
    }

    navigateToCreate() {
        this.nav.push(CreatePage);
    }

}

The first two lines will allow us to import the appropriate classes and components. The CreatePage component is something we’re going to create and doesn’t exist as of now.

The page template will refer to the HTML file that exists in the same directory as our logic file. We haven’t created it yet, but will when we’re done with the logic.

Inside the class we are defining a private nav variable that will allow us to navigate to the creation page and a public items array that will be bound to the UI. Inside the constructor method we initialize the navigation controller.

onPageDidEnter() {
    this.items = localStorage.getItem("items") != null ? JSON.parse(localStorage.getItem("items")) : [];
}

The above onPageDidEnter method is a reserved method in Ionic. Every time the page is entered, whether that be from a navigation push or a navigation pop, the code will execute. This is useful because when navigating back via a pop, the constructor is not called. Inside this method we read and parse the saved list of people.

For the Ionic 2 application we are using the browser based local storage. You’ll notice we do something slightly different in the NativeScript version.

The final function being the navigateToCreate will allow us to navigate to the creation page.

With the TypeScript code out of the way, we can focus on the small bit of UI. Open the project’s app/pages/list/list.html file and include the following markup:

<ion-navbar *navbar>
    <ion-title>Ionic 2 - Home</ion-title>
    <ion-buttons end>
        <button (click)="navigateToCreate()">Add</button>
    </ion-buttons>
</ion-navbar>

<ion-content class="list">
    <ion-list>
        <ion-item *ngFor="let item of items">
            {{ item.firstname }} {{ item.lastname }}
        </ion-item>
    </ion-list>
</ion-content>

The UI has a navigation button in the header and a list view as the core content. Each row of the list will be an object from the items array that was created in the TypeScript file.

Creating the Page for New Content

With the first page out of the way, we can create the second page. It will have a form that allows us to save data to be displayed in the list view. Like previously mentioned, we are using local storage for saving data.

Let’s start this page by working with the TypeScript file. Open the project’s app/pages/create/create.ts file and include the following code:

import {Page, NavController} from "ionic-angular";

@Page({
    templateUrl: "build/pages/create/create.html"
})
export class CreatePage {

    private items: Array<Object>;
    private nav: NavController;
    public firstname: string;
    public lastname: string;

    constructor(nav: NavController) {
        this.nav = nav;
        this.items = localStorage.getItem("items") != null ? JSON.parse(localStorage.getItem("items")) : [];
        this.firstname = "";
        this.lastname = "";
    }

    save() {
        if(this.firstname != "" && this.lastname != "") {
            this.items.push({firstname: this.firstname, lastname: this.lastname});
            localStorage.setItem("items", JSON.stringify(this.items));
            this.nav.pop();
        }
    }

}

Like with the previous page we are importing certain classes and components and defining the template to be used. The constructor method will initialize our variables. Since this page will never be accessed via a pop event we don’t need to have an onPageDidEnter method. We can load from our local storage directly from the constructor method.

The save method in this page is where the magic happens.

In this case we check to make sure firstname and lastname are set. If they are, add them to the items array and serialize the array so it can be saved to local storage. When done, we can navigate back to the previous page.

Now we can take a look at the UI that goes with this particular page. Open the project’s app/pages/create/create.html file and include the following markup:

<ion-navbar *navbar>
    <ion-title>Ionic 2 - Create</ion-title>
    <ion-buttons end>
        <button (click)="save()">Save</button>
    </ion-buttons>
</ion-navbar>

<ion-content class="create">
    <ion-list>
        <ion-item>
            <ion-input type="text" placeholder="First Name" [(ngModel)]="firstname"></ion-input>
        </ion-item>
        <ion-item>
            <ion-input type="text" placeholder="Last Name" [(ngModel)]="lastname"></ion-input>
        </ion-item>
    </ion-list>
</ion-content>

Like with the first page, there is a button in the header. More important is the form that exists in the content. There are two fields and they are bound to the TypeScript file via the Angular [(ngModel)] tag. With the variables bound, they can be saved when we click the save button.

Fixing the Routing File

Since we’re not using the stock home page, we need to fix up the routing file. Open the project’s app/app.ts file and include the following code:

import {App, Platform} from 'ionic-angular';
import {StatusBar} from 'ionic-native';
import {ListPage} from './pages/list/list';

@App({
    template: '<ion-nav [root]="rootPage"></ion-nav>',
    config: {}
})
export class MyApp {

    rootPage: any = ListPage;

    constructor(platform: Platform) {
        platform.ready().then(() => {
            StatusBar.styleDefault();
        });
    }

}

The only thing that really changed here is that we changed HomePage to ListPage wherever it occurs.

The Ionic 2 app that we’re going to convert is now finished. If you’d like to download the project, click here.

Building a Mobile App with NativeScript

The Ionic 2 project wasn’t too difficult to make. You’ll notice that it contains a lot of proprietary components in it, such as how navigation is done, or the particular markup tags. NativeScript is more vanilla to how Angular operates. This means that your Angular code can be more easily migrated to a web application. The UI is still proprietary, but this time it gets compiled to native UI components which again is great for performance.

Let’s start building our project. We’re going to assume you already have the NativeScript CLI installed at this point. Using your Command Prompt (Windows) or Terminal (Mac and Linux), execute the following:

tns create NativeScriptProject --ng
cd NativeScriptProject
tns platform add ios
tns platform add android

There are a few things you should note here. By using the --ng tag we are creating an Angular project that uses TypeScript. Also note that if you’re not using a Mac you cannot build for iOS.

Like with the Ionic 2 application, the NativeScript version will be a two page application. This time however, instead of pages we’re going to refer to them as components. This is how Angular intended pages to be called. Let’s go ahead and create the necessary files and directories:

mkdir app/components
mkdir app/components/list
mkdir app/components/create
touch app/components/list/list.component.ts
touch app/components/list/list.xml
touch app/components/create/create.component.ts
touch app/components/create/create.xml

Again I’m using a Mac which lets me create files and directories from my Terminal using mkdir and touch. If you’re using Windows, go ahead and create them however you see fit.

The default NativeScript Angular template comes with a bunch of CSS that we don’t necessarily want for our project. Open the project’s app/app.css file and remove everything that is in there.

Now we can start developing each of our components.

Creating the List Component

Remember how we created the list page for Ionic 2? It is going to be near identical in NativeScript. The core difference is that we’re going to use actual Angular components and classes rather than proprietary Ionic 2 components.

Starting with the logic that exists in our TypeScript file, open the project’s app/components/list/list.component.ts file and include the following code:

import {Component} from "@angular/core";
import {Router} from "@angular/router-deprecated";
import {Location} from "@angular/common";
import * as ApplicationSettings from "application-settings";

@Component({
    selector: "list",
    templateUrl: "./components/list/list.xml",
})
export class ListComponent {

    private router: Router;
    public items: Array<Object>;

    constructor(router: Router, location: Location) {
        this.router = router;
        this.items = JSON.parse(ApplicationSettings.getString("items", "[]"));
        location.subscribe((path) => {
            this.items = JSON.parse(ApplicationSettings.getString("items", "[]"));
        });
    }

    navigateToCreate() {
        this.router.navigate(["Create"]);
    }

}

The first three imports are directly out of the Angular documentation. This means that we could use most of this code in a web application if we wanted to.

The last import is a bit different though. Since this is a native application and doesn’t use a web browser or the DOM, we don’t have access to local storage. Instead we can make use of application-settings which behaves in nearly the same way.

You’ll notice that we’re defining our template as the soon to be created app/components/list/list.xml file. That will come next. Inside the ListComponent class we define a private variable for routing and a public variable that will allow us to bind our list to the UI.

The constructor method is slightly different from what we saw in Ionic 2:

constructor(router: Router, location: Location) {
    this.router = router;
    this.items = JSON.parse(ApplicationSettings.getString("items", "[]"));
    location.subscribe((path) => {
        this.items = JSON.parse(ApplicationSettings.getString("items", "[]"));
    });
}

With vanilla Angular, we don’t have the onPageDidEnter method that Ionic 2 was using to detect navigation and return events. Instead we have to subscribe to them using the Location component. In reality it isn’t much different and not any more difficult. You’ll also notice that the use of ApplicationSettings isn’t much different than the use of local storage.

The final method in this file will navigate us to the component for creating data. Navigation happens using the Angular router that we’ll configure after designing our components.

With the logic out of the way, it is time to look at the UI that will be paired with it. Open the project’s app/components/list/list.xml file and include the following markup:

<ActionBar title="NativeScript - Home">
    <ActionItem text="Add" (tap)="navigateToCreate()" ios.position="right"></ActionItem>
</ActionBar>
<GridLayout>
    <ListView [items]="items">
        <template let-item="item">
            <Label [text]="item.firstname + ' ' + item.lastname"></Label>
        </template>
    </ListView>
</GridLayout>

Like with Ionic 2, NativeScript has its own proprietary markup. The core difference is that this markup gets compiled to native UI code in the end.

At the top of this file we are defining an action bar with a single button. This button will call the navigateToCreate method that we created for navigating to the next component.

The core content of this component is the ListView. We tell it to bind to the items array that we created in the TypeScript file and to loop through it where each item in the array should be called item. Using item we can access the properties that exist in each of the objects and display them on the row with a Label tag.

Although different syntax, it isn’t too different than what we saw with Ionic 2.

Creating the Component for Adding New Data

Now we can focus on our second component, the one for creating new data for the list component. Again, you’ll notice many similarities between the Ionic 2 version and the NativeScript version.

Starting with the TypeScript file, open the project’s app/components/create/create.component.ts file and include the following code:

import {Component} from "@angular/core";
import {Location} from "@angular/common";
import * as ApplicationSettings from "application-settings";

@Component({
    selector: "create",
    templateUrl: "./components/create/create.xml",
})
export class CreateComponent {

    private location: Location;
    private items: Array<Object>;
    public firstname: string;
    public lastname: string;

    constructor(location: Location) {
        this.location = location;
        this.items = JSON.parse(ApplicationSettings.getString("items", "[]"));
        this.firstname = "";
        this.lastname = "";
    }

    save() {
        if(this.firstname != "" && this.lastname != "") {
            this.items.push({firstname: this.firstname, lastname: this.lastname});
            ApplicationSettings.setString("items", JSON.stringify(this.items));
            this.location.back();
        }
    }

}

We start by importing the appropriate Angular components and the NativeScript application-settings component. We define the XML UI file that will pair with the TypeScript file and define all of our variables in the constructor method. No surprises as of now.

In the save method we make sure the firstname and lastname variables are not empty and then save them to storage. When this has completed we navigate back in the navigation stack.

Now we can take a look at the XML file that is paired with the logic code. Open the project’s app/components/create/create.xml file and include the following markup:

<ActionBar title="NativeScript - Create">
    <NavigationButton text="Back" ios.position="left"></NavigationButton>
    <ActionItem text="Save" (tap)="save()" ios.position="right"></ActionItem>
</ActionBar>
<StackLayout>
    <TextField hint="First Name" [(ngModel)]="firstname"></TextField>
    <TextField hint="Last Name" [(ngModel)]="lastname"></TextField>
</StackLayout>

In the UI we create an action bar with potentially two buttons. iOS does not have hardware or software back buttons like Android, so we must create a navigation button for navigating back in the stack. We also create a button for saving our data.

Just like with Ionic 2 we make use of [(ngModel)], not because it is part of NativeScript or Ionic 2, but because it is the Angular thing to do for binding data to a TypeScript class.

Defining the Application Routes

This is where things can get a little different. Up until now, with the exception of minor framework differences, the Angular code between the frameworks have been pretty much the same. Ionic 2 does its own voodoo when it comes to navigation between pages or components. This is not vanilla Angular. NativeScript does navigation the Angular way. This means we need a core routing file.

Open your project’s app/app.component.ts file and include the following code:

import {Component} from "@angular/core";
import {RouteConfig} from "@angular/router-deprecated";
import {NS_ROUTER_DIRECTIVES, NS_ROUTER_PROVIDERS} from "nativescript-angular/router";

import {ListComponent} from "./components/list/list.component";
import {CreateComponent} from "./components/create/create.component";

@Component({
    selector: "my-app",
    directives: [NS_ROUTER_DIRECTIVES],
    providers: [NS_ROUTER_PROVIDERS],
    template: "<page-router-outlet></page-router-outlet>"
})
@RouteConfig([
    { path: "/list", component: ListComponent, name: "List", useAsDefault: true },
    { path: "/create", component: CreateComponent, name: "Create" },
])
export class AppComponent {

}

In this file we define all components used in our application. You can see we define both the ListComponent and CreateComponent in this file.

In the @RouteConfig we can define how to access these components. The ListComponent is the default component, while the CreateComponent can be accessed by trying to navigate to Create.

Fixing the Bootstrap for the iOS Platform

Since we are using the action bar in our NativeScript UIs, we need to make a revision to how the application is bootstrapped so it will work properly with iOS. Lucky for us, this isn’t complicated to do.

Open the project’s app/main.ts file and include the following code:

import {nativeScriptBootstrap} from "nativescript-angular/application";
import {AppComponent} from "./app.component";

nativeScriptBootstrap(AppComponent, null, { startPageActionBarHidden: false });

As you can see, the only difference from the original is the { startPageActionBarHidden: false } part. With that altered, it should work fine now.

The Ionic 2 application has now been converted to NativeScript. If you’d like to see the full project, it can be downloaded here. Using application-settings isn’t the only way to store data in NativeScript. I chose it because it was most relatable to local storage. I have other tutorials on how to use SQLite or Couchbase NoSQL for data storage.

Conclusion

We just saw how to take an Ionic 2 application that is based on Apache Cordova and convert it into a fully native NativeScript mobile application. Both Ionic 2 and NativeScript use Angular with the core difference being in the fact that you are left with a native application with NativeScript rather than an app that runs in a slow web view.

Most of the NativeScript code can be repurposed to a web application after changing the UI from NativeScript’s proprietary XML markup to HTML markup.

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.