Building a remote backend for an Ionic framework app with Parse

13 SK sriram kota Jul 14, 2014

In my previous article we built an Ionic framework app that pulls weather data from Forecast.io for a given latitude and longitude. In this tutorial, we're going to extend the app to use Parse as a remote backend.

If you're not familiar with Parse, it's a web-based platform that provides backend services. It allows developers to focus on the application development without having to worry about server setup and maintenance.

Since parse provides SDKs for Android, iOS and JavaScript, it's a natural choice for hybrid app developers looking for an easy-to-use remote backend.

Parse setup

Before we can begin using Parse, we have to sign up for a free developer account. Signing up is quick and easy. Just go here.

Once you signup, create an application by pressing the 'create new app' button

enter image description here

Once your app is created, Parse.com displays a window listing API keys that are specific to you application.

enter image description here

Cick the button labelled databrowser. You should now see the dashboard for your app.

enter image description here

From the dashboard you can manage your app settings, configure push notification, view analytics, create cloud code and create your data classes from the data browser. In this tutorial, we'll just be looking at the databrowser section. We'll explore analytics, push notifications and cloud code in future articles.

Click the new class button to, well, create a new class.

enter image description here

The default option for class type will be custom. Enter City for class name and press enter. You should now see a newly created class in the dashboard with the following default columns

  • objectId
  • createdAt
  • updatedAt
  • ACL

objectId is a unique alphanumeric ID generated by parse. We will be covering ACL's in future articles.

To add columns specific to the class City, click the button labelled + Col. Add the following columns.

  • name - string
  • latitude - integer
  • longitude - integer

Next, click the button labelled + Row to add blank rows.

Lastly, add the following data to pre-populate the class.

  • Miami, 25.7877,80.2241
  • New York City, 40.7127 , 74.0059
  • London, 51.5072, 1.1275
  • Los Angeles, 34.0500, 118.2500
  • Dallas, 32.7758, 96.7967
  • Frankfurt, 50.1117, 8.6858
  • New Delhi, 28.6100, 77.2300

Great, we have so far created an account on parse.com, got our API keys and created a class to store location details.

Before we begin coding the app we have to download the Parse JavaScript SDK here.

About the app

Our app shows the weather data for a given latitude and longitude, with the help of Forecast.io.

If you're just joining us, please refer to Part 1 of this tutorial for the preliminary coding and set up required.

Now, we're going to modify the application to use data from Parse.com instead of working with static data. You can begin by either making a copy of the code from Part 1 and make changes as specified, or download the complete source code for this version at the end of this tutorial. We'll be working with a copy of the existing code making changes as we go along.

To complete the app we need to:

  • Add parse-1.2.17.js to index.html
  • Modify LocationsCtrl in controllers.js to fetch data from Parse.
  • Initialize the Parse SDK in app.js
  • Modify the view file tab-cities.html to pass the currently selected items index to LocationsCtrl.changeCity.

If you downloaded the code for the previous article, you'll want to look for the ionic-weather-app folder. If you downloaded code into a different folder, substitute the folder name accordingly.

Go to the command prompt, navigate to the parent folder containing the code for part one of this tutorial, and type the following commands.

$cp -R ionic-weather-app ionic-weather-app2
$cd ionic-weather-app2/www

Now, copy parse-1.2.17.js to the folder js/lib/ionic/js/.

Next, open index.html, which is found in the www folder and find the line containing the following.

<script src="lib/ionic/js/angular/angular-resource.js"></script>

Add the following after below the line.

<script src="lib/ionic/js/parse-1.2.17.js"></script>

Save index.html, open www/js/controllers.js and replace its contents with the code below.

