Creating a Polling Application Using AngularJS and Laravel Part 2

Creating a Polling Application Using AngularJS and Laravel Part 2
Creating a Polling Application Using AngularJS and Laravel Part 2

In the previous part of Creating a Polling Application using AngularJS and Laravel tutorial, we had covered:

  • Application Directory Structure.
  • Modules and Dependency Injection.
  • Routing in AngularJS.
  • Views.

In this part of the tutorial, we will cover the following topics:

  • Creating Custom Services in AngularJS.
  • Controllers for the Polling Application.
  • Custom Directives in AngularJS.

Creating The pollService

A Single Page Application usually rely on a remote server to store and retrieve data using Ajax calls. Making such calls directly inside controllers can clutter the code base with unnecessary repetitions of the similar logic. In this kind of situations, injecting a functionality as a service can reduce the size of the controllers and at the same time makes our code more testable and reusable.

In this section, we will create a pollService which will allow our polling application to communicate with the Laravel RESTful API to retrieve and save the data. In AngularJS, we can register and define a service by using one of the following three methods of Module API:

  • factory('serviceName',function | getter) – can be used to create services with complex creation logic. The first argument is the name of the service and the second argument is a function that should return an object defining the service.
  • service('serviceName',Constructor) – can be used to create service with simple creation logic. The first argument is the name of the service and the second argument is a constructor function which is used by the Angular to create an instance of the service.
  • provider('serviceName',Constructor | Object) – we can use this method to create configurable services. The first argument is the name of the service. The second argument can be a constructor function or an object that implements a getter returning the service instance. The provider method is actually a merger of the above two methods i.e, factory and service — more or less.

Note 1
Since we are only using Laravel for the REST API, mingling the two frameworks together and tricking Laravel into serving Agnular application is useless here. So, we will serve them separately.
For simplicity, we will use the following commands to serve the Laravel RESTful API and AngularJS application respectively:

$ cd path_to_laravel_installation
$ artisan serve --port 8000

and

$ php -S localhost:8888 -t path_to_angularjs_application_folder

Note 2
The RESTful API will have the following end points:

  • GET http://localhost:8000/api/polls/{page} – will return a JSON encoded list of polls.
  • GET http://localhost:8000/api/poll/{id} – will return a JSON encoded poll.
  • POST http://localhost:8000/api/poll/{id}/option – will allow us to save the user’s selected choice.
  • GET http://localhost:8000/api/stats/{id} – will return JSON encoded stats of a poll.

Here is the code that registers and define our pollService:

// file: js/services.js

pollsApp.factory('pollService', function ($http, $q) {
        return {
            getData: function (route, param) {
                var defer = $q.defer();
                $http.get('http://localhost:8000/api/' + route + '/' + param).success(function (data) {
                        defer.resolve(data);
                    }
                ).error(function () {
                        defer.reject('An error has occurred :(');
                    }
                );
                return defer.promise;
            },
            postData: function (id, data) {
                var defer = $q.defer();
                data = $.param(data);
                $http.post('http://localhost:8000/api/poll/' + id + '/option', data,
                    {'headers': {
                        'Content-Type': 'application/x-www-form-urlencoded,charset=UTF-8'
                    }}).
                    success(function (data) {
                        defer.resolve(data);
                    }
                ).error(function () {
                        defer.reject('Cannot post data to the server :(');
                    }
                );
                return defer.promise;
            }
        };
    }
);

The pollService defines two methods: getData and postData. Both of these methods utilize the $http service to make Ajax calls to the RESTful API and return future objects i.e, promises.

To create future objects / promises, we are using the Angular deferred API $q. A successful $http call is notified by using the defer.resolve() method call on the deferred object. Similarly, a failure is notified by using the defer.reject() method call. Please refer to the $q service documentation here for further details.

One of the advantages of using promises, is the flawless error handling mechanism they provide for asynchronous calls.

Creating Controllers for the Application

We use controller('ControllerName', Constructor) method to register a controller class with the module. The first argument is the name we want to use for the controller and the second argument is the constructor function for the controller object.

Here is the code for the IndexController:

// file: js/controllers.js

pollsApp.controller('IndexController', function ($scope, pollService) {
    $scope.polls = [];
    $scope.alert = {showAlert: false, alertClass: 'success', msg: ''};
    $scope.dataAvailable = true;
    var pageNumber = 1;

    $scope.loadPolls = function () {
        pollService.getData('polls', pageNumber).then(function (data) {
                if (data.polls.length < 5) {
                    $scope.dataAvailable = false;
                }
                angular.forEach(data.polls, function (value) {
                    $scope.polls.push(value);
                });
                pageNumber++;
            },
            function (error) {
                $scope.alert = {showAlert:true, msg: error, alertClass: 'danger'};
            }
        );
    };

    $scope.loadPolls();
});

Inside the loadPolls() method, we are calling the getData() method of the pollService with the route as polls and a pageNumber. Since getData() returns a promise, the then() method is appended to the chain which takes two arguments: a success handler and an error handler. The success handler will be triggered in response to a defer.resolve call. In that case, we are pushing the received data into the polls property of the $scope object. Similarly, a rejected promise will trigger the error handler and an alert containing the error message will be shown to the user.

