Ionic and Telerik Backend Services: A Healthy Partnership

Building apps using hybrid mobile technologies has enabled developers to deploy beautiful products in record time. Recently I completed an entire app from sketch to launch in one week in order to catch holiday shoppers. Scaffolding a great UI using nice frontend development tools makes things easy from a visual perspective, but the real meat of an app is its data, so we need a quick way to create a frontend and a solid way to hook it up to a reliable backend. For my recent app project and for this tutorial, I used Ionic and Telerik’s Backend Services, which is part of the Telerik Platform and provides an extremely convenient data management solution tailor-made for the developer who needs speed, accuracy, security, and ease-of-deployment.

In this tutorial series, we’re going to create a project just in time to give back for the holidays – an app for use by a municipal food pantry or food bank.

Stay tuned later in the series for an announcement about this project! Spoiler alert: there’s a prize involved!

I’m going to call this app “Pntry” (because removing vowels makes things cool). The use case is to connect the needs of local food banks with donors and clients so that the food banks can more efficiently serve the needs of their clients and donors can learn what is needed and customize their donations.

This is a pretty involved project, so I’m going to break up these tutorials into a two-part series:

  1. In Part 1, we’ll get our work environment set up and complete the login and registration screens.
  2. In Part 2, we’ll port the project into Telerik Platform and create the the food bank’s forms via which they request donations of specific food items.

Let’s get started!

Note: For now, I’m using the Ionic CLI tools to scaffold an app locally, but in the next tutorial we’re going to port our codebase into Telerik’s Platform. This will speed up development even more and give access to the excellent suite of developer tools offered by the Platform. For now, however, we’re going to build locally to get our workflow set up.

It’s a snap to scaffold out a basic Ionic app and quickly skin it. Follow these directions to install Ionic. From the command-line, ensure that you are in the directory you want to work in and create an app by typing:

$ ionic start pntry sidemenu

This command kicks of Ionic’s scaffolding process:

scaffolding an Ionic app

Change directories to the pntry directory that was just created:

$cd pntry

…and type:

$ionic serve

If you have installed all of Ionic’s dependencies correctly, a browser window opens showing a basic app with some stubbed-out data:

a blank Ionic app

We’re going to alter the app so as to build something like this:

wireframes for Pntry

I have stripped down this Ionic project to the bare bones and created a basic app which is available to you in GitHub. This is just a skeleton app with a few colors and most of the pages we want to use pre-created and linked up to the side menu, to which I added icons because I’m a little crazy for icons. Download this code and replace the /www folder in the Ionic app you just created with the /www folder in this codebase. Restart Ionic by typing r in the terminal; you should see the new codebase.

Note: If you already have Ionic installed and are familiar with the setup process, you can simply quit any currently running Ionic processes, checkout this base project from github and unzip it, cd to the folder where the code resides, and type $ionic serve

The app should look like this:

closed app

…and with the side menu open:

open app

Structuring the app

Ionic apps are initially scaffolded to include a frontend View, a Controller, and a stubbed-out Model that is hard-coded in the Controller. We need a robust Service layer to handle our data manipulation. To do this, we are going to separate the controller from the data tier by creating a services.js file.

Below is a basic description of the various JavaScript files in the application:

  1. app.js includes the routes that the app needs to know to navigate from page to page
  2. controllers.js contains the functions that allow the user to interact with the frontend such that data will reach the backend
  3. service.js handles the interaction between the app and the backend and returns data back to the frontend where is it displayed without need for a page refresh due to Angular’s two-way data binding. It’s all pretty magical.

Go ahead and add a services.js file to the /www/js folder. We’ll flesh it out as we work through this tutorial. Add a reference to that file in index.html under the reference to controllers.js:

<script src="js/services.js"></script>

Add the following line to the services.js file:

angular.module('pntry.services', [])

Add a reference to this service file on the first line within app.js:

angular.module('pntry', ['ionic', 'pntry.controllers', 'pntry.services'])

Enabling Backend Services