angular.module('starter.controllers', ['ionic'])
.constant('FORECASTIO_KEY', 'add-your-forecastio-key-here')
.controller('HomeCtrl', function($scope,$state,Weather,DataStore) {
   //read default settings into scope
   console.log('inside home');

   $scope.city  = DataStore.city;
   var latitude  =  DataStore.latitude;
   var longitude = DataStore.longitude;

    //call getCurrentWeather method in factory ‘Weather’
    Weather.getCurrentWeather(latitude,longitude).then(function(resp) {
    $scope.current = resp.data;
    console.log('GOT CURRENT', $scope.current);
    }, function(error) {
      alert('Unable to get current conditions');
      console.error(error);
   });
})
.controller('LocationsCtrl', function($scope,$rootScope, $state,DataStore) {
     $scope.cities = [];
     var data = window.localStorage.getItem('cities');

    if (data != null )  {
        $scope.cities   = null;
        $scope.cities   = JSON.parse(data);
        console.log('using local storage');
   }
   else {
       var cityObj = Parse.Object.extend("City");
       var query = new Parse.Query(cityObj);
       //query.descending("createdAt");  //specify sorting
      //query.limit(20);  //specify limit -- fetch only 20 objects

       query.find({
           success:function(results) { 
               $scope.$apply(function() {
                  var index =0;
                  var Arrlen=results.length ;

                   for (index = 0; index < Arrlen; ++index) {
                       var obj = results[index];
                        $scope.cities.push({ 
                          id :  obj.id,
                          name: obj.attributes.name,
                          lat:  obj.attributes.latitude,
                          lgn:  obj.attributes.longitude
                        });
                   }
                  window.localStorage.setItem('cities', JSON.stringify($scope.cities));
            });     
        },
        error:function(error) {
              console.log("Error retrieving cities!");
        }
    }); //end query.find
}


$scope.changeCity = function(cityId) {

    //get lat and longitude for seleted location
    var data = JSON.parse(window.localStorage.getItem('cities'));

    var lat  = data[cityId].lat; //latitude
    var lgn  = data[cityId].lgn; //longitude
    var city = data[cityId].name; //city name

    DataStore.setCity(city);
    DataStore.setLatitude(lat);
    DataStore.setLongitude(lgn);

    $state.go('tab.home');
}
})
.controller('SettingsCtrl', function($scope) {
  //manages app settings
});

Here's what occurs in LocationsCtrl upon initialization.

  • Locate an item called cities from local storage.
  • If it's found, we populate $scope.cities with data from local storage.
  • Otherwise, we make a call to Parse for it to fetch objects of type 'City' and, upon successful retrieval, we parse (pun fully intended) the data into an array, add it to $scope.cities and store the data in local storage for subsequent use.

The reason we do the above is to ensure that we reduce the number of server calls and work with a local copy of data to improve performance.

The next thing to notice is that the method changeCity is also modified to use the data from local storage to lookup the selected city's latitude and longitude.

Save controllers.js, open www/templates/tab-cities.html and replace its content the code below.

<ion-view title="Cities">
    <ion-content class="has-header">
      <h1>Select city</h1>
        <ion-list>
          <ion-item ng-repeat="city in cities">
                      <span href="#" ng-click="changeCity('{{$index}}')">
                        {{city.name}}
                     </span>  
          </ion-item>
        </ion-list>
    </ion-content>
</ion-view>

In the above code, $index is the iterator offset provided by ng-repeat. It's a handy way to access elements in a collection by index.

Lastly open www/js/app.js and replace its content the code below.

angular.module('starter', ['ionic', 'starter.controllers', 'starter.services'])
.run(function($ionicPlatform) {
$ionicPlatform.ready(function() {
    // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
    // for form inputs)
    if(window.cordova && window.cordova.plugins.Keyboard) {
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
     }
   if(window.StatusBar) {
      // org.apache.cordova.statusbar required
     StatusBar.styleDefault();
    }

      //initialize parse
      Parse.Initialize("add-your-parse-application-Id","add-you-parse-javascript-key");
   });
})
.config(function($stateProvider, $urlRouterProvider) {
 $stateProvider
  // setup an abstract state for the tabs directive
     .state('tab', {
       url: "/tab",
       abstract: true,
       templateUrl: "templates/tabs.html"
   })
   // Each tab has its own nav history stack:
    .state('tab.home', {
      url: '/home',
      views: {
        'tab-home': {
           templateUrl: 'templates/tab-home.html',
           controller: 'HomeCtrl'
        }
      }
    })
    .state('tab.changecity', {
       url: '/city',
       views: {
         'tab-cities': {
          templateUrl: 'templates/tab-cities.html',
          controller: 'LocationsCtrl'
        }
      }
    })
    .state('tab.settings', {
       url: '/settings',
       views: {
         'tab-settings': {
          templateUrl: 'templates/tab-settings.html',
          controller: 'SettingsCtrl'
        }
      }
    });
   // if none of the above states are matched, use this as the fallback
    $urlRouterProvider.otherwise('/tab/home');
 });

