Modular Angular

Providers, services, factories.
Resources, wrappers, modules.

Basics first

How does the angular invoke services?

$injector.invoke example

var app = angular.module("app");

app.controller("UsersController", function UsersController($scope, UsersResource) {
  $scope.users = UsersResource.getAll();
});
  1. Angular runs `$injector.invoke()` on the `UsersController` function
  2. $injector parses the function's arguments into a string array: ["$scope", "UsersResource"]
  3. For every passed argument, it runs `getService(serviceName)`: which initializes the service OR if already initialized, gets it from the cache
  4. Finally, runs the `UsersController` function with injected services returns the function's value: `return fn.apply(context, services);`

Example

app.factory("CarsResource", function(){
  return {
    getAll: function() {
      return [{name: "Ferrari"}];
    }
  };
});

// ... somewhere in the controller
app.controller("CarsController", function ($scope, CarsResource) {
  $scope.cars = CarsResource.getAll();
});

Part I

Providers, services, factories.

What are they and what is the difference?

Let's read the docs... (RTFM)

Source: docs.angularjs.org

An Angular service is a singleton object created by a service factory.

These service factories are functions which, in turn, are created by a service provider.

The service providers are constructor functions. When instantiated they must contain a property called $get, which holds the service factory function.

Did you mean recursion?

... let's start from the code

and translate the docs word by word

"The service providers are constructor functions. When instantiated they must contain a property called $get, which holds the service factory function"

Provider example

app.provider("Greeter", function GreeterFactoryProvider() {
  // $get method initializes the factory
  this.$get = function GreeterFactory() {
    return {
      sayHello: function() {
        return "hello";
      }
    };
  };
});

// USAGE
app.run(function(Greeter){
  console.log(Greeter.sayHello());
});

Provider example with configuration

app.provider("Greeter", function GreeterFactoryProvider() {
  // private, local variable 
  var options = { locale: "en" };
  
  // public method available only in the .config() phase
  this.setLocale = function(locale) {
    options.locale = locale;
  };
  
  // $get method initializes the factory
  this.$get = function GreeterFactory() {
    return {
      sayHello: function() {
        return options.locale == "en" ? "hello" : "hola";
      }
    };
  };
});

// USAGE
app.config(function(GreeterProvider){
  GreeterProvider.setLocale("es");
});

app.run(function(Greeter){
  console.log(Greeter.sayHello());
});

Provider example - more usable