Now it’s time to hook up Telerik Backend Services to our frontend so that we can get these login and registration routines to actually do something. Backend Services is a cloud-based solution to store your app’s data. To access it, we are going to use the JavaScript SDK (though not pertinent to our app, there are plenty of other integrations available – you can learn more by reading the documentation.

Include the Backend Service file in your codebase. The easiest way to do this is to install it via Bower (note that it uses the former name of Backend Services, Everlive):

$bower install everlive

If you don’t have Bower installed, you can install it easily via npm using npm install -g bower

Bower installs files into the bower_components folder in the root. Move the everlive.all.min.js file that is installed by Bower to a new www/lib/everlive folder and reference it in index.html beneath the line referring to ionic.bundle.js:

<script src="lib/everlive/min/everlive.all.min.js"></script>

Go to Telerik’s Platform and login.

Don’t worry if you don’t have a Telerik Platform account; you can try it for free for 30 days. Learn more about pricing here.

Create a new workspace named Pntry, and click the green button to Create a Backend Services project.

backend

You are going to start from scratch in this project.

Users

Backend Services comes pre-built with some very nice features, foremost of which, for our purposes, is the Users object, which is like a database table that will be populated by our registered users.

Users

We are going to capture a displayname, username, password, email, and food bank name in our registration form and store this data in the Users object.

When the user successfully fills out the form, in addition, an email is generated automatically from the Backend to welcome the user to the network. The Backend comes preconfigured with four email templates for common registration and password management use cases.

Grab the API key from the API Key area of the Platform.

API Key

Now we can start fleshing out our Service layer. In the services.js file, add our first Factory object and add your own API Key:

angular.module('pntry.services', [])

.factory('API', function() {

  var api_key = 'your-api-key';

    return api_key;

});

Note: the backend offers both an API Key and a Master Key. The API Key is a public key, and should have access to public data in the backend. Read more about security scenarios and your API Keys here.

Our services layer will contain all the calls to the backend. Other service functions will be able to access that API key by invoking the factory.

Complete the Registration form

While we’re in the Backend Services area, let’s add one more field that we want to capture on registration. This will be the Organization Name so that we can identify the name of the food bank. In the Users area, click on the data structure icon on the right:

data icon

Add an item to the Users table called OrganizationName and click ‘add’ and ‘save’.

add item

Add a field to /www/templates/register.html under the opening card-list div so a new input field appears at the top of the registration form:

<label class="item item-input">
<span class="input-label">Organization Name</span>
<input type="text" name="organizationname" ng-model="registerData.organizationname"></label>

Complete the Registration functions

Now, we have to handle the registration form submission. We’ll begin by creating the User factory in services.js under the API factory. Note, we’re going to pass a token that we’re capturing during registration to and storing in localStorage:

.factory('User', function (API) {

var el = new Everlive({
        apiKey: API,
        scheme: 'https',
        token: localStorage.getItem('token')
     });
  return {
    register: function(registerData){          
        return el.Users.register(
            registerData.username, 
            registerData.password, 
              { 
                Email: registerData.email, 
                DisplayName: registerData.displayname,
                OrganizationName: registerData.organizationname
               })
            .then(function (data) {
                return data;
            },
            function(error) {
                return error;
            });               
    }
  }
});

Next, enable controller.js to use that new factory by adding its reference to the top of the that file. We are also using some extra goodies provided by Ionic for UI magic, so make sure the top of controller.js resembles this line:

.controller('AppCtrl', function($state, $scope, $ionicModal, $ionicPopup, User) {...

Including $state allows us to navigate to a new page after registration and login. $ionicPopup is a clean little popup that is useful in displaying registration errors generated from the backend. And User is the name of the factory you created in the services tier. In your controller, reference a factory like User.register in order to pass data back and forth from and to the service.

Finally, flesh out the doRegister() function in the controller:

// Perform the register action when the user submits the registration form
$scope.doRegister = function() {

    User.register($scope.registerData).then(function(data){
      if(data.result){
        //log me in
        $scope.loginData.username = $scope.registerData.username;
        $scope.loginData.password = $scope.registerData.password;
        $scope.doLogin();
        $scope.closeRegister();
        $state.go("app.inventory");
    }
    else{
      $ionicPopup.alert({
        title: status.data.message,
        template: 'Please try again!'
        });
      }
    });
}

Now, you should be able to open the registration form in your app, fill it in, click register, and view your data in Backend Services in the User table.

Building out Login and Authentication

By default, the Ionic project we generated comes prebuilt with a login screen. We are going to add to it a registration screen so that a food pantry can create a profile and own their own data. Most of the app needs to be password-protected, so we are going to store a token that is generated by Backend Services to help with our authentication.

Add a login function in services.js to the User factory beneath the register function:

login: function(loginData) {                
    return el.Users.login(
        loginData.username,
        loginData.password)
        .then(function (data) {
            return data;
        },
        function(error) {
            return error;
        });
},

Now, in the controller, things start to get interesting. We are going to use localStorage to save a token that the Backend Service sends back so that we can reuse it in future interactions.

Overwrite the current doLogin function with this code:

// Perform the login action when the user submits the login form
$scope.doLogin = function() {
    User.login($scope.loginData).then(function(data){
      if(data.result){
        localStorage.setItem("token",data.result.access_token);
        $state.go("app.inventory");
        $scope.loginmodal.hide();
      }
      else{
        $ionicPopup.alert({
            title: data.message,
            template: 'Please try again!'
          });
      }
    });
};

Finally, we can add a test to make sure that we have a token available and that it is valid; if it is not, we will push the user back to the home page; if so, we allow access to a given area. Add this code snippet above the previous code:

$scope.testLoginStatus = function() {

    var token = localStorage.getItem("token");        
     User.me(token).then(function(data){
      console.log(data)
      if(!data.result){
        $ionicPopup.alert({
            title: 'Your session has expired',
            template: 'Please login!'
          });
        //go home
        $state.go("app.home");          
        }
    });    
};

Now we can build out the service to check the User’s status and make sure the authentication token has been set during login:

//am I logged in?
me: function() {
    return el.Users.currentUser()
    .then(function (data) {
        return data;
    },
    function(error) {
        return error;
    });
}

Add the token test on click ( ng-click="testLoginStatus()") to each element of the menu in /www/templates/menu.html:

<ion-list>
    <ion-item nav-clear menu-close ng-click="testLoginStatus()" href="#/app/donationrequests">
      <i class="icon ion-help-buoy"></i> Donation Requests
    </ion-item>
    <ion-item nav-clear menu-close ng-click="testLoginStatus()" href="#/app/requestdonation">
      <i class="icon ion-speakerphone"></i> Request A Donation
    </ion-item>
    <ion-item nav-clear menu-close ng-click="testLoginStatus()" href="#/app/inventory">
     <i class="icon ion-clipboard"></i> Inventory
   </ion-item>
    <ion-item nav-clear menu-close ng-click="testLoginStatus()" href="#/app/needs">
      <i class="icon ion-compose"></i> Current Needs
   </ion-item>
</ion-list>

Reality check

The entire controllers.js file should now look like this:

angular.module('pntry.controllers',[])

.controller('AppCtrl', function($state, $scope, $ionicModal, $ionicPopup, User) {
  // Form data for the login modal
  $scope.loginData = {
    username: null,
    password: null
  };

  $scope.registerData = {
    username: null,
    password: null,
    email: null,
    displayname: null,
    organizationname: null
  };


  // Create the login modal that we will use later
  $ionicModal.fromTemplateUrl('templates/login.html', {
    scope: $scope
  }).then(function(loginmodal) {
    $scope.loginmodal = loginmodal;
  });

  // Create the login modal that we will use later
  $ionicModal.fromTemplateUrl('templates/register.html', {
    scope: $scope
  }).then(function(registermodal) {
    $scope.registermodal = registermodal;
  });

  $scope.testLoginStatus = function() {     
     User.me().then(function(data){
      console.log(data)
      if(!data.result){
        $ionicPopup.alert({
            title: 'Your session has expired',
            template: 'Please login!'
          });
        //go home
        $state.go("app.home");          
        }
    });    
  };

  //open/close routines
  $scope.openLogin = function() {
    $scope.loginmodal.show();
  };
  $scope.closeLogin = function(){
    $scope.loginmodal.hide();
  };
  $scope.openRegister = function() {
    $scope.registermodal.show();
  };
  $scope.closeRegister = function(){
    $scope.registermodal.hide();
  };

  // Perform the login action when the user submits the login form
  $scope.doLogin = function() {
    User.login($scope.loginData).then(function(data){
      if(data.result){
        localStorage.setItem("token",data.result.access_token);
        $state.go("app.inventory");
        $scope.loginmodal.hide();
      }
      else{
        $ionicPopup.alert({
            title: data.message,
            template: 'Please try again!'
          });
        }
    });
  };

  // Perform the register action when the user submits the registration form
  $scope.doRegister = function() {

    User.register($scope.registerData).then(function(data){
      if(data.result){
        //log me in
        $scope.loginData.username = $scope.registerData.username;
        $scope.loginData.password = $scope.registerData.password;
        $scope.doLogin();
        $scope.closeRegister();
        $state.go("app.inventory");
    }
    else{
      $ionicPopup.alert({
        title: status.data.message,
        template: 'Please try again!'
        });
      }
    });
  }
});
```
and the entire services.js file looks like this:
```
angular.module('pntry.services', [])

.factory('API', function() {

  var api_key = 'your-api-key';

    return api_key;


})

.factory('User', function (API) {

var el = new Everlive({
        apiKey: API,
        scheme: 'https',
        token: localStorage.getItem('token')
    });
  return {
    register: function(registerData){          
        return el.Users.register(
            registerData.username, 
            registerData.password, 
              { 
                Email: registerData.email, 
                DisplayName: registerData.displayname,
                OrganizationName: registerData.organizationname
               })
            .then(function (data) {
                return data;
            },
            function(error) {
                return error;
            });               
    },
    login: function(loginData) {                
        return el.Users.login(
            loginData.username,
            loginData.password)
            .then(function (data) {
                return data;
            },
            function(error) {
                return error;
            });
    },
    me: function() {
        return el.Users.currentUser()
            .then(function (data) {
                return data;
            },
            function(error) {
                return error;
            });
    }
  }
});

Hooray!

We have successfully created a frontend for our app with basic security, including register and login routines, connecting an Ionic frontend with Telerik Backend Services. In the process, we created a solid structure for our app with a services layer abstracted away from the controller and view, just like Mom taught us. In the next tutorial, we’ll build the four screens that a food bank can use to request donations and manage inventory.

Header image courtesy of WEBN-TV

Comments

  • Pingback: Ionic + Telerik Backend Services = Much Mobile App Love! | LadeezFirstMedia.com - mobile apps, web development, and more!()

  • Phil

    Great tutorial, was able to follow it very well. One thing though, in the adding organization name to the registration screen, the name of the input is “displayname” and should be “organizationname”.

    • Jen Looper

      Thanks, Phil, for catching this. I’ve made some edits and have a few tweaks to this article, then will start the next. Thanks for following along!

    • Jen Looper

      Thanks, Phil, for catching this. I’ve made some edits and have a few tweaks to this article, then will start the next. Thanks for following along!

  • Phil

    Great tutorial, was able to follow it very well. One thing though, in the adding organization name to the registration screen, the name of the input is “displayname” and should be “organizationname”.

    • Thanks, Phil, for this catch. Editing now and will be posting the second tutorial next week.

  • Richard Lovejoy

    Hi, thanks a lot for the tutorial. I noticed when testLoginStatus() fails it wasn’t taking me back to app.home. To address this I changed the function to accept an ‘event’ param to prevent the default behavior of going to the protected page:

    $scope.testLoginStatus = function(event) {

    var token = localStorage.getItem(“token”);
    User.me(token).then(function(data){
    console.log(data)
    if(!data.result){

    // ADDED CALL TO preventDefault() to stop it from going to protected content!!
    event.preventDefault();

    $ionicPopup.alert({
    title: ‘Your session has expired’,
    template: ‘Please login!’
    });
    //go home
    $state.go(“app.home”);

    }
    });
    };

    The ion-item’s that do the check, pass the $event:

    Donation Requests

    (extra note: this solution doesn’t quite work on Firefox for some reason)

    • Jen Looper

      excellent enhancement, thank you!

  • Pingback: Ionic & Telerik Backend Services: A Healthy Partnership Part 2 -Telerik Developer Network()

  • Michael Duddles

    Fantastic Telerik backend tutorial, Jen. And WAY extra fantastic for integrating Ionic! Coincidentally, I’ve been looking into the best way to handle Ionic app multi-platform builds and backend services functionality, and Telerik absolutely NAILS it. I’m sold. Thanks!

    • Jen Looper

      Thank you, Michael, for your kind comments! Much appreciated. I think using Ionic on the frontend and Telerik on the backend is a match made in heaven. If you develop such an app, do please tell us about it for consideration in our Featured Apps program!

    • Jen Looper

      …also don’t forget there is a ‘part 2’ of this tutorial — complete this app and win!

      • Michael Duddles

        Thanks for mentioning, Jen. Definitely will complete part 2 today.

  • Barry Mc Gettigan

    Hi, thanks for the tutorial. I keep getting an error saying “Failed to load the resource: the server responded with a 403 (forbidden)”. The app worked for me before but it is now broken.