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 private
s :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 :(