1077 votes

Angular.service vs angular.factory

J'ai vu à la fois angular.factory() et angular.service() utilisés pour déclarer des services; cependant, je ne trouve pas angular.service nulle part dans la documentation officielle.

Quelle est la différence entre les deux méthodes?
Lequel devrait être utilisé pour quoi (en supposant qu'ils font des choses différentes)?

25 votes

Possible duplicate de confused about service vs factory

4 votes

J'ai cherché "[angularjs] service factory", mais je me suis aussi souvenu qu'il y avait déjà une question à ce sujet (parce que j'avais envisagé de poser cette question moi-même à un moment donné).

2 votes

Dans une recherche, les crochets carrés signifient-ils une balise?

1274voto

Gil Birman Points 9000
  angular.service('myService', myServiceFunction);
  angular.factory('myFactory', myFactoryFunction);

J'ai eu du mal à comprendre ce concept jusqu'à ce que je me le représente de cette façon :

Service: la fonction que vous écrivez sera instanciée :

  myInjectedService  <----  new myServiceFunction()

Factory: la fonction (constructeur) que vous écrivez sera exécutée :

  myInjectedFactory  <---  myFactoryFunction()

Qu'en faire dépend de vous, mais il y a quelques patterns utiles...

Comme écrire une fonction de service pour exposer une API publique :

function myServiceFunction() {
  this.awesomeApi = function(optional) {
    // calculer des choses
    return awesomeListOfValues;
  }
}
---------------------------------------------------------------------------------
// Injecté dans votre contrôleur
$scope.awesome = myInjectedService.awesomeApi();

Ou utiliser une fonction de factory pour exposer une API publique :

function myFactoryFunction() {
  var aPrivateVariable = "yay";

  function hello() {
    return "hello mars " + aPrivateVariable;
  }

  // exposer une API publique
  return {
    hello: hello
  };
}
---------------------------------------------------------------------------------
// Injecté dans votre contrôleur
$scope.hello = myInjectedFactory.hello();

Ou utiliser une fonction de factory pour retourner un constructeur :

function myFactoryFunction() {
    return function() {
        var a = 2;
        this.a2 = function() {
            return a*2;
        };
    };
}
---------------------------------------------------------------------------------
// Injecté dans votre contrôleur
var myShinyNewObject = new myInjectedFactory();
$scope.four = myShinyNewObject.a2();

Lequel choisir ?...

Vous pouvez accomplir la même chose avec les deux. Cependant, dans certains cas, la factory vous offre un peu plus de flexibilité pour créer un injectable avec une syntaxe plus simple. C'est parce que tandis que myInjectedService doit toujours être un objet, myInjectedFactory peut être un objet, une référence de fonction, ou n'importe quelle valeur. Par exemple, si vous écriviez un service pour créer un constructeur (comme dans le dernier exemple ci-dessus), il devrait être instancié de cette façon :

var myShinyNewObject = new myInjectedService.myFunction()

ce qui est discutablement moins souhaitable que ceci :

var myShinyNewObject = new myInjectedFactory();

(Mais vous devriez être prudent en utilisant ce type de motif en premier lieu car l'instanciation d'objets dans vos contrôleurs crée des dépendances difficiles à suivre et difficiles à simuler pour les tests. Mieux vaut avoir un service gérer une collection d'objets pour vous que d'utiliser new() à tout bout de champ.)


Une dernière chose, ce sont tous des Singletons...

Gardez à l'esprit que dans les deux cas, Angular vous aide à gérer un singleton. Peu importe où ou combien de fois vous injectez votre service ou fonction, vous obtiendrez la même référence au même objet ou fonction. (Sauf lorsque une factory retourne simplement une valeur comme un nombre ou une chaîne. Dans ce cas, vous obtiendrez toujours la même valeur, mais pas une référence.)

2 votes

Serait-il préférable de l'appeler un constructeur d'objet plutôt que Newable ?

0 votes

Vous avez dit à 2 endroits différents que le service sera new-ed, puis que l'usine sera new-ed, lequel est-ce?

2 votes

@Hugo, Je démontrai simplement que vous pouvez accomplir la même chose de manière efficace avec les deux, simplement la syntaxe différera.

319voto

Kirk Strobeck Points 1022

Tout simplement ..

const user = {
  firstName: 'john'
};

// Factory
const addLastNameFactory = (user, lastName) => ({
  ...user,
  lastName,
});

console.log(addLastNameFactory(user, 'doe'));

// Service
const addLastNameService = (user, lastName) => {
  user.lastName = lastName; // MAUVAIS! Mutation
  return user;
};

console.log(addLastNameService(user, 'doe'));

169 votes

Mec, merci. Ce n'est pas que les détails des autres réponses ne sont pas valables, mais parfois vous avez besoin de la version de 10 secondes.

4 votes

Il suffit que la fonction de service ne retourne rien. Le this.name = ... est suffisant pour montrer qu'il expose une API.

3 votes

Cependant, si vous retournez un objet, il l'utilisera à la place de celui-ci. jsfiddle.net/Ne5P8/1221

247voto

Manish Chhabra Points 1865

Voici les principales différences :

Services

Syntaxe : module.service( 'nomDuService', fonction );

Résultat : Lorsque vous déclarez nomDuService comme un argument injectable, vous obtiendrez une instance d'une fonction passée à module.service.

Utilisation : Peut être utile pour partager des fonctions utilitaires qu'il est utile d'invoquer en ajoutant simplement ( ) à la référence de la fonction injectée. Peut également être exécuté avec injectedArg.call( this ) ou similaire.

Factories

Syntaxe : module.factory( 'nomDeLaFactory', fonction );

Résultat : Lorsque vous déclarez nomDeLaFactory comme un argument injectable, vous obtiendrez la valeur renvoyée en invoquant la référence de la fonction passée à module.factory.

Utilisation : Peut être utile pour renvoyer une fonction de 'classe' qui peut ensuite être créée en utilisant new.

Voici un exemple utilisant des services et une factory. En savoir plus sur AngularJS Service vs Factory.

Vous pouvez également consulter la documentation d'AngularJS et des questions similaires sur stackoverflow confused about service vs factory.

27 votes

Je ne suis pas d'accord avec votre exemple d'utilisation d'une fabrique. À la fois les services et les fabriques (en supposant qu'une fonction est retournée. Cela pourrait simplement être une valeur ou un objet) peuvent être instanciés. En fait, un service est la seule option garantie d'être instanciable car vous disposez d'une instance de fonction. Je dirais que l'avantage d'utiliser une FABRIQUE plutôt qu'un SERVICE est qu'elle permet un certain contrôle sur l'accès aux propriétés - privées et publiques par exemple, alors que toutes les propriétés du service sont par nature exposées. Et je considère un fournisseur comme une fabrique d'une fabrique - seulement il est injectable et configurable au moment de la configuration.

1 votes

@DrewR Merci pour votre commentaire, j'ai trouvé un bon exemple de méthodes publiques et privées en utilisant une Factory: stackoverflow.com/a/14904891/65025

0 votes

Je dois être d'accord avec @DrewR sur ce point, en fait. J'ai déjà utilisé des factories pour renvoyer des objets, mais honnêtement, à ce stade, il serait peut-être préférable d'utiliser $providers tout le temps.

139voto

Tyler McGinnis Points 3675

TL;DR

1) Lorsque vous utilisez un Usine on crée un objet, on lui ajoute des propriétés, puis on retourne ce même objet. Lorsque vous passez cette fabrique dans votre contrôleur, les propriétés de l'objet seront désormais disponibles dans ce contrôleur via votre fabrique.

