"So you want to build a native app?"






2014


Hybrid Apps!
-
HTML5 that acts like native
-
Direct access to native APIs
-
Familiar web dev environment
-
A single code base
"I have heard hybrid apps are slow!"
“It's not 2007 anymore”
Mobile devices have rapidly improved!
Year | Device | Processor | Ram |
2007 | iPhone 1 | 620 MHz | 128 MB |
Mobile devices have rapidly improved!
Year | Device | Processor | Ram |
2007 | iPhone 1 | 620 MHz | 128 MB |
2010 | iPhone 4 | 1 GHz |
512 MB |
Mobile devices have rapidly improved!
Year | Device | Processor | Ram |
2007 | iPhone 1 | 620 MHz | 128 MB |
2010 | iPhone 4 | 1 GHz | 512 MB |
2014 | iPhone 6 | 1.4 GHz dual-core | 1 GB |
Mobile devices have rapidly improved!
Year | Device | Processor | Ram |
2007 | iPhone 1 | 620 MHz | 128 MB |
2010 | iPhone 4 | 1 GHz | 512 MB |
2014 | iPhone 6 | 1.4 GHz dual-core | 1 GB |
2015 | iPhone 6s | 1.8 GHz dual-core | 2 GB |
WEB TECHNOLOGIES YOU ALREADY
KNOW & LOVE
(You'll feel right at home)



SUPERPOWERED BY
ANGULAR
Extends the HTML vocabulary
Proven for large-scale app development
UI Components using Directives & Services
Sass!
CSS generated from the Sass preprocessor
Quickly give your app its own look and feel
CSS designed to be easily overridden
Supported Devices
iOS 6+
Android 4+
Win Phone 10



How it all comes together
- Your App
- Ionic
- Angular
- WebView (Cordova)
- Native App
Ionic Components
Demo

What are we building today?
ANGULAR
OVERVIEW
Quick Introduction
to the Basics
angular.module(
'todo',
[
'ionic',
'todo.services',
'backand',
'todo.backand',
'todo.config.constants',
'todo.interceptors'
]
);
www\js\app.js
Module In Action
angular.module(
'todo',
[
'ionic',
'todo.services',
'backand',
'todo.backand',
'todo.config.constants',
'todo.interceptors'
]
);
<body ng-app="todo">
www\js\app.js & www\index.html
Module In Action
angular
.module('todo')
.config(config);
config.$inject = ['BackandProvider', '$stateProvider',
'$urlRouterProvider', '$httpProvider', 'CONSTS'];
function config(
BackandProvider,
$stateProvider,
$urlRouterProvider,
$httpProvider,
CONSTS) {
BackandProvider.setSignUpToken(CONSTS.signUpToken);
BackandProvider.setAppName(CONSTS.appName);
$httpProvider.interceptors.push('APIInterceptor');
})
www\js\config\app.config.js
Config In Action
$stateProvider
.state('login', {
url: '/login',
templateUrl: 'templates/login.html',
controller: 'LoginController as login'
})
www\js\config\app.config.js
Routes In Action

.state('tab', {
url: '/tab',
abstract: true,
templateUrl: 'templates/tabs.html'
})
www\js\config\app.config.js
Routes In Action

