La création de services avec AngularJS

Publié le 13 mai 2014 par Numa Claudel | front

Cet article est publié sous licence CC BY-NC-SA

Bonjour à tous, nous avons précédemment fait une petite introduction a AngularJS, avec certains concepts permettant de commencer à l’utiliser. Pour continuer sur ce schéma, je vous propose de découvrir ensemble comment créer et se servir des services dans une application AngularJS.

 Pour commencer

Cinq méthodes: provider, factory, service, value et constant. Ce sont ces méthodes de module qui permettent de créer un service:

  • provider est la manière la plus configurable de créer un service
  • factory, service et value délèguent à la méthode provider avec une configuration standard
  • constant se comporte de manière plus spécifique puisque chaque service constant est son propre provider

value et constant sont des services plutôt destinés à contenir des valeurs/objets comme leur nom l’indique, tandis que factory et service sont juste des raccourcis de la méthode provider et permettent donc de créer des services sans se soucier de leur configuration.

La différence entre service et factory ? Voici ce que l’on trouve sur la documentation d’AngularJS:

Factory and Service are the most commonly used recipes. The only difference between them is that Service recipe works better for objects of custom type, while Factory can produce JavaScript primitives and functions.

Puisque la méthode factory se comporte comme une fonction JavaScript habituelle, on peut simuler des variables privées qui permettent de ne pas exposer toute la logique si ce n’est pas nécessaire. C’est donc généralement cette méthode qui est préférée pour la création de service.

 Un service de logs

J’ai pensé, pour illustrer ce billet, a un service de génération de logs. Créer des logs avec JavaScript est déjà possible, alors nous allons englober le système existant pour l’enrichir. Commençons par poser la base de ce dont nous avons besoin :

<!DOCTYPE html>
<html ng-app="serviceApp">
  <head>
  </head>
  <body>
    <div ng-controller="indexCtrl">

    </div>

    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js"></script>
    <script src="app.js"></script>
    <script src="services.js"></script>
  </body>
<html>

Un fichier index.html dans lequel nous chargeons AngularJS bien sûr, un fichier JavaScript pour l’application et le fichier qui va contenir notre service.

Le code du fichier app.js:

var serviceApp = angular.module('serviceApp', []);

serviceApp.controller('indexCtrl', ['$scope', function($scope) {

}]);

Et enfin notre service:

var services = angular.module('services', []);

services.factory('Logger', function() {
  var logger = {};

  var active = false; // par défaut le service est désactivé

  // Retourne la date et l'heure courante
  var currentDateTime = function() {
    var currentdate = new Date();
    var datetime = currentdate.getDate() + '/' +
                   (currentdate.getMonth() + 1) + '/' +
                   currentdate.getFullYear() + ' ' +
                   currentdate.getHours() + ':' +
                   currentdate.getMinutes() + ':' +
                   currentdate.getSeconds();
    return datetime;
  }

  logger.turnOn = function() {
    active = true;
  };

  logger.turnOff = function() {
    active = false;
  };

  // Retourne le message reçu précédé de la date et de l'heure,
  // avec le niveau d'alerte voulu
  logger.log = function(msg, type) {
    var type = type || '';

    if(console && active) { // si la console de JavaScript existe et que le service est actif
      var message = currentDateTime() + ' - ' + msg;

      switch (type) {
      case 'e':
        console.error(message);
        break;
      case 'w':
        console.warn(message);
        break;
      case 'd':
        console.debug(message);
        break;
      default:
        console.log(message);
        break;
      }
    }
  };

  return logger;
});

Ce service contient un paramètre et une méthode privée, et expose les méthodes: turnOn, turnOff et log.

Il faut ajouter en dépendance de l’application le module services et en dépendance du contrôleur notre service:

var serviceApp = angular.module('serviceApp', ['services']);

serviceApp.controller('indexCtrl', ['$scope', 'Logger', function($scope, Logger) {

}]);

Le contrôleur accède maintenant à toutes les fonctions visibles de notre Logger:

serviceApp.controller('indexCtrl', ['$scope', 'Logger', function($scope, Logger) {
    Logger.turnOn(); // On active le logger
    Logger.log('Page chargée !'); // Log au chargement de la page
}]);

Le service en l’état est très basique. Que pensez vous de l’enrichir en récupérant des informations sur l’utilisation de l’application ou sur l’utilisateur ? Pour cela, nous allons avoir besoin d’un service qui s’occupera de contenir les données, et puisque ce service sera exclusivement destiné à cette tâche, alors la méthode de module value est toute indiquée:

services.value('Current', {
  user: {
    firstname: 'Numa',
    lastname: 'Claudel',
  },
  collectedData: {},
  usedActions: []
});

Complétons notre Logger:

services.factory('Logger', ['Current', function(Current) {
  .
  .
  .
  // Enregistre quelques informations sur l'utilisateur
  logger.storeUserInfos = function() {
    Current.collectedData.screen = { width: screen.width, height: screen.height, screenColor: screen.colorDepth };
  };

  // Enregistre l'action utilisée par l'utilisateur et à quelle heure
  logger.storeUserAction = function(name) {
    Current.usedActions.push({ action: name, at: currentDateTime() });
  };

  return logger;
}]);

