Service Patterns in AngularJS

by Jeremy Mason


Download the slides

https://github.com/jeremyalan/AngularJS-ServicePatterns

Contact me

jeremyalan@gmail.com
Twitter @jeremyalan

Sponsors

Quick Poll

Angular components

  • Modules
  • Services
  • Controllers
  • Directives
  • Filters

Modules

Combines related components of your application
into a single, reusable component

  • All other components reside within modules
  • Modules can depend on other modules
  • Delayed initialization
  • Modules are NOT loaded async
    - still need RequireJS or similar

Modules

Create a new module
angular.module('myModule', ['dep1', 'dep2']);
Retrieve an existing module
angular.module('myModule');

// Order of execution matters!
// This fails if module has not been declared yet
Initialize a module
angular.module('myModule')
    .config(function () { ... })
    .run(function () { ... })
More on this later...

Services

Custom objects that are shared across your application

  • Singletons
  • Injectable > let's dig a little deeper...

Dependency Injection

Allows services to be passed into a dependent module,
such as a controller, directive, or another service

Why should we care?

  • Supports testability
  • Discourages the use of global variables
  • Allows for complex configuration scenarios
    More on this later...

Dependencies

Inline Declaration

  • Angular calls .toString() on constructor function
  • DOES NOT WORK with minified code
function MyService($http) {
    ...
};

angular.module('myModule')
    .service('$myService', MyService);
    

Dependencies

Array arguments

  • Uses string values to counteract minification
  • Similar syntax to other module components (directives)
  • Class declaration tightly coupled to module registration
function MyService($http) {
    ...
};

angular.module('myModule')
    .service('$myService', ['$http', MyService]);

Dependencies

$inject

  • Use string values to counteract minification
  • $inject is tied to class declaration, not module registration
function MyService($http) {
    ...
};
MyService.$inject = ['$http'];

angular.module('myModule')
    .service('$myService', MyService);

$injector

Angular's implementation of the Service Locator pattern
  • Created automatically during app initialization
  • Can be used at runtime against any function
    $injector.invoke(function (dep) { ... });
  • Or, create your own!
    var injector = angular.injector(['ng', 'mod1']);

Services

  • Constant/Value
    - Pass an instance
  • Service
    - Pass a constructor function
  • Factory
    - Pass a function that returns an instance
  • Provider
    - Pass a function that returns a "service object"
  • Decorator
    - Transform or replace an instance during creation

Constant

module.constant(...)
angular.module('api')
    .constant('$apiKey', '1234567890abcdef');

Service

module.service(...)
function PersonService($http) {
    this.create = function (info) {
        return $http.post('/api/person', info);
    };
}

angular.module('api')
    .service('$person', PersonService);

Factory

module.factory(...)
angular.module('api')
    .factory('$person', function ($http) {
        return {
            create: function (info) {
                return $http.post('/api/person', info);
            }
        };
    });

Provider

module.provider(...)
angular.module('api')
    .provider('$person', function () {
        return {
            $get: function ($http) {
                return {
                    create: function (info) {
                        return $http.post('/api/person', info);
                    }
                };
            }
        };
    });

Provider

  • Strong separation between configuration and consumption
  • Creates two injectable instances
    $myService, $myServiceProvider
  • Providers can be injected into config callbacks
    // Fail!
    angular.module('...')
        .config(function ($myService) { ... });
        
    // OK!
    angular.module('...')
        .config(function ($myServiceProvider) { ... });
        
    angular.module('...')
        .run(function ($myService) { ... });
        
    angular.module('...')
        .run(function ($myServiceProvider) { ... });

Provider

Example
angular.module('api')
    .provider('$person', function () {
        var personCache = { };
        var provider = { cache: false };
        
        provider.$get = function ($q, http) {
            return {
                fetch: function (id) {
                    
                    if (provider.cache && personCache[id]) {
                        return $q.when(personCache[id]);
                    }
                    
                    return $http.get('/api/persons/' + id)
                        .then(function (result) {
                            
                            if (provider.cache) {
                                personCache[id] = result.data;
                            }
                            
                            return result.data;
                        });
                }
            };
        };
        
        return provider;
    });
    
angular.module('myApp')
    .config(function ($personProvider) {
        $personProvider.cache = true;
    });

Decorator

module.decorator(...)
angular.module('api')
    .decorator('$person', function ($delegate) {
        var newInstance = {};
        
        // ...
        
        return newInstance;
    });
  • Use $delegate to inject the service

Decorator

Modify a service using decorator
angular.module('api')
    .decorator('$person', function ($q, $delegate) {
        var personCache = {};
        
        var fetch = $delegate.fetch;
        $delegate.fetch = function (id) {
            if (personCache[id]) {
                return $q.when(personCache[id]);
            }
            
            return fetch(id);
        };
        
        return $delegate;
    });

Decorator

Replace a service using decorator
angular.module('api')
    .decorator('$person', function ($q, $delegate) {
        var personCache = {};
        
        return {
            fetch: function (id) {
                if (personCache[id]) {
                    return $q.when(personCache[id]);
                }
                
                return $delegate.fetch(id);
            }
        };
    });

Controllers

module.controller(...)
function CreatePersonCtrl($scope, $person) {
    $scope.model = {};
    
    $scope.create = function () {
        var info = {
            FirstName: $scope.model.firstName;
            LastName: $scope.model.lastName;
        };
        
        $person.create(info)
            .then(...);
    };
}

angular.module('app')
    .controller('CreatePersonCtrl', CreatePersonCtrl);

Thanks again to our sponsors!

Thank you for listening!

Questions?