.state('tab.projects', {
url: '/projects',
views: {
'tab-projects': {
templateUrl: 'templates/tab-projects.html',
controller: 'ProjectsController as vm'
}
}
})
www\js\config\app.config.js
.state('tab', {
url: '/tab',
abstract: true,
templateUrl: 'templates/tabs.html'
})
Routes In Action
<ion-tab
title="Projects"
icon="ion-document"
href="#/tab/projects">
<ion-nav-view
name="tab-projects">
</ion-nav-view>
</ion-tab>
www\js\config\app.config.js
.state('tab.projects', {
url: '/projects',
views: {
'tab-projects': {
templateUrl: 'templates/tab-projects.html',
controller: 'ProjectsController as vm'
}}
})
Tab In Action
<ion-tab
title="Projects"
icon="ion-document"
href="#/tab/projects">
<ion-nav-view
name="tab-projects">
</ion-nav-view>
</ion-tab>
www\js\config\app.config.js
.state('tab.projects', {
url: '/projects',
views: {
'tab-projects': {
templateUrl: 'templates/tab-projects.html',
controller: 'ProjectsController as vm'
}}
})
Tab In Action
www\js\config\app.config.js
Default Route
State to show when user hits home page
$urlRouterProvider.otherwise('/projects');
$urlRouterProvider.otherwise('/projects');
$urlRouterProvider.otherwise(function ($injector) {
var $state = $injector.get("$state");
$state.go("tab.projects");
});
www\js\config\app.config.js
Default Route
State to show when user hits home page
Anatomy of A View
<ion-view view-title="Projects">
<ion-content>
<!-- Content Here -->
</ion-content>
</ion-view>
www\templates\tab-projects.html
-
Grids
-
Menus
-
Tabs
-
ng-click
-
ng-options
-
ng-repeat
Examples

<ion-list>
<ion-item class="item item-text-wrap"
ng-repeat="project in vm.projects | orderBy:'name'"
ui-sref="tab.tasks({
projectId: project.id,
projectName: project.name
})">
<h2>{{project.name}} </h2>
<p>created: {{project.created_on}}</p>
<ion-option-button
class="button-assertive "
ng-click="vm.deleteProject(project) ">
Delete
</ion-option-button>
</ion-item>
</ion-list>
www\templates\tab-projects.html
ion-list Directive
(function () {
'use strict';
angular
.module('todo')
.controller('ProjectsController', ProjectsController);
ProjectsController.$inject = [
'ProjectService', '$ionicModal',
'$state', '$scope',
'$ionicListDelegate', 'projects'];
function ProjectsController(ProjectService, $ionicModal,
$state, $scope,
$ionicListDelegate, projects) {
}
})();
www\js\controllers\projects.controller.js
Controller
$scope
Glue between the controller and view
$scope - $broadcast and $on
-
$broadcast - fire event to selected scope
-
$on - can receive the event
-
$rootScope - send event to the whole app
function getMoreProjects() {
...
$scope.$broadcast('scroll.infiniteScrollComplete')
}
function doRefresh() {
...
$scope.$broadcast('scroll.refreshComplete');
}
www\js\controllers\projects.controller.js
$scope
Types of Services
constant | store values that will never change |
(function () {
angular.module('todo.config.constants', [])
.constant('CONSTS', {
anonymousToken: 'ec9b4565-8d2e-4740-a4dc-4e8e52dbbc72',
signUpToken: '536d7143-edf0-451b-9e8d-fa065c563eb6',
appName: 'ddjtodo'
})
.constant('$ionicLoadingConfig', {
template: '<ion-spinner></ion-spinner>'
})
})();
www\js\config\app.constants.js
constant
Types of Services
constant | store values that will never change |
factory | Service created using constructor function Uses Revealing Module pattern |
angular
.module('todo.services')
.factory('ProjectService', ProjectService);
ProjectService.$inject = [];
function ProjectService() {
var service = {
};
return service;
}
www\js\services\projects.service.js
Factory
angular.module('todo.services')
.factory('ProjectService', ProjectService);
ProjectService.$inject = [
'$http', 'BackandDataService', 'UserModel'
];
function ProjectService($http, BackandDataService, UserModel) {
var service = {
getProjects: getProjects,
addProject: addProject,
deleteProject: deleteProject,
};
return service;
function getProjects(pageNumber, pageSize) {}
function addProject(name) {}
function deleteProject(project) {}
}
www\js\services\projects.service.js
Revealing Module
Types of Services
constant | store values that will never change |
service | Service created using constructor function. using the this keyword and more OOP-like |
factory | Service created using constructor function Uses Revealing Module pattern |
API Interceptors
(service)
angular.module('todo.interceptors')
.service('APIInterceptor', ApiInterceptor);
ApiInterceptor.$inject = ['$rootScope', '$q'];
function ApiInterceptor($rootScope, $q) {
var service = this;
service.responseError = responseError;
service.request = request;
service.response = response;
function request(config) { return config }
function response(response) { return response; }
function responseError(response) {
return $q.reject(response);
}
}
www\js\interceptors\api.interceptor.js
Capture Errors
function request(config) {
$rootScope.$broadcast('loading:show');
return config;
}
function response(response) {
$rootScope.$broadcast('loading:hide');
return response;
}
function responseError(response) {
$rootScope.$broadcast('loading:hide');
return $q.reject(response);
}
www\js\interceptors\api.interceptor.js
Global Loading...
angular
.module('todo')
.config(config);
config.$inject = [
'BackandProvider', '$stateProvider',
'$urlRouterProvider', '$httpProvider', 'CONSTS'];
function config(
BackandProvider, $stateProvider,
$urlRouterProvider, $httpProvider, CONSTS) {
$httpProvider.interceptors.push('APIInterceptor');
}
www\js\config\app.config.js
Using Interceptor
Must Read


