Build Your First Mobile App with Ionic – Part 2
The focus of this app is to easily get hold of developers that actively contribute to open source in PHP and JS.
Luckily for us, we have the data of the most active github users from 1st of Dec 2014 to 1st of Dec 2015 available to us by the help of paulmillr here.
We could decide to just fetch it from there or download it as a .json file.
Download it and put in the www folder.
Step 6
Let’s create a Developers Service. Create a new file services.js in the js folder.
Now, Open services.js and add this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
angular.module('devangelist.services', []) .service('Developers', function ($http, $q) { this.getData = function() { var d = $q.defer(); $http.get('github-users-stats.json').success(function(data) { var PHP = _.filter(data, function(result) { return result.language === "PHP"; }); var JS = _.filter(data, function(result) { return result.language === "Javascript"; }); d.resolve({ "PHP": PHP, "JS": JS }); }).error(function(data) { d.reject(data); }); return d.promise; }; }); |
In the code above, our service has one method called getData and we injected two inbuilt AngularJS services $http
and $q
. $http
for network operations like fecthing and posting of data from/to external services and $q
for dealing with promises.
1 2 3 4 5 6 7 |
var PHP = _.filter(data, function(result) { return result.language === "PHP"; }); var JS = _.filter(data, function(result) { return result.language === "JavaScript"; }); |
This piece of code above filters through the result returned from the json file using underscore
module and extracts the PHP developers into a variable and JS developers into another.
Then if it is succesful, we resolve the promise with an object containing PHP and JS data and if it is not, we reject the promise.
Quickly go to app.js and add this at the top to load the underscore module:
1 2 3 4 |
angular.module('underscore', []) .factory('_', function() { return window._; // }); |
Also, Inject the devangelist.services
into the global devangelist
module like so:
1 |
angular.module('devangelist', ['ionic', 'devangelist.controllers', 'devangelist.services']) |
Then, go to your terminal and run:
1 |
bower install underscore --save |
So that we can have the underscore
library in the lib folder.
Now, open index.html and reference the underscore library file and services.js file
1 2 |
<!-- Load the underscore module --> <script src="lib/underscore/underscore-min.js"></script> |
1 |
<script src="js/services.js"></script> |
Now, let’s create a Developer controller that can fetch data from the Developers Service.
Open controllers.js and add this to the existing empty controllers in the file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
.controller('DeveloperCtrl', function($scope, $ionicLoading, Developers) { $scope.developers = []; $ionicLoading.show({ template: '<i class="ion-loading-c"></i>', noBackdrop: true }); Developers.getData().then(function(developers){ $scope.developers = developers; $ionicLoading.hide(); },function(err){ $ionicLoading.hide(); }); }) |
From the code above, apart from the $scope
variable, we have injected the Developers
Service and $ionicLoading
service.
The $ionicLoading service has a show method that can be called on it to show a loading icon while data is been fetched.
1 2 3 4 5 6 |
Developers.getData().then(function(developers){ $scope.developers = developers; $ionicLoading.hide(); },function(err){ $ionicLoading.hide(); }); |
The code above simply fetches the data via the getData
method from the Developers
service and assigns the result to developers
array in the controller, then hides the loading icon.
Open developers.html and replace it with this:
developers.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
<ion-view class="developer-view"> <ion-nav-title class="has-tabs-top"> <span>Developers</span> </ion-nav-title> <ion-tabs class="tabs-striped tabs-top tabs-color-light"> <ion-tab class="tab-title" title="PHP"> <ion-pane> <ion-content> <div class="list card developer" ng-repeat="dev in developers.PHP"> <div class="item title"> <h2 class="developer-name">{{dev.name}}</h2> </div> <div class="item item-image"> <img class="full-image developer-image" ng-src="{{dev.gravatar}}"> </div> <div class="item item-body"> <div class="developer-tags"> <ul class="developer-tags-list"> <li class="developer-tag-item"> <span class="badge badge-energized">Contributions: {{dev.contributions}}</span> </li> <li class="developer-tag-item"> <span class="badge badge-energized">Followers: {{dev.followers}}</span> </li> </ul> </div> <p class="developer-bio" ng-bind-html="dev.location"> </p> <div class="developer-buttons"> <div class="row"> <div ng-if="dev.login" class="col"> <a href="" ng-click="goToUrl('http://github.com/{{dev.login}}')" class="button button-small button-outline button-block">@{{dev.login}}</a> </div> </div> </div> </div> </div> </ion-content> </ion-pane> </ion-tab> <ion-tab class="tab-title" title="JS"> <ion-pane> <ion-content> <div class="list card developer" ng-repeat="dev in developers.JS"> <div class="item title"> <h2 class="developer-name">{{dev.name}}</h2> </div> <div class="item item-image"> <img class="full-image developer-image" ng-src="{{dev.gravatar}}"> </div> <div class="item item-body"> <div class="developer-tags"> <ul class="developer-tags-list"> <li class="developer-tag-item"> <span class="badge badge-energized">Contributions: {{dev.contributions}}</span> </li> <li class="developer-tag-item"> <span class="badge badge-energized">Followers: {{dev.followers}}</span> </li> </ul> </div> <p class="developer-bio" ng-bind-html="dev.location"> </p> <div class="developer-buttons"> <div class="row"> <div ng-if="dev.login" class="col"> <a href="" ng-click="goToUrl('http://github.com/{{dev.login}}')" class="button button-small button-outline button-block">@{{dev.login}}</a> </div> </div> </div> </div> </div> </ion-content> </ion-pane> </ion-tab> </ion-tabs> </ion-view> |
We are simply looping through the data in our developers array in the DeveloperCtrl.
We have an object of two keys namely PHP and JS.
That’s why we have the ng-repeat as dev in developers.PHP
and for the Javascript tab, we have the ng-repeat as dev in developers.JS
.
Quickly update your developers.scss file. Somethings have changed.
Your developers.scss should look like so now:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
.developer-view { background: $content-bg; .tabs-striped .tabs { background-color: $top-bar-bg; .tab-item { max-width: none; } } .tab-title{ color: $top-bar-color; @include cssCalc("font-size", "#{($font-size)} + 2px"); font-weight:400; } .tab-title.tab-item.tab-item-active{ border-color: $main-menu-bg; margin-top: 0px; border-width: 0px 0px 4px 0px !important; .tab-title{ font-weight:600; } } .developer { .item.title { background-color: $main-menu-bg; } .item-image { border-top: none; } } .developer-name{ text-align: center; text-transform: uppercase; color: $top-bar-bg; font-weight: bold; // font-size: 18px; @include cssCalc("font-size", "#{($font-size)} + 6px"); } .developer-bio{ margin-top:0px; } .developer-tags { margin-bottom: 10px; .developer-tags-list { @include flexbox(); @include flex-wrap(wrap); } .badge { position: initial; margin: 4px 2px; background-color: $main-menu-bg; } .developer-tag-item { text-align:center; } } .developer-buttons { background-color: $speaker-actions-bg; .row, .col { padding:0px; } .button { margin:0px; border-color: $top-bar-bg; color: $top-bar-bg; &.activated{ background-color: $top-bar-bg; color: $top-bar-color; } } .col:first-child { margin-right:8px; } .col:last-child { margin-left:8px; } } } |
Let’s check our app. This is how it should look like now:
Fantastic!
Now, if you click on the button that shows the developer’s github handle, nothing happens.
Let’s fix that, it should actually open a link to the developer’s profile.
Add this to DeveloperCtrl
1 2 3 |
$scope.goToUrl = function(url){ window.open(url, '_blank', 'location=yes'); } |
Now, click on the link, it now opens the developers profile in a new window.
One more thing
Right now, our app kinda looks dry. Let’s add some animations to spice it up.
Download animate.css from here and put it in your css folder
Reference it in your index.html like so:
1 2 |
<!-- animate.css --> <link href="css/animate.css" rel="stylesheet"> |
Now go to developers.html and add animate and zoomIn classes to the first div in the PHP ion-tab like so:
1 2 3 4 5 6 7 8 |
<ion-tab class="tab-title" title="PHP"> <ion-pane> <ion-content> <div class="list card developer animated zoomIn" ng-repeat="dev in developers.PHP"> </div> </ion-content> </ion-pane> </ion-tab> |
Add animate and bounceInRight classes to the div in the JS ion-tab like so:
1 2 3 4 5 6 7 8 |
<ion-tab class="tab-title" title="JS"> <ion-pane> <ion-content> <div class="list card developer animated bounceInRight" ng-repeat="dev in developers.JS"> </div> </ion-content> </ion-pane> </ion-tab> |
Note: I collapsed the divs in these two code snippets
Check out the results and see how the divs bounce in and zoom in. Cool, right?
Feel free to play with the different animations and select the one you really like.
Update: This post was updated January 3, 2016
If you observe carefully, you’ll discover that we are actually loading hundreds of data into the view at once which is not cool.
Let’s lazy load the data and implement infinite scrolling.
Ionic has a very easy way of handling that, there is a directive called <ion-infinite-scroll>
Open developers.html
Add this just before the closing </ion-content> tag for the PHP and JS sections
1 |
<ion-infinite-scroll on-infinite="loadMore()" distance="5%"></ion-infinite-scroll> |
This ion-infinite-scroll component has an on-infinite attribute directive which calls a loadMore function.
Let’s go ahead and create this function in the DeveloperCtrl.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
.controller('DeveloperCtrl', function($scope, $ionicLoading, Developers) { $scope.developers = []; $ionicLoading.show({ template: '<i class="ion-loading-c"></i>', noBackdrop: true }); var end = 20; Developers.getData().then(function(developers){ $scope.developers = developers; console.log("Developers", $scope.developers); $ionicLoading.hide(); },function(err){ $ionicLoading.hide(); }); $scope.loadMore = function() { Developers.getMoreData(end).then(function(developers){ $scope.developers = developers; console.log("Developers", $scope.developers); $scope.$broadcast('scroll.infiniteScrollComplete'); }); end += 10; }; $scope.goToUrl = function(url){ window.open(url, '_blank', 'location=yes'); } }) |
Now, in the loadMore function, it still calls the Developers Service but it passes and argument to a getMoreData method. Let’s hold it right there.
Quickly head over to the Developers Service and add this getMoreData method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
this.getMoreData = function(end) { var d = $q.defer(); $http.get('github-users-stats.json').success(function(data) { var PHP = _.filter(data, function(result) { return result.language === "PHP"; }); var JS = _.filter(data, function(result) { return result.language === "JavaScript"; }); d.resolve({ "PHP": PHP.slice(0, end), "JS": JS.slice(0, end) }); }).error(function(data) { d.reject(data); }); return d.promise; }; |
This method is similar to the getData method that exists already but there is a twist to it.
It takes in an argument called end here:
1 2 3 4 |
d.resolve({ "PHP": PHP.slice(0, end), "JS": JS.slice(0, end) }); |
We are slicing the data from our github results to return only a specific amount of data.
Initially, when the loadMore function is called from the Controller, it fetches 20 results, When it is called again, it adds 1o to the end variable to add 10 more results to the array. Check out the code from the DeveloperCtrl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var end = 20; .... .... $scope.loadMore = function() { Developers.getMoreData(end).then(function(developers){ $scope.developers = developers; console.log("Developers", $scope.developers); $scope.$broadcast('scroll.infiniteScrollComplete'); }); end += 10; }; |
Here we are letting the ion-infinite-scroll
know that we’re done loading in the new items. To do this, we had to broadcast the scroll.infiniteScrollComplete
event.
Now, we need to tweak the original getData method in the Developers Service to load only 10 results into the view.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
this.getData = function() { var d = $q.defer(); $http.get('github-users-stats.json').success(function(data) { var PHP = _.filter(data, function(result) { return result.language === "PHP"; }); var JS = _.filter(data, function(result) { return result.language === "JavaScript"; }); d.resolve({ "PHP": PHP.slice(0,10), "JS": JS.slice(0,10) }); }).error(function(data) { d.reject(data); }); return d.promise; }; |
Awesome.
So, once the application loads up, it calls the getData method from the Developer Service which loads up just 10 results from the view.
When you get to the end of the view while scrolling, the infinite scrolling component invokes the loadMore function from the Developer controller which in turn calls the getMoreData method from the Developer Service to load more results into the view.
Quite Simple, right?. I’m also surprised how simple this was!
Conclusion
We have been able to set up the developers view and it works properly.
In the next blog post, we’ll be setting up the location of the developers on the location view and also setting up the about view. Stay tuned
Please, let me know if you have any questions or observations in the comments section below.
- How to build your own Youtube – Part 10 - August 1, 2016
- How to build your own Youtube – Part 9 - July 25, 2016
- How to build your own Youtube – Part 8 - July 23, 2016
- How to build your own Youtube – Part 6 - July 6, 2016
- Introducing Laravel Password v1.0 - July 3, 2016
- How to build your own Youtube – Part 5 - June 28, 2016
- How to build your own Youtube – Part 4 - June 23, 2016
- How to build your own Youtube – Part 3 - June 15, 2016
- How to build your own Youtube – Part 2 - June 8, 2016
- How to build your own Youtube – Part 1 - June 1, 2016