app.provider('myFacebook', function() {
  // private, local variable
  var initParams = {};
  
  // public method available only in the .config() phase
  this.init = function (appId) {
    initParams.appId = appId;
    FB.init(initParams);
  };
  
  // $get: defines the `myFacebook` singleton service
  this.$get = function ($q) {
    return {
      // public method, available as `myFacebook.login()` in the controllers
      login: function () {
        var deferred = $q.defer();
        FB.login(deferred.resolve)
        return deferred.promise;
      }
    }
  };
};

// Usage
// Let's configure the myFacebook service
// Beware:
// - myFacebookProvider is only visible in the app.config phase.
// - we cannot access `myFacebook` service here yet.
app.config(function(myFacebookProvider){
  myFacebookProvider.init("123");
});

// Finally, let's use the myFacebook service.
app.controller("LoginController", function(myFacebook){
  $scope.login = function() {
    myFacebook.login();
  };
})

"Service factories are functions created by a service provider"

// source code taken straight from angular.js
function factory(name, factoryFn) { 
  return provider(name, { 
    $get: factoryFn 
  }); 
}

Factory is just a provider with $get method and no configuration.

Factory example

app.factory("Auth", function AuthFactory(){
  var currentUser;
  function loginAsAdmin() {
    currentUser = {id: 1};
  }
  
  return {
    login: function login(name) {
      if (name == "obama") {
        loginAsAdmin();
      }
    }
  };
});

// USAGE
app.controller("FooController", function(Auth) {
  $scope.login = function() {
    Auth.login($scope.username);
  };
});

When injecting, Angular calls the AuthFactory function and stores it's value.

AuthFactory is initialized only once, on first demand - after that, the previous [cached] value gets retrieved.

"Service (...) is created by a service factory"

// source code taken straight from angular.js
function service(name, constructor) {
  return factory(name, ['$injector', function($injector) {
    return $injector.instantiate(constructor);
  }]);
}

service() is just a wrapper around the factory() method

You can easily achieve .service() effect by using .factory() method.

There are no other differences.

Service vs Factory

angular.factory('myFactory', myFactoryFunction);
angular.service('myService', myServiceFunction);

// Factory: the function that you write will be invoked:
myInjectedFactory  <---  myFactoryFunction()

// Service: the function that you write will be new-ed:
myInjectedService  <---  new myServiceFunction()

The only difference between the service and the factory is the $injector.instantiate()

// source code taken straight from angular.js
function instantiate(Type, locals) {
  var Constructor = function() {},
      instance, returnedValue;
      
  Constructor.prototype = Type.prototype;
  instance = new Constructor();
  returnedValue = invoke(Type, instance, locals);
  
  return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
}
In short: instantiate(Car) will return a new Car instance,
UNLESS the Constructor method (function Car(){}) returns something: then it returns the returned object.

These are the same

function AuthFactory() {
  var currentUser;
  function loginAsAdmin() {
    currentUser = {id: 1};
  }
  
  return {
    login: function login(name) {
      if (name == "obama") {
        loginAsAdmin();
      }
    }
  };
}

These are not the same

function AuthClass() {
  var currentUser;
  
  function loginAsAdmin() {
    currentUser = {id: 1};
  }
  
  this.login = function login(name) {
    if (name == "obama") {
      loginAsAdmin();
    }
  };
}

"Service is a singleton object"

(and so is the Factory)

var uniqueId = 0;

app.service("Car", function Car() {
  this._id = ++uniqueId;
  this.getId = function getId() {
    return this._id;
  };
});

$injector.invoke(function(Car) {
  console.log(Car.getId()); // => 1
});
$injector.invoke(function(Car) {
  console.log(Car.getId()); // => 1
});

Factory's function is initialized only once, and then its' value is stored in the cache.

Other useful provider-like methods:

// function value(name, value)
// .value() is just a factory returning given value.
app.value('movieTitle', 'The Matrix');

// function constant(name, value)
// Constants can be injected everywhere,
// but can never be changed.
app.constant("ENV", "staging");

// function decorator(serviceName, decorFn)
app.config(function ($provide) {
  // Use decorator, to replace existing providers.
  $provide.decorator('movieTitle', function ($delegate) {
    return $delegate + ' - starring Keanu Reeves';
  });
});
All compared together

When to use them?

Real-life examples

ngFacebook - a wrapper for FB api


      

Resources (Repositories?) - to handle data management


      

Resources (Repositories?) - to handle data management


      

Form objects - to handle form validation, data handling and submitting


      

PromiseFactory - small, often usable class function


      

ngCordova - wrappers for external APIs


      

Last thoughts

Name your objects consequently

Good

app.factory("FormFactory", function() {
  return function FormFactory() { ... };
});

app.factory("UserFormFactory", function(FormFactory) {
  return function UserFormFactory() { ... };
})

(append `...Factory` to inherited Class factory functions)

Wrong

app.provider("myFacebookProvider", function() { ... });
app.config(function(myFacebookProviderProvider) { ... });
app.run(function(myFacebookProvider) { ... })

(Angular automatically appends `Provider` to providers in .config() phase)

Good

app.provider("myFacebook", function() { ... });
app.config(function(myFacebookProvider) { ... });
app.run(function(myFacebook) { ... })

OK (but not necessary):

app.service("UserSettingsService", function() {
  // some singleton class with global user defined settings
});
// could be as well just "UserSettings", if it's explicit enough

app.service("FlashMessagesService", function() {
  // .set and .getAll() methods
});
// IMHO better than just "FlashMessages"
// "FlashService" would be even better

Always remember: better be explicit, than non-readable.

Even better: be consequent.

Move logic from controllers to external services/factories

Move logic from controllers to external services/factories

Examples

Watchout for circular dependency injection

app.service("NotificationService", function(UserSettings){
  // ...
  if (UserSettings.allowNotifications()) sendNotifications();
});

app.service("UserSettings", function(NotificationService){
  // ...
  this.enableNotifications = function() {
    NotificationService.sendAll();
  };
});

// Angular will trigger error in the console:
// "Uncaught Error: [$injector:cdep] Circular dependency found:
// UserSettings <- NotificationService <- UserSettings"

Organize your code

Links

That's all, folks!

Any questions?