Ionic Framework has been around for a few years now and has completely changed the way people develop hybrid mobile applications. With Angular out and Ionic 2 nearing stable release, the Ionic 1 and AngularJS predecessor will be a thing of the past and forgotten. What if you’ve gone all in with the first version of Ionic Framework, how do you convert to the latest and greatest?
We’re going to see how to take a simple Ionic Framework application and convert it to Ionic 2. While there will be similarities, the process is manual, but better in the long run.
To make this migration as easy as possible, we’re going to first develop a fresh Ionic Framework 1 application. After we have a fully functional Ionic 1 application, we’re going to create an Ionic 2 application from it. While the code will be different, the end result will be the same.
The application we build for both Ionic 1 and Ionic 2 will be a simple todo list type application. It will have two different screens and store data in HTML5 local storage. The goal here isn’t to build something extravagant, but instead show the conversion process.
Note that before going further, you need to have the Ionic 2 CLI installed. The original CLI will not create Ionic 2 applications.
To create a fresh Ionic Framework project, execute the following from your Command Prompt (Windows) or Terminal (Mac and Linux):
ionic start Ionic1Project blank --v1
cd Ionic1Project
ionic platform add ios
ionic platform add android
Notice that we’re using the --v1
tag to specify an Ionic Framework 1 project. Also notice that we’re adding the iOS platform. If you’re not using a Mac with Xcode installed, you cannot build for the iOS platform.
Now we can worry about developing the application, but before we start writing code, we need to create a few files and directories. Create the following:
mkdir www/pages
touch www/pages/list.html
touch www/pages/create.html
If your Command Prompt or Terminal does not have the mkdir
or touch
commands, go ahead and create the above manually.
Each of the two pages will be in the navigation stack via the AngularJS UI-Router. Before we write the UI markup in the HTML files, let’s worry about the AngularJS logic.
Open the project’s www/js/app.js file and include the following:
angular.module('starter', ['ionic'])
.run(function($ionicPlatform) {
$ionicPlatform.ready(function() {
if(window.cordova && window.cordova.plugins.Keyboard) {
cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
cordova.plugins.Keyboard.disableScroll(true);
}
if(window.StatusBar) {
StatusBar.styleDefault();
}
});
})
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state("list", {
url: "/list",
templateUrl: "pages/list.html",
controller: "ListController",
cache: false
})
.state("create", {
url: "/create",
templateUrl: "pages/create.html",
controller: "CreateController"
});
$urlRouterProvider.otherwise("/list");
})
.controller("ListController", function($scope, $state) {
$scope.people = localStorage.getItem("people") ? JSON.parse(localStorage.getItem("people")) : [];
$scope.create = function() {
$state.go("create");
};
})
.controller("CreateController", function($scope, $state) {
$scope.people = localStorage.getItem("people") ? JSON.parse(localStorage.getItem("people")) : [];
$scope.person = {}
$scope.save = function() {
if($scope.person.firstname && $scope.person.lastname) {
$scope.people.push({
firstname: $scope.person.firstname,
lastname: $scope.person.lastname
});
localStorage.setItem("people", JSON.stringify($scope.people));
$state.go("list");
}
};
});
There is a lot of code above, so we’re going to break it down to understand what is happening. We need a firm understanding to make the migration a success.
The first thing you’ll notice that isn’t part of the default Ionic 1 template is the .config
method:
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state("list", {
url: "/list",
templateUrl: "pages/list.html",
controller: "ListController",
cache: false
})
.state("create", {
url: "/create",
templateUrl: "pages/create.html",
controller: "CreateController"
});
$urlRouterProvider.otherwise("/list");
})
This is where we define all the possible routes for navigation in the application. We are defining two different routes, each of which having an HTML UI file and its own controller for page logic. The default page will be the list page.
The list page has its own controller called ListController
:
.controller("ListController", function($scope, $state) {
$scope.people = localStorage.getItem("people") ? JSON.parse(localStorage.getItem("people")) : [];
$scope.create = function() {
$state.go("create");
};
})
We are injecting $scope
and $state
components in this controller so we can bind data to the UI and navigate to different routes. When the controller loads data will be read from local storage. If the data does not exist, the people
variable will be initialized as an empty array. When the create
method is called, the next screen will be navigated to.
That brings us to the CreateController
which is part of the next screen:
.controller("CreateController", function($scope, $state) {
$scope.people = localStorage.getItem("people") ? JSON.parse(localStorage.getItem("people")) : [];
$scope.person = {}
$scope.save = function() {
if($scope.person.firstname && $scope.person.lastname) {
$scope.people.push({
firstname: $scope.person.firstname,
lastname: $scope.person.lastname
});
localStorage.setItem("people", JSON.stringify($scope.people));
$state.go("list");
}
};
});
Like with the previous controller we are loading the saved data. We are defining a person
object that will be bound to the UI and we are defining a save
method. When the save
method is called we first make sure that the firstname
and lastname
properties of the person
object are not undefined or blank. If not, push the data into the array and serialize it to be stored again in local storage. After saving the data, navigate to the previous component.
This brings us to the HTML that goes with the logic code.
Open the project’s www/pages/list.html file and include the following markup:
<ion-view title="Ionic 1 List">
<ion-nav-buttons side="right">
<button class="button button-clear" ng-click="create()">
<i class="icon ion-plus"></i>
</button>
</ion-nav-buttons>
<ion-content>
<ion-list>
<ion-item ng-repeat="person in people">
{{person.firstname}} {{person.lastname}}
</ion-item>
</ion-list>
</ion-content>
</ion-view>
In the list page we have a single button in the navigation bar. When the button is pressed, the create
method from the ListController
is executed. In the core content we have a list of data. The list is populated by looping through the people
array and printing out the data to the screen.
In the www/pages/create.html file, we have the following markup:
<ion-view title="Ionic 1 Create">
<ion-nav-buttons side="right">
<button class="button button-clear" ng-click="save()">
<i class="icon ion-checkmark"></i>
</button>
</ion-nav-buttons>
<ion-content>
<ion-list>
<ion-item class="item-input item-stacked-label">
<span class="input-label">First Name</span>
<input type="text" ng-model="person.firstname" placeholder="Nic" />
</ion-item>
<ion-item class="item-input item-stacked-label">
<span class="input-label">Last Name</span>
<input type="text" ng-model="person.lastname" placeholder="Raboy" />
</ion-item>
</ion-list>
</ion-content>
</ion-view>
Like with the previous page we have a single button in the navigation bar. When pressed the save
method will be called. In the core content we have another list, but this time the list has two form elements bound by properties in the person
object through the ng-model
tags.
Finally we need to make some changes to the project’s www/index.html file. Open it and include the following:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
<title></title>
<link href="lib/ionic/css/ionic.css" rel="stylesheet">
<link href="css/style.css" rel="stylesheet">
<script src="lib/ionic/js/ionic.bundle.js"></script>
<script src="cordova.js"></script>
<script src="js/app.js"></script>
</head>
<body ng-app="starter">
<ion-pane>
<ion-nav-bar class="bar-positive"></ion-nav-bar>
<ion-nav-view></ion-nav-view>
</ion-pane>
</body>
</html>
Really we only changed what is in the <ion-pane>
tags. By creating an <ion-nav-bar>
we can set the title and navigation buttons from each of the pages. Each of the pages will be routed through the <ion-nav-view>
tags.
At this point you should have a function Ionic Framework 1 application. It can be tested by executing the following from the Command Prompt or Terminal:
ionic run [platform]
Of course remember to swap out [platform]
with the correct platform.
If you’d like to save yourself some time, you can download the full project listed above, here. Running the downloaded project will require you to restore the state first which involved downloading the dependencies, platforms, and plugins.
With the base of our project created, it is now time to convert it to Ionic 2. To make things easier to understand, we’ll create a fresh Ionic 2 project and I’ll point out the changes as we go along.
From the Command Prompt (Windows) or Terminal (Linux and Mac), execute the following:
ionic start Ionic2Project blank --v2
cd Ionic2Project
ionic platform add ios
ionic platform add android
Notice the --v2
tag in the above. This means we are creating an Ionic 2 project that uses Angular and TypeScript rather than AngularJS. Just like with the previous application, we are adding the iOS build platform, but you won’t be able to build unless you’re using a Mac.
The base template comes with a default page called HomePage
, but we are not going to use it. Create the following directories and files:
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
If you don’t have the mkdir
and touch
commands, create them manually. Want to save yourself some major time? The Ionic 2 CLI has some convenience features. Run the following instead of all the commands listed above:
ionic g page list
ionic g page create
The above uses the Ionic 2 generator functions. Those commands or similar should be used when creating any new files in the project.
Things are a bit different in Ionic 2. While we didn’t have to mash all controllers, routes, etc., into the AngularJS www/js/app.js file, it wouldn’t have made too much of a difference. It still would have looked messy. With Ionic 2, each page has its own HTML and TypeScript file. This keeps the code incredibly clean.
Starting with the default page, being our list page. Open the project’s src/pages/list/list.ts file and include the following code:
import {Component} from '@angular/core';
import {NavController} from 'ionic-angular';
import {CreatePage} from '../create/create';
@Component({
templateUrl: 'list.html'
})
export class ListPage {
public people: Array<Object>;
public constructor(public navCtrl: NavController) { }
public ionViewDidEnter() {
this.people = localStorage.getItem("people") ? JSON.parse(localStorage.getItem("people")) : [];
}
public create() {
this.navCtrl.push(CreatePage);
}
}
You can already tell TypeScript and Angular is different from AngularJS. Don’t let it scare you away though. We start things off by including essential Angular and Ionic 2 component dependencies. We also include the page for creating data which we’ve yet to design.
In the @Component
block we are defining which HTML file should pair with this TypeScript file.
The real magic happens in the ListPage
class though. In Ionic Framework 1, anything that was part of the $scope
was accessible from the UI. In the case of Ionic 2, anything that is defined as public
is accessible from the UI. This means that our array of objects called people
and all methods in this file are accessible.
The constructor
method is where we do all our dependency injections, similarly how we defined things like $scope
and $state
in AngularJS. It is also where we can initialize variables. In Ionic Framework 1 we were able to navigate using $state
, but in Ionic 2 we navigate using the NavController
component.
While it is fine to initialize things in the constructor
method, it is frowned upon to load data in it. This is why we make use of the reserved ionViewDidEnter
method that gets triggered every time the page is navigated to. We also need to use this because the constructor
method does not trigger when navigating backwards in the stack.
Finally we have the create
method where we push the next page into the navigation stack. Before we look at that next page, let’s look at the HTML that goes with this TypeScript file.
Open the project’s src/pages/list/list.html file and include the following markup:
<ion-header>
<ion-navbar>
<ion-title>
Ionic 2 List
</ion-title>
<ion-buttons end>
<button ion-button icon-only (click)="create()">
<ion-icon name="add"></ion-icon>
</button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content padding>
<ion-list>
<ion-item *ngFor="let person of people">
{{person.firstname}} {{person.lastname}}
</ion-item>
</ion-list>
</ion-content>
Like with the previous application we create a navigation bar with a title and a button. The UI components are slightly different in Ionic 2 than they were in Ionic 1. In the core content we again have a list where we loop through each of the items in the people
array and present them on the screen.
There are differences though. Instead of using the AngularJS ng-click
tag, we are now using the Angular (click)
tag. Not a big deal here. Instead of using the AngularJS ng-repeat
to loop through items we are using the Angular *ngFor
tag. Overall, the differences in the markup are not too different.
This brings us to the page for creating data. Open the project’s src/pages/create/create.ts file and include the following code:
import {Component} from '@angular/core';
import {NavController} from 'ionic-angular';
@Component({
templateUrl: 'create.html'
})
export class CreatePage {
private people: Array<Object>;
public firstname: string;
public lastname: string;
public constructor(public navCtrl: NavController) { }
public ionViewDidEnter() {
this.people = localStorage.getItem("people") ? JSON.parse(localStorage.getItem("people")) : [];
}
public save() {
if(this.firstname && this.lastname) {
this.people.push({
firstname: this.firstname,
lastname: this.lastname
});
localStorage.setItem("people", JSON.stringify(this.people));
this.navCtrl.pop();
}
}
}
Like with the previous page we are importing the essentials and defining an HTML page in the @Component
block.
In the CreatePage
class we have a few private
and public
variables. Since we never need to display the people
array in the UI of this page, it can be private. The other two variables will be bound to a form in the UI.
In the constructor
method we are injecting the NavController
for navigation and loading our data from local storage in the reserved onPageDidEnter
method. This is nothing new to us so far.
In the save
method we first make sure the two public variables are defined and not empty. If this is true, push them into our people
array so we can serialize it and save it to local storage. When the save is complete, use the navigation controller to pop backwards in the navigation stack. This is essentially saying, go to the previous page.
Time to work on the HTML markup that goes with this TypeScript file. Open the project’s src/pages/create/create.html file and include the following markup:
<ion-header>
<ion-navbar>
<ion-title>
Ionic 2 Create
</ion-title>
<ion-buttons end>
<button ion-button icon-only (click)="save()">
<ion-icon name="checkmark"></ion-icon>
</button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content padding>
<ion-list>
<ion-item>
<ion-label stacked>First Name</ion-label>
<ion-input type="text" [(ngModel)]="firstname" placeholder="Nic"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>Last Name</ion-label>
<ion-input type="text" [(ngModel)]="lastname" placeholder="Raboy"></ion-input>
</ion-item>
</ion-list>
</ion-content>
Again we have a navigation bar with a title and a button. When the button is pressed it will call the save
method that was listed as public
. In the core content we have another list, but this time with input fields in it. Remember in AngularJS we used the ng-model
tags to bind data to the logic file? We can use the same, they are just called [(ngModel)]
instead.
We’re not in the clear yet. We’re no longer using the default HomePage
so we have to configure the new default page. Open the project’s src/app/app.component.ts file and make it look like the following:
import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar, Splashscreen } from 'ionic-native';
import { ListPage } from '../pages/list/list';
@Component({
templateUrl: 'app.html'
})
export class MyApp {
rootPage = ListPage;
constructor(platform: Platform) {
platform.ready().then(() => {
StatusBar.styleDefault();
Splashscreen.hide();
});
}
}
Notice we’ve just swapped out HomePage
with our ListPage
class.
Being that this is a multiple page application, we also need to define the possible pages in Angular’s @NgModule
block. Open the project’s src/app/app.module.ts file and include the following:
import { NgModule, ErrorHandler } from '@angular/core';
import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
import { MyApp } from './app.component';
import { ListPage } from '../pages/list/list';
import { CreatePage } from '../pages/create/create';
@NgModule({
declarations: [
MyApp,
ListPage,
CreatePage
],
imports: [
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
ListPage,
CreatePage
],
providers: [{provide: ErrorHandler, useClass: IonicErrorHandler}]
})
export class AppModule {}
Notice that both pages were imported and added to the declarations
and entryComponents
arrays of the @NgModule
block. Now we’re able to navigate between them.
When it comes to running the project, it is exactly the same as with Ionic Framework 1. Using the Terminal or Command Prompt, execute the following:
ionic run [platform]
Just remember to swap out [platform]
with the platform you want to run for. If you want to download this project and run it, you can download the source code here. Again, running the downloaded project will require you to restore the state which involves restoring the platforms, plugins, and project dependencies that were downloaded with NPM.
In this example we took a basic Ionic Framework 1 project that used AngularJS and converted it to an Ionic 2 project that used Angular and TypeScript. While much of the logic was the same, TypeScript does have a significantly different structure than AnguarJS. It may seem difficult at first, but the end result will be a much more maintainable project.
If you downloaded the source code to these projects, enjoy it, but please do not share it. I put a lot of time into coming up with the source and this tutorial and wouldn’t want my credit to be lost.