API Helper with Angular and RESTful APIs

API Helper – Connecting with RESTful APIs

Click Here for TL;DR

API Helper with AngularJS and RESTful Endpoints

Okay our short break from new posts is over! With this post I want to talk about integrating your AngularJS SPA with RESTful APIs and best practices.

RESTful APIs

For a quick overview on RESTful APIs. Perhaps you have been involved in a project or working on one alone where integration with the back-end of your app is done via endpoints. Angular offers the $httpProvider which allows you to easily make XHR requests and handle success and error callbacks. Click here for more on Angular’s $httpProvider.

Below is an example of an Angular Factory utilizing the $httpProvider:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
angular.factory('Users', function($q, $http) {
  var getUsers = function() {
  $http.get('http://tld.com/api/v1/users')
    .success(function(data){
      return data;
    })
    .error(function(error){
      return error;
     });
  };

  return {
    getUsers : getUsers
  }

});

You will notice that $q is injected as a dependency for our Service. This gives us access to using promises with out HTTP Requests. Click here for more on the $q Service.

($q) A service that helps you run functions asynchronously, and use their return values (or exceptions) when they are done processing.

We will have an entire blog post on Promises later.

Here is an example of the same service above utilizing the $q Service:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
angular.factory('Users', function($q, $http) {
    var getUsers = function() {
    var d = $q.defer();
        $http.get('http://tld.com/api/v1/users')
            .success(function(data){
                return d.resolve(data);
            })
            .error(function(error){
                return d.reject(error);
            });
            return d.promise;
    };

    return {
        getUsers : getUsers
    }

});

 

HTTP Requests in Services

On a quick side note you will notice that we are making our HTTP Requests from our Services not our Controllers or any other part of our Angular app. When we are creating calls to endpoints it is likely that we will have to call this endpoint from many different controllers and different parts of our application. It makes sense to us to declare this request in one spot and be able to call it from anywhere in our application when needed. Here is an example of utilizing the above Factory in a Controller:

1
2
3
4
5
6
7
8
9
10
angular.controller('ProfileCtrl', function($scope, Users) {
    Users.getUsers()
        .then(function(data){
            $scope.allUsers = data;
        })
        .catch(function(error){
            console.error('There was an error retrieving users: ', error);
        })

    });

We can call this same function Users.getUsers() from any Controller, Directive, etc. that we have in our Angular app.

API Helper

Okay now to the point! Notice the URL we are hitting with our GET request in the above Factory: http://tld.com/api/v1/users. There are several disadvantages to hardcoding this URL in our factory. The most obvious is, what do we do when we get a new API version? Search all of our files and switch api/v1/ to api/v2/? There are many other disadvantages to hardcoding this URL in each requests but… we have a solution! Take a look at this API Helper Service below:

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
angular.factory('APIHelper', () {

    var baseApiUrl = 'http://tld.com/api/v1/',
    endpoints = {
        users : baseApiUrl + 'users'
    };

    function fillUrl(urlFormat, pathParams) {
        var url = urlFormat;
        angular.forEach(pathParams, function (val, name) {
            if (typeof(val) === 'undefined' || val === null || val === '') {
                url = url.replace(RegExp('/:' + name, 'g'), '');
            } else {
                url = url.replace(RegExp(':' + name, 'g'), val);
            }

        });

        return url;
    }

    return {
        endpoints: endpoints,
        fillUrl: fillUrl,
        getLastUrlSegment: function (url) {
            var match = /([^\/]+)\/?$/.exec(url);
            return match && match[1];
        },
        getLastUrlSegments: function (list) {
            return [].concat(list).map(this.getLastUrlSegment);
        }
    };
});

 

First let’s see what utilizing this in a Service looks like and then we will go over each part:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
angular.factory('Users', function($q, $http, APIHelper) {
    var getUsers = function() {
        var d = $q.defer();
        //notice the new argument for our $http.get() method
        $http.get(APIHelper.fillUrl(APIHelper.endpoints.users))
            .success(function(data){
                return d.resolve(data);
            })
            .error(function(error){
                return d.reject(error);
            });
            return d.promise;
        };

    return {
        getUsers : getUsers
    }

});

 

Okay so, when looking at the argument in out $http.get method you’ll notice the first thing you see is APIHelper.fillUrl(). This function takes two arguments, our urlFormat and our pathParams. In the above example we have only entered one argument, urlFormat. You can see this argument is APIHelper.endpoints.users. Looking at our APIHelper js file we can see this translates to http://tld.com/api/v1/users. This is exactly what we had in our first example. The difference here is that now I can reference this path (APIHelper.endpoints.users) in many locations of my app and only need to change it in one spot if something changes with my endpoint URL.

The second argument, pathParams, is used when entering dynamic data into our endpoint. Let’s look at an example of that. In this example we will be hitting an endpoint to get data for a specific user.

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
angular.factory('Users', function($q, $http, APIHelper) {
    var getUsers = function() {
        var d = $q.defer();
        //notice the new argument for our $http.get() method
        $http.get(APIHelper.fillUrl(APIHelper.endpoints.users))
            .success(function(data){
                return d.resolve(data);
            })
            .error(function(error){
                return d.reject(error);
            });
            return d.promise;
        };

        var getUser = function(id) {
            var d = $q.defer();
            $http.get(APIHelper.fillUrl(APIHelper.endpoints.user, {id : id}))
            .success(function(data){
            return d.resolve(data);
            })
            .error(function(error){
            return d.reject(error);
            });
            return d.promise;
            };

        return {
        getUsers : getUsers,
        getUser : getUser
        }

});