Vous remarquerez que notre Logger dépend maintenant du service Current que nous avons fraîchement créé. Maintenant complétons le contrôleur pour qu’il utilise les nouvelles fonctions de notre Logger:

serviceApp.controller('indexCtrl', ['$scope', 'Logger', 'Current',
  function($scope, Logger, Current) {
    Logger.turnOn(); // On active le logger
    Logger.log('Page chargée !'); // Log au chargement de la page

    $scope.user = Current.user;
    Logger.storeUserInfos(); // récupère et stocke quelques données sur l'utilisateur

    $scope.actionExemple = function() {
      Logger.storeUserAction('actionExemple'); // stocke l'action avec l'heure courante

      // vider les données de temps en temps
      if(Current.usedActions.length >= 10) {
        // => envoyer les données au serveur

        Current.usedActions = [];
      }
    }
  }
]);

Et enfin ajoutons le bouton de l’actionExemple à notre vue:

<div ng-controller="indexCtrl">
   peux tu cliquer sur ce bouton ? :
  <button ng-click="actionExemple()">Action</button>
</div>

Vous noterez la présence de la directive ng-click fournie par AngularJS, qui nous permet d’appeler une fonction du contrôleur au clic sur l’élément.

Pour résumer un peu, on peut observer que pour créer un service et l’utiliser il faut:

  • le déclarer avec angular.module('unNomDeModule', [])."leType" ou “leType” sera une des 5 méthodes de définition de service
  • que la fonction qui définie le service retourne un objet le contenant
  • ajouter le module contenant les services en dépendance de l’application
  • ajouter le service en dépendance de la portion de code dans lequel on veut l’utiliser (dans notre cas le contrôleur)

 Ajouter un service de requêtes Ajax

On a très souvent besoin d’aller récupérer ou envoyer des données sur le serveur en Ajax, alors pourquoi ne pas centraliser ça dans un module de services Ajax.

ajax.factory('GetSomeData', ['$http', function($http) {
  return {
    get: function(params, callback) {
      $http.get('/resource/url?' + params).
        success(function(data, status) {
          callback(data, status);
        }).
        error(function(error, status) {
          callback(error, status);
        });
    }
  };
}]);

Une requête Ajax spécifique en GET construite grâce au service $http fournit par AngularJS, dans laquelle je me suis servis d’un des raccourcis de définition de méthode disponible. Utiliser cette requête dans un contrôleur se fera de la sorte: GetSomeData.get('color=blue', function() {}), mais ce n’est qu’un exemple.

Et dans le cas de l’utilisation d’une ressource serveur, faut-il définir toutes les requêtes d’un CRUD ? Heureusement, pour interagir avec un serveur RESTful comme dans le cas de Rails, AngularJS fournit un autre service qui apporte les méthodes nécessaires à ce type d’interactions. Il se nomme $resource du module ngResource:

ajax.factory('User', ['$resource', function($resource) {
  return $resource('/users/:id', { id: '@id' }, { update: { method: 'PUT' } });
}]);

Ce service nous permet de définir une ressource, dans ce cas User, que l’on va pouvoir réutiliser dans n’importe quel contrôleur. A savoir que $resource fournit de base les méthodes: get, save, query, remove et delete, auxquelles nous avons ajouté update. Pour utiliser cette ressource, il faut charger angular-resource et le fichier ajax.js dans notre index.html, ajouter le module ngResource en dépendance de l’application et ajouter User en dépendance de notre contrôleur :

    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js"></script>
    <script src="https://code.angularjs.org/1.2.16/angular-resource.min.js"></script>
    <script src="app.js"></script>
    <script src="services.js"></script>
    <script src="ajax.js"></script>
  </body>
<html>
var serviceApp = angular.module('serviceApp', ['services', 'ajax', 'ngResource']);

serviceApp.controller('indexCtrl', ['$scope', 'Logger', 'Current', 'User',
  function($scope, Logger, Current, User) {
    Logger.turnOn(); // On active le logger
    Logger.log('Page chargée !'); // Log au chargement de la page

    // récupère l'utilisateur voulu
    User.get({ id: 1 }, function(data) {
      Current.user = data;
    });

    // un update des informations de l'utilisateur se fera sous cette forme:
    // User.update(Current.user, function() {});
    .
    .
    .
  }
]);

 Conclusion

Créer des services avec AngularJS permet de garder les contrôleurs éloignés de la logique d’accès aux données, ou de fonctionnements généraux de l’application. Le code d’un contrôleur peut ainsi se concentrer sur la fonctionnalité propre à la portion de vue qui lui est allouée. J’espère vous avoir aidé à y voir un peu plus clair au sujet des services avec AngularJS.

L’équipe Synbioz.

Libres d’être ensemble.