app.controller('myFactoryCtrl', function($scope, myFactory){
  $scope.artist = myFactory.getArtist();
});

app.factory('myFactory', function(){
  var _artist = 'Shakira';
  var service = {};

  service.getArtist = function(){
    return _artist;
  }

  return service;
});

2) Lorsque vous utilisez Service Angular l'instancie en coulisse avec le mot-clé "new". De ce fait, vous ajouterez des propriétés à 'this' et le service renverra 'this'. Lorsque vous passez le service dans votre contrôleur, les propriétés de 'this' seront désormais disponibles dans ce contrôleur par le biais de votre service.

app.controller('myServiceCtrl', function($scope, myService){
  $scope.artist = myService.getArtist();
});

app.service('myService', function(){
  var _artist = 'Nelly';
  this.getArtist = function(){
    return _artist;
  }
});

Non TL;DR

1) Usine
Les usines sont le moyen le plus populaire de créer et de configurer un service. Il n'y a pas grand-chose de plus que ce que dit le TL;DR. Il suffit de créer un objet, de lui ajouter des propriétés, puis de retourner ce même objet. Ensuite, lorsque vous passez la fabrique dans votre contrôleur, les propriétés de l'objet seront désormais disponibles dans ce contrôleur via votre fabrique. Un exemple plus complet est présenté ci-dessous.

