An introduction to building mobile apps with Firebase, AngularJS and Ionic
Tweeter me @wedgybo
A group chat application based around hacker news posts
The battle plan
Feel free to shout out with any questions
> npm install -g ionic
> ionic start myhackernews
> cd myhackernews
> ionic setup sass
> ionic serve --lab
Provides AngularJS and an awesome set of components to help quickly build cross platform mobile applications
Build apps using their API to sync data in realtime.
My Hacker News
Blue Peter moment:
> bower install firebase angularfire
<!-- ionic/angularjs js -->
<script src="lib/ionic/js/ionic.bundle.js"></script>
<script src="lib/firebase/firebase.js"></script>
<script src="lib/angularfire/dist/angularfire.min.js"></script>
angular.module('starter', ['ionic', 'starter.controllers', 'starter.services', 'firebase'])
www/index.html
www/js/app.js
Angular uses services as the standard way of interacting with data. Use these to keep your data logic code in once place.
.factory('HackerNews', function ($http) {
return {
newest: function () {
// Returns a promise to the data from the API
return $http.get('http://hn-api.ionic.io/new/1');
}
};
})
www/js/services.js
We want to get the latest posts and be able to view and share the ones of interest with our buddies
.controller('HackerNewsCtrl',
function($scope, $ionicLoading, HackerNews) {
$scope.posts = [];
$scope.loadPosts = function () {
HackerNews.newest().then(function (posts) {
$scope.posts = posts.data;
});
};
// Load the posts when this controller is loaded
$scope.loadPosts();
})
www/js/controllers.js
<ion-view title="Hacker News">
<ion-content>
<ion-list>
<ion-item class="item-button-right" ng-repeat="post in posts" ng-click="viewPost(post)">
<h2>{{post.title}}</h2>
<p>{{post.submitted_by}} - {{post.submitted_on}}</p>
</ion-item>
</ion-list>
</ion-content>
</ion-view>
www/templates/tab-hackernews.html
.factory('PostViewer', function ($rootScope, $sce, $ionicModal) {
// Create a new scope for this viewer to bind to
var $scope = $rootScope.$new();
angular.extend($scope, {
post: null,
hideModal: function() {
return modal.hide();
},
trustedUrl: function (url) {
return $sce.trustAsResourceUrl(url);
}
});
$ionicModal.fromTemplateUrl(
'templates/post-detail.html',
{
scope: $scope
}
).then(function (ionicModal) {
modal = ionicModal;
});
return {
viewPost: function (post) {
// Update the scope with the latest post to view
$scope.post = post;
modal.show();
}
};
})
www/js/services.js
<ion-modal-view>
<ion-header-bar align-title="left">
<h1 class="title">{{post.title}}</h1>
<div class="buttons">
<button class="button button-outline button-positive" ng-click="hideModal()">
Close
</button>
</div>
</ion-header-bar>
<ion-content scroll="false">
<iframe height="100%" width="100%" ng-src="{{trustedUrl(post.url)}}" border="0"></iframe>
</ion-content>
</ion-modal-view>
// Inject PostViewer and add this to HackerNewsCtrl
$scope.viewPost = function (post) {
PostViewer.viewPost(post);
};
www/js/controllers.js
www/templates/post-detail.html
We need a service to store the information about the user and which group they are participating in
.factory('User', function ($window) {
var user = {
name: '',
group: ''
};
// Decode the user in local storage
user = JSON.parse($window.localStorage.getItem('user')) || user;
return {
get: function () {
return user;
},
save: function (updatedUser) {
user = updatedUser;
// Save any changes back to local storage for next time
$window.localStorage.setItem('user', JSON.stringify(user));
}
};
});
www/js/services.js
ng-model: Maps data
ng-change: Automagical saving
<ion-view title="Account">
<ion-content class="padding">
<div class="list">
<label class="item item-input item-label">
<span class="input-label">Username</span>
<input type="text" placeholder="Username"
ng-model="user.name" ng-change="saveUser(user)">
</label>
<label class="item item-input item-label">
<span class="input-label">Group</span>
<input type="text" placeholder="Group name"
ng-model="user.group" ng-change="saveUser(user)">
</label>
</div>
</ion-content>
</ion-view>
$scope.saveUser = function (user) {
User.save(user);
}
www/js/controllers.js - AccountCtrl
www/templates/tab-account.html
Lets create a service to interact with the group
.factory('GroupNews', function ($q, $firebase, User) {
var ref = new Firebase("https://my-hacker-news.firebaseio.com/groups/" + User.get().group || '');
return {
posts: $firebase(ref).$asArray(),
getPosts: function () {
return this.posts.$loaded();
},
sharePost: function (post) {
return this.posts.$add(post);
},
get: function (postId) {
return $firebase(ref.child(postId)).$asObject().$loaded();
}
};
})
<button class="button button-clear" ion-stop-event="click" ng-click="sharePost(post)">
<i class="icon ion-ios7-upload-outline"></i>
</button>
$scope.sharePost = function (post) {
$ionicLoading.show({
template: 'Sharing post ' + post.title
});
GroupNews.sharePost(post).then($ionicLoading.hide);
};
www/js/controllers.js
www/templates/tab-hackernews.html
www/js/services.js
.controller('ChatsCtrl', function($scope, GroupNews) {
GroupNews.getPosts().then(function (posts) {
$scope.posts = posts;
});
})
<ion-view title="Chats">
<ion-content>
<ion-list>
<ion-item ng-repeat="post in posts" type="item-text-wrap" href="#/chats/{{post.$id}}">
<h2>{{post.title}}</h2>
</ion-item>
</ion-list>
</ion-content>
</ion-view>
www/templates/tab-chats.html
www/js/controllers.js
We'll setup for viewing information about the shared post
.controller('ChatDetailCtrl',
function($scope, $stateParams, $firebase, User, GroupNews, PostViewer) {
GroupNews.get($stateParams.postId).then(function (post) {
$scope.post = post;
});
$scope.viewPost = function (post) {
PostViewer.viewPost(post);
};
})
.state('tab.chat-detail', {
url: 'chats/:postId',
views: {
'tab-chats': {
templateUrl: 'templates/chat-detail.html',
controller: 'ChatDetailCtrl'
}
}
})
www/js/app.js
www/js/controllers.js
We need to show some detail on the post
<ion-view title="Group Chat">
<ion-content padding="true">
<div class="list card">
<div class="item item-text-wrap">
<h2>{{post.title}}</h2>
</div>
<div class="item">
<p>
Post by: {{post.submitted_by}}
</p>
<p>
<span href="#" class="subdued">{{post.submitted_on}}</span> |
<span href="#" class="subdued">{{post.points}} Points</span> |
<span href="#" class="subdued">{{post.comments}} Comments</span>
</p>
</div>
<a class="item item-icon-left item-icon-right" ng-click="viewPost(post)" href="#">
<i class="icon ion-ios7-world"></i>
View post
<i class="icon ion-ios7-arrow-right"></i>
</a>
</div>
</ion-view>
www/templates/chat-detail.html
Lets get some commenting on the go
<ul class="comments">
<li class="comment" ng-repeat="comment in post.userComments">
<p>{{comment.text}}<span>{{comment.user}}</span></p>
</li>
</ul>
</ion-content>
<ion-footer-bar keyboard-attach class="bar-stable item-input-inset">
<label class="item-input-wrapper">
<input type="text" placeholder="Type your message" ng-model="comment.text" />
</label>
<button class="button button-clear" ng-click="addComment(post.$id, comment)">
Send
</button>
</ion-footer-bar>
www/templates/chat-detail.html
We need to hookup the ng-click actions to the controller which then adds comments via the GroupNews service
addComment: function (postId, comment) {
$firebase(ref.child(postId + '/userComments')).$asArray().$add(comment);
},
www/js/services.js - GroupNews
$scope.comment = {
user: User.get().name
};
$scope.addComment = function (postId, comment) {
GroupNews.addComment(postId, comment);
};
$scope.viewPost = function (post) {
PostViewer.viewPost(post);
};
www/js/controllers.js - ChatDetailCtrl
(Do we have time?)
Lets allow the user to refresh the latest list of Hacker News posts
<ion-refresher
pulling-text="Pull to refresh..."
on-refresh="loadPosts()">
</ion-refresher>
www/templates/tab-hackernews.html
HackerNews.newest(1).then(function (posts) {
$scope.posts = posts;
$scope.$broadcast('scroll.refreshComplete');
});
www/js/controllers.js - HackerNewsCtrl.loadPosts
Lets clean up those comments
.comments {
margin:0; padding:0;
list-style-type:none;
li {
display:block; clear:both;
max-width:50%;
margin:0 0 1rem 0; padding:0;
p {
border-radius:.75rem;
background:#e6e5eb; color:#383641;
padding:.6875rem; margin:0;
font-size:.875rem;
span {
display: block;
font-size: 10px;
font-weight: light;
}
}
}
li.mine {
float:right;
p {
background:#158ffe; color:#fff;
span {
text-align: right;
}
}
}
}
.filter('isMine', function (User) {
return function (input) {
return User.get().name === input;
};
})
ng-class="{'mine': {{comment.user | isMine}}}"
Use the ionic tools to create projects for your device of choice.
Live reload changes on the device is pretty damn awesome!
> ionic platform add [ios|android]
> ionic run -l [ios|android]
Point your mobile browser here and give it a whirl.
https://wedgybo.github.io/my-hacker-news
Remember to name yourself and set the group to gdgedinburgh in the account page.
If nothing shows. Tell your browser to load unsecure scripts
jamie@mallzee.com - +JamieSutherland - @wedgybo