Now we have created a user key in our endpoint object that takes a param of ‘id’. Let’s look at updated APIHelper Factory:

1
2
3
4
5
6
7
8
9
angular.factory('APIHelper', () {

  var baseApiUrl = 'http://tld.com/api/v1/',
  endpoints = {
    users : baseApiUrl + 'users',
    user : baseApiUrl + 'users/:id'
  };

...........

Now we have added the user endpoint which has a pathParam of :id. You’ll notice our Users Factory getUser() method takes an argument of id which is then inserted into the object that is our second argument in our fillUrl() method.

Let’s look at what calling this from our Controller looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
angular.controller('ProfileCtrl', function($scope, $stateParams, Users) {

    //In this example we are pulling out user ID from $stateParams, how and where we get the userId is not important for this example, the point is that we have it!
    var userId = $stateParams.userId;

    Users.getUser(userId)
        .then(function(data){
            $scope.currentUser = data;
        })
        .catch(function(error){
            console.error('There was an error retrieving users: ', error);
        })

});

Request – Start to Finish

Okay so lets follow the logic here and understand what’s happening.

  1. I grab my user ID value in my controller and call my Users Service.  I am calling the getUser() method and passing the ID as my argument.
  2. The Users Service getUser() method is invoked, an HTTP Request is made and the argument for the $http.get() method is: APIHelper.fillUrl(APIHelper.endpoints.user, {id : id}))
  3. APIHelper.fillUrl() method is invoked.  The urlFormat is set as http://tld.com/api/v1/user/:id in accordance to your endpoints object.
  4. The object passed as a second argument is evaluated and the id is inserted in place of “:id” in the :id found in the urlFormat.
  5. Our HTTP Request is sent and the data is returned via a promise.

In The End

When making HTTP Requests to endpoints for your RESTful API you should place these calls in Services.

It is a good practice and opens more options for you to use promises with these requests ($q Service).

Hardcoding your endpoint in each request in not scalable, updatable, or maintainable. We solve this by creating an APIHelper Service (see example above)

Having an APIHelper allows you to change your base URL and endpoints in one location having it update across all Services using APIHelper.

The fillUrl method allows you to pass dynamic data (i.e. user id’s) into your endpoints. (See above example).

 

Pro Tip: Use Promises:

Promises allow for asynchronous function calls and give you access to their return values when they are ready. Don’t limit yourself to using these with HTTP Requests! Below is an example of a diolog box directive that uses promises:

Directive JS:

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
angular.directive('dialogPopUp', [ '$log', '$rootScope', 'DialogPopUp',
        function ($log, $rootScope, DialogPopUp) {
            return {
                restrict: 'E',
                scope: true,
                replace: true,
                templateUrl: 'build/global/directives/dialogPopUp.html',
                link: function ($scope, $element, $attrs) {

                    $scope.show = false;

                    $scope.title = '';
                    $scope.message = '';

                    $rootScope.$on('dialog-show', function($event, title, message){
                       $scope.show = true;
                        $scope.title = title;
                        $scope.message = message;
                    });

                    $scope.yes = function(){
                        DialogPopUp.yes();
                        $scope.show = false;
                    };

                    $scope.no = function(){
                        DialogPopUp.no();
                        $scope.show = false;
                    };

                }
            };
        }
    ]);
});

Directive Partial:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div id="dialog-background" ng-show="show">
    <div id="dialog-wrap">
        <div class="dialog-inner-wrap">
            <div class="dialog-box">
                <div class="dialog-yes" ng-click="yes()"><div class="btn btn-blue">Yes</div></div>
                <div class="dialog-no" ng-click="no()"><div class="btn btn-blue">No</div></div>
                <div class="dialog-title">
                    <p>{{title}}</p>
                </div>
                <div class="dialog-message">
                    <p>{{message}}</p>
                </div>
            </div>
        </div>
    </div>
</div>

Dialog Service:

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
angular.factory('DialogPopUp', ['$rootScope', '$q', '$log', function ($rootScope, $q, $log) {

        var current;

        return {
            show : function(title, message){
                if(current){
                    current.reject();
                }
                $rootScope.$emit('dialog-show', title, message);
                current = $q.defer();
                return current.promise;
            },
            yes : function(){
                current.resolve();
                current = null;
            },
            no : function(){
                current.reject();
                current = null;
            }
        };

    }]);
});

Call the directive in your DOM:

1
<dialog-pop-up></dialog-pop-up>

Reference it in your Controller:

1
2
3
4
5
6
7
8
9
angular.controller('myCtrl', function($scope, DialogPopUp){
    DialogPopUp.show("Remember You?", "Would You Like Us To Save Your Password?")
        .then(function(){
            //action for when user clicks yes
        })
        .catch(function(){
            //action for when user clicks no
        });
});

Leave a Reply

Your email address will not be published. Required fields are marked *