app.factory('myFactory', function(){
  var service = {};
  return service;
});

Maintenant, les propriétés que nous attachons à 'service' seront disponibles lorsque nous passerons 'myFactory' dans notre contrôleur.

Ajoutons maintenant quelques variables "privées" à notre fonction de rappel. Celles-ci ne seront pas directement accessibles depuis le contrôleur, mais nous mettrons éventuellement en place des méthodes getter/setter sur 'service' pour pouvoir modifier ces variables 'privées' si nécessaire.

app.factory('myFactory', function($http, $q){
  var service = {};
  var baseUrl = 'https://itunes.apple.com/search?term=';
  var _artist = '';
  var _finalUrl = '';

  var makeUrl = function(){
   _artist = _artist.split(' ').join('+');
    _finalUrl = baseUrl + _artist + '&callback=JSON_CALLBACK';
    return _finalUrl
  }

  return service;
});

Vous remarquerez que nous ne rattachons pas ces variables/fonctions à 'service'. Nous les créons simplement afin de pouvoir les utiliser ou les modifier plus tard.

  • baseUrl est l'URL de base requise par l'API iTunes.
  • _artiste est l'artiste que l'on souhaite consulter.
  • _finalUrl est l'URL finale et entièrement construite vers laquelle nous ferons l'appel à iTunes makeUrl est une fonction qui créera et retournera notre URL compatible avec iTunes.

Maintenant que nos variables d'aide/privées et notre fonction sont en place, ajoutons quelques propriétés à l'objet "service". Quoi que nous mettions sur 'service', nous pourrons l'utiliser directement dans n'importe quel contrôleur dans lequel nous passons 'myFactory'.

Nous allons créer des méthodes setArtist et getArtist qui renvoient ou définissent simplement l'artiste. Nous allons également créer une méthode qui appellera l'API iTunes avec l'URL que nous avons créée. Cette méthode va renvoyer une promesse qui sera remplie une fois que les données auront été renvoyées par l'API iTunes. Si vous n'avez pas beaucoup d'expérience dans l'utilisation des promesses dans Angular, je vous recommande vivement de faire une plongée approfondie à leur sujet.

En dessous de setArtist accepte un artiste et vous permet de le définir. getArtist renvoie l'artiste callItunes appelle d'abord makeUrl() afin de construire l'URL que nous utiliserons avec notre requête $http. Ensuite, il configure un objet promesse, effectue une requête $http avec notre URL finale, puis, comme $http renvoie une promesse, nous sommes en mesure d'appeler .success ou .error après notre requête. Nous résolvons alors notre promesse avec les données iTunes, ou nous la rejetons avec un message disant 'There was an error'.