In the IndexController, we are also providing the user with a simple one-way pagination by incrementing the pageNumber variable on each method call.

The code for the PollController:

// file: js/controllers.js

pollsApp.controller('PollController', function ($scope, $routeParams, pollService) {
    $scope.poll = {};
    $scope.alert = {showAlert: false, alertClass: 'success', msg: ''};

    pollService.getData('poll', $routeParams.id).then(function (data) {
            $scope.poll = data.poll;
        },
        function (error) {
            $scope.alert = {showAlert: true, msg: error, alertClass: 'danger'};
        }
    );

    $scope.save = function () {
        if ($scope.poll.option) {
            pollService.postData($routeParams.id, {option: $scope.poll.option}).then(function (data) {
                    $scope.alert = { showAlert:true, msg: angular.fromJson(data).mesg, alertClass: 'success' };
                },
                function (error) {
                    $scope.alert = {showAlert:true, msg: error, alertClass: 'danger'};
                });
        } else {
            $scope.alert = {showAlert:true, msg: 'Select something please!', alertClass: 'warning'};
        }
    };

});

The first call, pollService.getData inside the constructor of the PollController will retrieve a single poll with its options.

The save() method first checks if a selection has been made by the user. In that case, we will post the selected option to the server by calling postData() method on the pollService. In the case of a success, we will alert the user with a success message from the server.

The code for the StatsController:

// file: js/controllers.js

pollsApp.controller('StatsController', function ($scope, $routeParams, pollService) {
    $scope.id = $routeParams.id;
    var tempOptions = [];
    var tempStats = [];
    $scope.alert = {showAlert: false, alertClass: 'success', msg: ''};

    pollService.getData('stats', $routeParams.id).then(function (data) {
            //incomming data format:[{"option":"Kate","stats":"2"},{"option":"Sublime Text","stats":"7"},{"option":"Vim","stats":"4"}]
            angular.forEach(data.stats, function (value) {
                tempOptions.push(value.option);
                tempStats.push(value.stats);
            });
        },
        function (error) {
            $scope.alert = {showAlert:true, msg: error, alertClass: 'danger'};
        }
    );
    $scope.data = {
        series: tempOptions,
        data: [
            {
                y: tempStats
            }
        ]
    };
    $scope.chartType = 'bar';
    $scope.config = {
        labels: false,
        legend: {
            display: true,
            position: 'right'
        }
    };
});

In the success handler of the getData() method, incoming data is parsed to get the available options and stats. This data is then assigned to the data property of the $scope object. ac-chart directive will use this data property to plot the bar graph. The rest are the simple configurations for the ac-chart directive.

Creating a Custom Directive

Custom directives in AngularJS allow us to extend the HTML vocabulary specific to an application. By using custom directives we can define the behavior of an element or a group of elements according to the application’s needs. In particular, directives allow us to: manipulate the DOM, reduce repetitions in the markup, use a declarative approach to map the application’s logic on the Document Object Model ( DOM ).

Let us first see the code for the poll-alert directive and then we will examine the code:

// file: js/directives.js

pollsApp.directive('pollAlert', function () {
    return {
        restrict: 'E',
        scope: {
            showAlert: '=',
            alertMessage : '=',
            alertClass: '='
        },
        template: '<div class="alert alert-{{alertClass}}" ng-show="showAlert">' +
            '<button type="button" class="close" ng-click="close()">&times;</button>' +
            '<p>{{alertMessage}}</p>' +
            '</div>',
        link: function(scope) {
            scope.close = function() {
                scope.showAlert = false;
            };
        }
    };
});

We use directive('directiveName', factory function) method to create and register a custom directive with the module. The directive() method takes two arguments: a camel cased directive name and a factory function returning the object that defines the directive. Angular normalizes the camelCased directive names internally as camel-cased. So, the name pollAlert will be used as poll-alert inside the HTML templates.

Now, let us take a look at the properties of the directive object returned by the factory:

  • restrict – defines the declaration style for the directive: ‘E’ is for element, ‘A’ for attribute, ‘C’ for class and ‘M’ is for a comment.
  • scope – defines the type of the scope. The available options are:
    • scope: false – use the existing scope.
    • scope: true – creates a new scope that inherits from the enclosing controller’s scope.
    • scope: {/* attributes */} – creates an isolated scope. We use this option for creating reusable components ( directives ). The = sign can be used if the properties of the scope has the same name as the bindings.
  • template – HTML template which will be appended to the directive element.
  • link – defines the DOM listeners. Angular also uses the link functions to keep the view synchronized with the model.

For complete details on available options please refer to the documentation page here.

Download Source Code

That is all folks! In the next and last part of this tutorial we will create the RESTful API using the Laravel. Regards!

3 thoughts on “Creating a Polling Application Using AngularJS and Laravel Part 2”

  1. Nice Tut. Well written, particularly how you used practical use-cases without going down a rabbit hole with them and stayed on point of exploring the overall usage patterns of Angular.

  2. Hi Tut,

    Thanks for your code. I am studying MEAN and this is a good introduction to an exercise I would like to implement. I will make a reference to your blog and comment it between the rest of students.

    Regards

Leave a Reply

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

Time limit is exhausted. Please reload CAPTCHA.