David Jones

Angularjs flexible directives with services

Introduction

If you have ever tried to build a custom directive in AngularJS but found it a challenge to make it reusable because it has relied on information that was tricky to get into our directive because the controller is the part of the module that handled it.

Well one way you can trigger DOM events when a piece of information changes is to use a service that is injected into both the controller and directive, with a watcher defined in the directive that is called when the value of the property in your service changes.

As an example we are going to look at the scenario where you want to display a header if the user is logged in.

The controller

Lets assume we have an application set up with register and login functionality. We may have a user controller that looks something like this.

(function () {
    'use strict';

    angular
        .module('myApp.user')
        .controller('UserController', UserController);

    UserController.$inject = ['$scope'];

    function UserController ($scope) {
        $scope.register = register;
        $scope.login    = login;
        $scope.logout   = logout;

        function register () {
            // Register code would be here
        }

        function login () {
            // Login code would be here
        }

        function logout () {
            // Logout code would be here
        }
    }
})();

With the use of a service to keep track of the logged in user client side and some small tweaks to this controller we can make sure we always know whether the user is logged in or not. If we save their user information will also save trips to the server. Lets inject a service called UserService and add some function calls to our login and logout functions.

Heres what our controller could look like.

(function () {
    'use strict';

    angular
        .module('myApp.user')
        .controller('UserController', UserController);

    UserController.$inject = ['$scope', 'UserService'];

    function UserController ($scope, UserService) {
        $scope.register = register;
        $scope.login    = login;
        $scope.logout   = logout;

        function register () {
            // Register code would be here
        }

        function login () {
            // Login code would be here
            UserService.toggleLoggedIn();
        }

        function logout () {
            // Logout code would be here
            UserService.toggleLoggedIn();
        }
    }
})();

The service

The service will be responsible for storing our data and giving us getter and setter methods to access and manipulate. It doesn't need to do anymore than this. Lets looks at the code.

(function () {
    'use strict';

    angular
        .module('myApp')
        .factory('UserService', UserService);

    function UserService () {
        return {
            userloggedIn   : false,
            getUserLoggedIn: getUserLoggedIn,
            toggleLoggedIn : toggleLoggedIn
        };

        function getUserLoggedIn () {
            return this.userLoggedIn;
        }

        function toggleLoggedIn () {
            if (!this.userloggedIn) {
                this.userloggedIn = true;
            } else {
                this.userloggedIn = false;
            }
        }
    }
})();

The directive

Last but not least lets look at the directive. This directive will have a watcher that will watch the value of the userLoggerIn property from our user service and call a function when every a change is detected.

(function () {
    'use strict';

    angular
        .module('myApp')
        .directive('userHeader', UserHeader);

    function UserHeader () {
        return {
            restrict: 'AC',
            controller: UserHeaderCtrl
        };

        UserHeaderCtrl.$inject = ['$scope', 'UserService'];

        function UserHeaderCtrl ($scope, UserService) {
            $scope.UserService = UserService;
            $scope.$watch('UserService.userLoggedIn', toggleUserHeader);

            function toggleUserHeader () {
                var elem = getUserHeaderContainer();
                if (UserService.getUserLoggedIn() === true) {
                    elem.css('display', 'block');
                } else {
                    elem.css('display', 'none');
                }
            }

            // Getters of DOM elements
            function getUserHeaderContainer () {
                return angular.element(document.getElementById('user-header'));
            }
        }
})();

There we have it. We have made the communication between a controller and directive very easy by using a service. Now we can inject the service into any controller we like and the directive will still react as expected. I like this approach as it makes your code elegant while separating concerns.

Thanks for reading.