app.factory('myFactory', function($http, $q){
  var service = {};
  var baseUrl = 'https://itunes.apple.com/search?term=';
  var _artist = '';
  var _finalUrl = '';

  var makeUrl = function(){
    _artist = _artist.split(' ').join('+');
    _finalUrl = baseUrl + _artist + '&callback=JSON_CALLBACK'
    return _finalUrl;
  }

  service.setArtist = function(artist){
    _artist = artist;
  }

  service.getArtist = function(){
    return _artist;
  }

  service.callItunes = function(){
    makeUrl();
    var deferred = $q.defer();
    $http({
      method: 'JSONP',
      url: _finalUrl
    }).success(function(data){
      deferred.resolve(data);
    }).error(function(){
      deferred.reject('There was an error')
    })
    return deferred.promise;
  }

  return service;
});

Maintenant, notre usine est complète. Nous pouvons maintenant injecter 'myFactory' dans n'importe quel contrôleur et nous pourrons alors appeler les méthodes que nous avons attachées à notre objet de service (setArtist, getArtist, et callItunes).

app.controller('myFactoryCtrl', function($scope, myFactory){
  $scope.data = {};
  $scope.updateArtist = function(){
    myFactory.setArtist($scope.data.artist);
  };

  $scope.submitArtist = function(){
    myFactory.callItunes()
      .then(function(data){
        $scope.data.artistData = data;
      }, function(data){
        alert(data);
      })
  }
});

Dans le contrôleur ci-dessus, nous injectons le service 'myFactory'. Nous définissons ensuite les propriétés de notre objet $scope qui proviennent des données de 'myFactory'. Le seul code délicat ci-dessus est celui que vous utilisez si vous n'avez jamais eu affaire à des promesses auparavant. Étant donné que callItunes renvoie une promesse, nous sommes en mesure d'utiliser la méthode .then() et de ne définir $scope.data.artistData que lorsque notre promesse est remplie par les données iTunes. Vous remarquerez que notre contrôleur est très "fin". Toute notre logique et nos données persistantes sont situées dans notre service, et non dans notre contrôleur.

2) Service
La chose la plus importante à savoir lors de la création d'un service est qu'il est instancié avec le mot-clé "new". Pour les gourous du JavaScript, cela devrait vous donner une bonne idée de la nature du code. Pour ceux d'entre vous qui n'ont qu'une connaissance limitée de JavaScript ou pour ceux qui ne savent pas trop ce que fait le mot-clé "new", passons en revue quelques principes fondamentaux de JavaScript qui nous aideront à comprendre la nature d'un service.

Pour bien voir les changements qui se produisent lorsque vous invoquez une fonction avec le mot clé "new", créons une fonction et invoquons-la avec le mot clé "new", puis montrons ce que l'interpréteur fait lorsqu'il voit le mot clé "new". Les résultats finaux seront les mêmes.

Tout d'abord, créons notre Constructeur.

var Person = function(name, age){
  this.name = name;
  this.age = age;
}

Il s'agit d'une fonction constructrice typique de JavaScript. Maintenant, chaque fois que nous invoquons la fonction Person en utilisant le mot-clé "new", "this" sera lié à l'objet nouvellement créé.

Ajoutons maintenant une méthode au prototype de notre personne afin qu'elle soit disponible pour chaque instance de notre "classe" de personne.

Person.prototype.sayName = function(){
  alert('My name is ' + this.name);
}

Maintenant, comme nous avons placé la fonction sayName sur le prototype, chaque instance de Person pourra appeler la fonction sayName afin d'alerter le nom de cette instance.

Maintenant que nous avons la fonction constructeur de Person et la fonction sayName sur son prototype, créons une instance de Person et appelons la fonction sayName.

var tyler = new Person('Tyler', 23);
tyler.sayName(); //alerts 'My name is Tyler'

Ainsi, le code permettant de créer un constructeur Person, d'ajouter une fonction à son prototype, de créer une instance Person, puis d'appeler la fonction sur son prototype ressemble à ceci.

var Person = function(name, age){
  this.name = name;
  this.age = age;
}
Person.prototype.sayName = function(){
  alert('My name is ' + this.name);
}
var tyler = new Person('Tyler', 23);
tyler.sayName(); //alerts 'My name is Tyler'

