Pourquoi y a-t-il 5 types de services dans AngularJS ?

Avec les directives, les services sont les composants les plus importants d'AngularsJS. En effet, ce sont eux qu'appellent nos controleurs pour faire le gros du travail, qu'il s'agisse de la partie métier de notre application ou de l'accès à des web services par exemple. Si vous ne savez pas ce qu'est un service, jetez un oeil à ce post sur les factories illustré d'un tuto vidéo. Les factories sont en effet un des types de services les plus utilisés.
Pour revenir à notre sujet des divers types de services, chacun des cinq types est mis en oeuvre par une fonction spécifique :

  • la fonction provider(). La plus bas niveau de toutes. Elle permet de créer des services que l'on peut configurer ensuite depuis la méthode de configuration de notre application (généralement app.config())
  • factory(). La plus utilisée. C'est un wrapper autours de provider(). Ce wrapper et les suivants ne permettent plus de configurer un service après coup.
  • service(). Wrapper autours de la fonction factory(), qui elle-même utilise provider(). On l'utilise en générale quand une application angular dispose déjà de classes JavaScript (avec constructeurs) que l'on veut réutiliser. Avoir appelé "service" un des cinq types de services est source de confusion, je vous l'accorde. C'est un peu comme appeler "pizza" un type de pizza, ou "fils" un de vos fils. Mais AngularJS étant un excellent framework, on peut passer à ses créateurs cette bizarrerie.
  • value(). Wrapper de factory(). Utilisée à la place de factory() quand on n'a pas besoin de passer de paramètres à un service, c'est à dire que l'on n'a pas besoin d'injecter de module à une factory.
  • constant(). Différente de toutes les autres. C'est l'exception, en ce qu'elle n'appelle pas factory(), ni provider(). Elle se contente d'enregistrer un service qui sera injectable dans des controleurs par exemple. Très utile pour créer des constantes de configuration par exemple. Un service de type 'constant' peut en outre être injecté dans un module de configuration.

Les services sont conçus pour être injectés dans d'autres composants de votre application. Des éléments qui savent comment créer d'autres éléments injectables sont des providers. C'est le service "$provide", fournit par angular, qui comporte la fonction "provider()" permettant de créer un service. $provide créé donc un provider qui contient une fonction utilisée pour créer un service. C'est compliqué exprimé de la sorte. Mais tout ceci se résume à :

$provide.provider()

Pour créer le service toto :

$provide.provider('toto', function(){

});

Les autres types de services (à l'exception de constant()) sont de simples wrappers de cette fonction.
L'exemple ci-dessus a créé le service toto, dont le provider est totoProvider (angular ajoute le suffixe 'Provider').
Un service doit posséder une propriété $get:

$provide.provider('toto', function(){
  this.$get = function(){

  }
})

La fonction assignée à $get sera appelée par angular pour créer notre service. Ce service sera représenté par l'objet retourné par $get :

$provide.provider('toto', function(){
  this.$get = function(){
    var bla = 'azerty';
    return {
      bla: bla
    }
  }
})

Pour utiliser ce service dans un controleur, il suffit de l'injecter :

app.controller('ExampleController', function($scope, toto){  
  $scope.bla = toto.bla;
});

Utiliser directement provider()

Angular est ainsi fait que l'on peut appeler directement provider() depuis notre application :

app.provider('toto', function(){  
  this.$get = function(){
    var bla = 'azerty';
    return {
      bla: bla
    }
  }  
});

Enfin, pour que la complexicité de la fonction plus bas niveau qu'est provider() apporte quelque chose de plus que la plus simple fonction factory(), voici un exemple de configuration de service :

app.provider('toto', function(){  
  this.$get = function(){
    var bla = 'azerty';
    if(doExtraStuff){
      console.log("Extra stuff done");
    }
    return {
      bla: bla
    };
  };

  var doExtraStuff = false;
  this.setDoExtraStuff = function(value){
    doExtraStuff = value;
  };  
});


app.config(function(totoProvider){  
  totoProvider.setDoExtraStuff = true;
});

Il est ainsi possible de changer la valeur d'une variable (dans notre cas doExtraStuff) grâce à un setter - que nous avons appelé setDoExtraStuff - depuis app.config(), ce qui aura pour conséquence de faire exécuter à notre service plus au moins de travail.

Voilà. Si vous n'avez pas compris grand chose ou pas besoin du niveau de contrôle supplémentaire qu'apporte un service provider, ne vous faites pas de soucis : continuez à utiliser factory (et constant). Au moins, maintenant, vous savez ce qu'il se passe en coulisses.