-
Powerful backend-as-a-service
-
Auto-Generated REST API
-
Security & User Management
-
Server-Side Logic


Text
Visual Data Model

Json Data Model

Generated RestApi
return $http ({
method: 'GET',
url: Backand.getApiUrl() + '/1/objects/project',
params: {
pageSize: 20,
pageNumber: 1,
filter: null,
sort: ''
}
});
User Management




Backand.signin(email, password);
www\js\services\login.service.js
Back& User Api
Backand.signin(email, password);
Backand.signup(
firstName, lastName,
email, password, confirmPassword);
www\js\services\login.service.js
Back& User Api
Backand.signin(email, password);
Backand.signup(
firstName, lastName,
email, password, confirmPassword);
Backand.signout();
www\js\services\login.service.js
Back& User Api
Backand.signin(email, password);
Backand.signup(
firstName, lastName,
email, password, confirmPassword);
Backand.signout();
Backand.socialSignIn(provider);
www\js\services\login.service.js
Back& User Api
Backand.signin(email, password);
Backand.signup(
firstName, lastName,
email, password, confirmPassword);
Backand.signout();
Backand.socialSignIn(provider);
Backand.socialSignUp(provider);
www\js\services\login.service.js
Back& User Api
Backand.signin(email, password);
Backand.signup(
firstName, lastName,
email, password, confirmPassword);
Backand.signout();
Backand.socialSignIn(provider);
Backand.socialSignUp(provider);
Backand.getUserDetails()
www\js\services\login.service.js
Back& User Api
Backand.signin(email, password);
Backand.signup(
firstName, lastName,
email, password, confirmPassword);
Backand.signout();
Backand.socialSignIn(provider);
Backand.socialSignUp(provider);
Backand.getUserDetails()
Backand.getToken()
www\js\services\login.service.js
Back& User Api
Lets Get Coding
Sticky Notes
Done With Exercise
Have Question
Need Help
Starting ionic project
$ ionic serve

demo@demo.com
demo12

Open Project In VSCode

Making Master/Detail
Obvious



<ion-item
class="item item-text-wrap"
ng-repeat="project in vm.projects | orderBy:'name'"
ui-sref="tab.tasks({
projectId: project.id, projectName: project.name })"
ng-style="{'line-height': '50px'}"
>
<h2>{{project.name}} </h2>
18 <i class="ion-chevron-right icon"></i>
<p>created: {{project.created_on}}</p>
<ion-option-button class="button-assertive "
ng-click="vm.deleteProject(project) ">
Delete
</ion-option-button>
</ion-item>
www\templates\tab-projects.html
Adding > icon
<ion-item
11 class="item item-text-wrap item-icon-right"
ng-repeat="project in vm.projects | orderBy:'name'"
ui-sref="tab.tasks({
projectId: project.id, projectName: project.name })"
ng-style="{'line-height': '50px'}"
>
<h2>{{project.name}} </h2>
<i class="ion-chevron-right icon"></i>
<p>created: {{project.created_on}}</p>
<ion-option-button class="button-assertive "
ng-click="vm.deleteProject(project) ">
Delete
</ion-option-button>
</ion-item>
www\templates\tab-projects.html
Adding > icon
<ion-item
class="item item-text-wrap item-icon-right"
ng-repeat="project in vm.projects | orderBy:'name'"
ui-sref="tab.tasks({
projectId: project.id,
projectName: project.name })"
ng-style="{'line-height': '50px'}"
>
<h2>{{project.name}} </h2>
18 <i class="ion-chevron-right icon icon-accessory"></i>
<p>created: {{project.created_on}}</p>
<ion-option-button class="button-assertive "
ng-click="vm.deleteProject(project) ">
Delete
</ion-option-button>
</ion-item>
www\templates\tab-projects.html
Adding > icon
Add New Task