Voyons maintenant ce qui se passe réellement lorsque vous utilisez le mot-clé "new" en JavaScript. La première chose que vous devriez remarquer est qu'après avoir utilisé 'new' dans notre exemple, nous sommes capables d'appeler une méthode (sayName) sur 'tyler' comme s'il s'agissait d'un objet - c'est parce que c'est le cas. Premièrement, nous savons que le constructeur de notre personne renvoie un objet, que cela soit visible ou non dans le code. Deuxièmement, nous savons que, puisque notre fonction sayName est située sur le prototype et non directement sur l'instance de la personne, l'objet que la fonction Personne renvoie doit déléguer à son prototype en cas d'échec de la recherche. En termes plus simples, lorsque nous appelons tyler.sayName(), l'interprète dit "OK, je vais chercher l'objet 'tyler' que nous venons de créer, localiser la fonction sayName, puis l'appeler. Attendez une minute, je ne la vois pas ici - tout ce que je vois est le nom et l'âge, laissez-moi vérifier le prototype. Oui, on dirait qu'elle est dans le prototype, je vais l'appeler".

Vous trouverez ci-dessous un code qui vous permettra de réfléchir à ce que le mot clé "new" fait réellement en JavaScript. Il s'agit essentiellement d'un exemple de code du paragraphe précédent. J'ai mis la "vue de l'interpréteur" ou la façon dont l'interpréteur voit le code à l'intérieur des notes.

var Person = function(name, age){
  //The line below this creates an obj object that will delegate to the person's prototype on failed lookups.
  //var obj = Object.create(Person.prototype);

  //The line directly below this sets 'this' to the newly created object
  //this = obj;

  this.name = name;
  this.age = age;

  //return this;
}

Maintenant que vous savez ce que fait réellement le mot-clé "new" en JavaScript, la création d'un service dans Angular devrait être plus facile à comprendre.

La chose la plus importante à comprendre lors de la création d'un service est de savoir que les services sont instanciés avec le mot clé "new". En combinant cette connaissance avec nos exemples ci-dessus, vous devriez maintenant reconnaître que vous allez attacher vos propriétés et méthodes directement à "this" qui sera ensuite retourné par le service lui-même. Voyons cela en action.

Contrairement à ce que nous avons fait à l'origine dans l'exemple Factory, nous n'avons pas besoin de créer un objet puis de le renvoyer car, comme nous l'avons déjà mentionné à plusieurs reprises, nous avons utilisé le mot-clé "new" afin que l'interpréteur crée cet objet, le délègue à son prototype, puis le renvoie pour nous sans que nous ayons à faire ce travail.

Tout d'abord, créons notre fonction "privée" et notre fonction d'aide. Cela devrait vous sembler très familier puisque nous avons fait exactement la même chose avec notre factory. Je n'expliquerai pas ici ce que fait chaque ligne, car je l'ai fait dans l'exemple de la factory. Si vous ne comprenez pas bien, relisez l'exemple de la factory.

app.service('myService', function($http, $q){
  var baseUrl = 'https://itunes.apple.com/search?term=';
  var _artist = '';
  var _finalUrl = '';

  var makeUrl = function(){
    _artist = _artist.split(' ').join('+');
    _finalUrl = baseUrl + _artist + '&callback=JSON_CALLBACK'
    return _finalUrl;
  }
});

Maintenant, nous allons attacher toutes nos méthodes qui seront disponibles dans notre contrôleur à 'this'.

app.service('myService', function($http, $q){
  var baseUrl = 'https://itunes.apple.com/search?term=';
  var _artist = '';
  var _finalUrl = '';

  var makeUrl = function(){
    _artist = _artist.split(' ').join('+');
    _finalUrl = baseUrl + _artist + '&callback=JSON_CALLBACK'
    return _finalUrl;
  }

  this.setArtist = function(artist){
    _artist = artist;
  }

  this.getArtist = function(){
    return _artist;
  }

  this.callItunes = function(){
    makeUrl();
    var deferred = $q.defer();
    $http({
      method: 'JSONP',
      url: _finalUrl
    }).success(function(data){
      deferred.resolve(data);
    }).error(function(){
      deferred.reject('There was an error')
    })
    return deferred.promise;
  }

});