That’s it. Save the file, then build and run the app from the command line.

$ionic build ios  
$ionic run ios

ionic run ios installs the build on an iOS device. If you don't have a device handy, you can run the app in an emulator with the following command.

$ionic emulate ios

You can also view the app in a browser. If you don't have a web server installed, you can use Python's SimpleHTTPServer. From the terminal, navigate to the www folder and run the following command.

$python -m SimpleHTTPServer 3000

Now, open your favorite browser and vist http://localhost:3000. If this doesn't work, make sure SimpleHTTPServer is installed and that the path is correct.

If all is well, you should see the app run in the browser.

Conclusion

In this article, we've seen how easy it is to integrate Parse as a remote backend with Ionic apps. In future tutorials, we'll see how to add push notifications to an Ionic app.

If you're unfamiliar with AngularJS, I recommended referring to the AngularJS docs for a better understanding of it. In the mean time, you can donwload this article's source code on GitHub

Till next time, happy coding!

13 comments


Or enter your name and Email
  • M matiullah 2 years ago
    nice tutorial but when navigate to city tab it shows empty....any suggestion
  • C Chris 3 years ago
    I'ts not reading from Parse. Downloaded everything from GIT, put all keys into place, app runs, get the initial weather report for Miami, so forecast works, but when I navigate to the city tab, it is empty.
    • AB Ashrujit Bhattacharya 2 years ago
      yes. It's not reading from parse. not sure what to do..
  • TC Tim Cooper 3 years ago
    Just a small thing.. My console complained about "Initialize" being capitalized.. Should be: Parse.initialize("add-your-parse-application-Id","add-you-parse-javascript-key");
  • MW Michelle Wong 3 years ago
    Nice tutorial! Looking forward to see your next push notification tutorial!
  • S Stian 3 years ago
    Great tutorial! I hope you make the push notification tutorial soon :) Been struggeling with push notifications for ionic
  • R Remo 3 years ago
    Nice tutorial! Looking forward to the push notification tutorial!
  • DZ Dmitri Zaitsev 3 years ago
    Interesting writeup. Just few remarks or confusions: - Couldn't see why angular-resource.js is needed - Is DataStore the same as in the previous article? - Is it possible that 'LocationsCtrl' is doing too much? The best practice seems to put http requests into a service. - Using directly `.attribute` on a Parse object is discouraged, in favour of `.get`. I also found helpful to use `.toJSON()` to transform Parse response into plain array.
    • SK sriram kota 3 years ago
      Hello Dmitri, Thanks for your comments. You are correct, angular-resource.js is not needed in this instance as we are not using resources, i forgot to remove references to that, its an oversight on my part. Sorry about that. yes, datastore is the same. I agree LocationsCtrl could be refactored to use services either with http or resources. I wanted to keep the article simple to get the concepts across. Interesting, i never knew about toJSON() and also about using get instead of .attribute. Thank you for sharing and your detailed comments. What would you like me to write about next?
    • DZ Dmitri Zaitsev 3 years ago
      Yes, both object.get('property') and object.toJSON() come from Backbone.js that Parse JS SDK is built on. See more in their reference http://www.parse.com/docs/js/. The architectural design of Backbone is to "hide" those properties behind ".attributes" (that, in principle, should be made private but JS lacks those), and only access them via "getters" and "setters". For next writing - any app with Parse/Angular is great. Too much is written for Angular alone but with Parse it gets more original. Also performance things like how to cache offline, how to figure resource limits based on device, are harder to find.
  • SK sriram kota 3 years ago
    Hello, You only need to add forecast_io api key once inside controllers.js
  • ML Marc Lafay 3 years ago
    Hello , I'm frontend dev and need information. Where do I enter my forecast API key ? I replace 'FORECASTIO_KEY' everywhere ?