<ion-view enable-menu-with-back-views="true">
<ion-nav-title>Tasks: {{vm.project.name}}
</ion-nav-title>
4 <ion-nav-buttons side="right">
<button
class="button button-icon ion-compose"
ng-click="vm.showTaskModal()">
</button>
</ion-nav-buttons>
<ion-content>
...
<ion-content>
</ion-view>
www\templates\tab-project-tasks.html
Task Adding + icon
34 function activate() {
$ionicModal
.fromTemplateUrl('templates/modal-new-task.html', {
scope: $scope
}).then(function(modal) {
vm.taskModal = modal;
});
}
www\js\controllers\tasks.controller.js
Wire Up Modal
96 function showTaskModal() {
vm.taskModal.show();
}
function closeTaskModal() {
vm.taskModal.hide();
}
$scope.$on('$destroy', function () {
vm.taskModal.remove();
});
www\js\controllers\tasks.controller.js
Wire Up Modal
82
function saveNewTask(task) {
TaskService.addTask(vm.project, task)
.then(function (result) {
vm.tasks.push(result);
closeTaskModal();
task.name = '';
});
}
www\js\controllers\tasks.controller.js
Wire Up Save
Delete Task

12
<ion-item
class="item-text-wrap item item-icon-right"
ng-repeat="task in vm.tasks | orderBy: ['completed','name']">
{{task.name}}
<i class="ion-close icon icon-accessory"
ng-click="vm.deleteTask(task)"></i>
</ion-item>
www\templates\tab-project-tasks.html
Delete Button
100
function deleteTask(task) {
TaskService.deleteTask(vm.project, task)
.then(function (result) {
vm.tasks.splice(vm.tasks.indexOf(task), 1);
});
}
www\js\controllers\tasks.controller.js
Wire Up Delete
Complete Task

12
<ion-item
class="item-text-wrap item item-icon-left item-icon-right"
ng-repeat="task in vm.tasks | orderBy: ['completed','name']">
{{task.name}}
<i class="icon"
ng-class="task.completed ?
'ion-checkmark-circled' : 'ion-ios-circle-outline'"
ng-click="vm.completeTask(task, $index)">
</i>
<i class="ion-close icon icon-accessory"
ng-click="vm.deleteTask()"></i>
</ion-item>
www\templates\tab-project-tasks.html
Complete Button
96
function completeTask(task) {
TaskService.completeTask(vm.project, task)
.then(function (result) {
});
}
www\js\controllers\tasks.controller.js
Wire Up Complete
Pull to Refresh

10
<ion-content>
<ion-refresher
pulling-text="Pull to refresh..."
on-refresh="vm.doRefresh()">
</ion-refresher>
<ion-list ng-if="vm.tasks.length > 0">
www\templates\tab-project-tasks.html
Refresher
62
function doRefresh() {
vm.refreshing = true;
vm.pageNumber = 1;
TaskService.getTasks(vm.project, vm.pageNumber, vm.pageSize)
.then(function (result) {
vm.tasks = result;
})
.finally(function () {
$scope.$broadcast('scroll.refreshComplete');
vm.refreshing = false;
});
}
www\js\controllers\tasks.controller.js
Wire Up Refresh
Infinite Scroll