Maintenant, comme dans notre usine, setArtist, getArtist, et callItunes seront disponibles dans n'importe quel contrôleur dans lequel nous passons myService. Voici le contrôleur myService (qui est presque exactement le même que notre contrôleur d'usine).

app.controller('myServiceCtrl', function($scope, myService){
  $scope.data = {};
  $scope.updateArtist = function(){
    myService.setArtist($scope.data.artist);
  };

  $scope.submitArtist = function(){
    myService.callItunes()
      .then(function(data){
        $scope.data.artistData = data;
      }, function(data){
        alert(data);
      })
  }
});

Comme je l'ai déjà mentionné, une fois que vous comprenez vraiment ce que fait "new", les services sont presque identiques aux fabriques dans Angular.

12 votes

Vous voudrez peut-être fournir un lien direct vers votre blog. tylermcginnis.com/angularjs-factory-vs-service-vs-provider J'ai trouvé cela un peu plus facile à lire.

3 votes

Rien de mal à répéter votre blog ici, mais je suis d'accord que c'est un excellent article de blog.

5 votes

Bonne explication détaillée de ce que chacun fait sous le capot, mais il n'est toujours pas clair pourquoi et quand quelqu'un choisirait d'utiliser un Service plutôt qu'une Factory. En d'autres termes, quand préférerais-je avoir un nouvel objet par rapport à celui retourné par une factory. Je pense que c'est la plus grande confusion.

24voto

pixelbits Points 5710

app.factory('fn', fn) vs. app.service('fn',fn)

Construction

Avec les factories, Angular invoquera la fonction pour obtenir le résultat. C'est le résultat qui est mis en cache et injecté.

 //factory
 var obj = fn();
 return obj;

Avec les services, Angular invoquera la fonction constructeur en appelant new. La fonction construite est mise en cache et injectée.

  //service
  var obj = new fn();
  return obj;

Implémentation

Les factories retournent généralement un objet littéral car la valeur de retour est celle qui est injectée dans les contrôleurs, les blocs d'exécution, les directives, etc.

  app.factory('fn', function(){
         var foo = 0;
         var bar = 0;
         function setFoo(val) {
               foo = val;
         }
         function setBar (val){
               bar = val;
         }
         return {
                setFoo: setFoo,
                serBar: setBar
         }
  });

Les fonctions de service ne retournent généralement rien. Au lieu de cela, elles effectuent une initialisation et exposent des fonctions. Les fonctions peuvent également faire référence à 'this' car elles ont été construites en utilisant 'new'.

app.service('fn', function () {
         var foo = 0;
         var bar = 0;
         this.setFoo = function (val) {
               foo = val;
         }
         this.setBar = function (val){
               bar = val;
         }
});

Conclusion

Concernant l'utilisation de factories ou de services, ils sont tous les deux très similaires. Ils sont injectés dans des contrôleurs, des directives, des blocs d'exécution, etc., et utilisés dans le code client de manière assez similaire. Ils sont également tous deux des singletons - ce qui signifie que la même instance est partagée entre tous les endroits où le service/factory est injecté.

Alors lequel devriez-vous préférer? L'un ou l'autre - ils sont tellement similaires que les différences sont triviales. Si vous en choisissez un plutôt que l'autre, soyez simplement conscient de la façon dont ils sont construits, afin de pouvoir les implémenter correctement.

0 votes

Les fonctions de service ne "ne retournent rien", elles retournent implicitement l'objet construit SI vous ne spécifiez pas votre propre instruction de retour (dans ce dernier cas, l'objet que vous avez retourné est celui qui sera créé et mis en cache, similaire à une usine).

0 votes

Je pense que tu interprètes mal cela... Quand je dis retour, je veux dire du point de vue de l'implémentation de la fonction de service

0 votes

Êtes-vous sûr que l'usine est également une ville unique?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X