Service angular ES6

Voici une façon d'écrire des service angular en ES6 qui permet de bien mieux comprendre la différence entre un service et une factory. Jusqu'à aujourd'hui, j'écrivais mes factory à l'aide d'une class et mes service à l'aide d'une function. J'étais obligé d'utiliser une function et pas une arrow-function afin de générer un scope propre à l'objet instancié. Du coup, je me retrouvais avec des factory super hype en es6 et des services en vieux ES5 :(

Avant

angular.module('exemple')  
.service('MyCoolService', function ($q, $http) {
  this.doSomething = () => {
    let deferred = $q.defer();
    $http.get('/pwet')
      .success(data => deferred.resolve(data))
      .error(err => deferred.reject('You failed'));
    return deferred.promise;
  };
})
.factory('MyCoolThing', ($q, MyCoolService) => class MyCoolThing {
  doSomething () {
    let deferred = $q.defer();
    MyCoolService.doSomething()
      .then(data => deferred.resolve(data))
      .catch(err => deferred.reject(err));
    return deferred.promise;
  }
});

Si je rédigeais mon service avec une arrow function comme la factory, this vaudrait window car arrow function conserve le scope parent. C'est pourquoi j'étais obligé d'utiliser une function. Mais quand on réfléchi à la façon dont fonctionnent services et factory, on trouve rapidement la solution !

Après

Une factory est une fonction faisant usage de constructeur directement renvoyé lors de l'injection, tandis que le service est instancié avant d'être injecté. Si j'injecte MyCoolThing, c'est le constructeur de la classe MyCoolThing qui sera renvoyé. Si j'injecte MyCoolService, MyCoolService sera d'abord instancié une fois puis cette instance unique sera injectée à chaque fois qu'elle sera demandée. On peut alors rédiger son service de la sorte :

angular.module('exemple')  
.service('MyCoolService', class MyCoolService {
  constructor ($q, $http) {
    this._$q = $q;
    this._$http = $http;
  }

  doSomething() {
    let deferred = this._$q.defer();
    this._$http.get('/pwet')
      .success(data => deferred.resolve(data))
      .error(err => deferred.reject('You failed'));
    return deferred.promise;
  }
});

Une fois transpilé, le constructor de la classe deviendra une function qui sera donc l'argument passé à service(). C'est bien cette fonction qui servira à injecter les dépendances. On peut alors écrire des méthodes de classes pour rendre son code plus présentable avec le seul inconvénient qu'il faut alors placer les dépendances dans des attributs de la classe. Vivement les privates :x

Problèmes avec ngAnnotate

Je n'avais pas pensé à ça quand j'ai rédigé ce post, quand on build l'application et qu'on uglify le code, ngAnnotate ne fait plus correctement son travail :( On va s'en sortir en rédigeant son code ainsi :

angular.module('exemple')  
.service('MyCoolService', ($q, $http) => {
  class MyCoolService {
    doSomething() {
      let deferred = $q.defer();
      $http.get('/pwet')
        .success(data => deferred.resolve(data))
        .error(err => deferred.reject('You failed'));
      return deferred.promise;
    }
  }
  return new MyCoolService();
});

C'est moins joli :(

Hadrien

Hi, I'm a french Javascript Lead Developer, Web Architect from Toulouse, France. I've worked for 12 years for many projects with YUI, AngularJS, Aurelia.io and now React and React native.

Toulouse, France https://hadrien.eu