21
<ion-infinite-scroll
on-infinite="vm.getMoreTasks() "
ng-if="vm.moreDataCanBeLoaded
&& !vm.refreshing
&& vm.tasks.length > 0"
immediate-check="false"
>
</ion-infinite-scroll>
www\templates\tab-project-tasks.html
Wire Up Infinite Scroll
43
function getMoreTasks() {
vm.pageNumber = vm.pageNumber + 1;
TaskService.getTasks(vm.project, vm.pageNumber, vm.pageSize)
.then(function (result) {
var rowNum = result.length;
if (rowNum === 0 || rowNum < vm.pageSize) {
vm.moreDataCanBeLoaded = false;
}
if (rowNum > 0) {
vm.tasks = vm.tasks.concat(result);
}
})
.finally(function () {
$scope.$broadcast('scroll.infiniteScrollComplete')
});
}
www\js\controllers\tasks.controller.js
Wire Up Get More
Profile Page

10
<ion-tab title="Profile"
icon="ion-ios-person"
href="#/tab/profile">
<ion-nav-view name="tab-profile"></ion-nav-view>
</ion-tab>
www\templates\tabs.html
Add Tab
66
.state('tab.profile', {
url: '/profile',
views: {
'tab-profile': {
templateUrl: 'templates/tab-profile.html',
controller: 'ProfileController as vm',
}
}
})
www\js\config\app.config.js
Add Route
Confirm
Before
Task Delete


103
TasksController.$inject = ['TaskService', '$stateParams',
'$ionicModal', '$scope', 'tasks',
'$ionicPopup'];
function TasksController(TaskService, $stateParams,
$ionicModal, $scope, tasks,
$ionicPopup) {
....
function deleteTask(task) {
$ionicPopup.confirm({
title: 'Are You Sure?',
template: 'Are you sure you want to delete this task?'
}).then(function (res) {
if (res) {
TaskService.deleteTask(vm.project, task)
.then(function (result) {
vm.tasks.splice(vm.tasks.indexOf(task), 1);
});
}
});
}
www\js\tasks.controller.js
ionic popup
Should Also
Add to
Project Delete

Theming
-
Stop ionic serve
-
Run ionic setup sass
/*
$light: #fff !default;
$stable: #f8f8f8 !default;
$positive: #387ef5 !default;
$calm: #11c1f3 !default;
$balanced: #33cd5f !default;
$energized: #ffc900 !default;
$assertive: #ef473a !default;
$royal: #886aea !default;
$dark: #444 !default;
*/
scss\ionic.app.scss
Theme Colors
$calm: pink !default;

15
<ion-item
class="item-text-wrap item item-icon-left item-icon-right"
ng-repeat="task in vm.tasks | orderBy: ['completed','name']"
ng-class="{completedTask: task.completed}"
>
{{task.name}}
<i class="icon"
ng-class="task.completed ?
'ion-checkmark-circled' : 'ion-ios-circle-outline'"
ng-click="completeTask(task, $index)">
</i>
<i class="ion-close icon icon-accessory"
ng-click="vm.deleteTask()"></i>
</ion-item>
www\templates\tab-project-tasks.html
Complete Class
50
.completedTask {
text-decoration: line-through;
color: #A9A9A9;
}
scss\app.scss
Complete SCSS
Gulp Tasks

-
Add css & js reference tags
-
Compile Sass
Ionic Tooling

Automatically generate icons and splash screens
Creates size needed for each platform
Icons and Splash Screens
$ ionic resources
$ ionic resources --icon
$ ionic resources --splash
Platform Setup

VS Code Snippets

200+ Snippets
700 icons
-
Web Site - ionicframework.com
-
Docs - ionicframework.com/docs/
-
Ionic Meetups - blog.ionic.io/ionic-worldwide
-
Ionic Slack - ionicworldwide.herokuapp.com
-
Ionic Forums - blog.ionic.io/ionic-worldwide
Resources

Blogs

Time to Go
Create Your
Mobile App

thank you
Fluent Conf Ionic Workshop
By Justin James
Fluent Conf Ionic Workshop
Workshop for Ionic
- 3,332