Services non-singleton à AngularJS

AngularJS indique clairement dans sa documentation que les services sont des singletons:

AngularJS services are singletons 

module.factory , module.factory renvoie également une instance Singleton.

Étant donné qu’il existe de nombreux cas d’utilisation pour les services non-singleton, quel est le meilleur moyen d’implémenter la méthode factory pour renvoyer des instances d’un service, de sorte que chaque fois qu’une dépendance ExampleService est déclarée, elle est satisfaite par une autre instance ExampleService ?

Je ne pense pas que nous devrions jamais avoir une new fonction capable de renvoyer une fabrique, car cela commence à décomposer l’dependency injection et la bibliothèque se comportera de manière maladroite, en particulier pour les tiers. En bref, je ne suis pas certain qu’il existe des cas d’utilisation légitimes pour les services autres que les singleton.

Une meilleure façon d’accomplir la même chose est d’utiliser la fabrique comme une API pour renvoyer une collection d’objects avec des méthodes de lecture et de définition associées. Voici un pseudo-code indiquant comment utiliser ce type de service:

 .controller( 'MainCtrl', function ( $scope, widgetService ) { $scope.onSearchFormSubmission = function () { widgetService.findById( $scope.searchById ).then(function ( widget ) { // this is a returned object, complete with all the getter/setters $scope.widget = widget; }); }; $scope.onWidgetSave = function () { // this method persists the widget object $scope.widget.$save(); }; }); 

Ceci est juste un pseudo-code pour rechercher un widget par ID et ensuite être capable de sauvegarder les modifications apscopes à l’enregistrement.

Voici un pseudo-code pour le service:

 .factory( 'widgetService', function ( $http ) { function Widget( json ) { angular.extend( this, json ); } Widget.prototype = { $save: function () { // TODO: ssortingp irrelevant fields var scrubbedObject = //... return $http.put( '/widgets/'+this.id, scrubbedObject ); } }; function getWidgetById ( id ) { return $http( '/widgets/'+id ).then(function ( json ) { return new Widget( json ); }); } // the public widget API return { // ... findById: getWidgetById // ... }; }); 

Bien qu’ils ne soient pas inclus dans cet exemple, ces types de services flexibles peuvent également gérer facilement l’état.


Je n’ai pas le temps pour le moment, mais si cela peut vous être utile, je pourrai plus tard rassembler un simple Plunker.

Je ne suis pas tout à fait sûr de ce que vous essayez de satisfaire. Mais il est possible d’avoir une fabrique de retour des instances d’un object. Vous devriez pouvoir modifier cela pour répondre à vos besoins.

 var ExampleApplication = angular.module('ExampleApplication', []); ExampleApplication.factory('InstancedService', function(){ function Instance(name, type){ this.name = name; this.type = type; } return { Instance: Instance } }); ExampleApplication.controller('InstanceController', function($scope, InstancedService){ var instanceA = new InstancedService.Instance('A','ssortingng'), instanceB = new InstancedService.Instance('B','object'); console.log(angular.equals(instanceA, instanceB)); }); 

JsFiddle

Actualisé

Considérez la demande suivante pour les services non-singleton . Dans lequel Brian Ford note:

L’idée que tous les services sont des singletons ne vous empêche pas d’écrire des fabriques singleton qui peuvent instancier de nouveaux objects.

et son exemple de retour d’instances d’usines:

 myApp.factory('myService', function () { var MyThing = function () {}; MyThing.prototype.foo = function () {}; return { getInstance: function () { return new MyThing(); } }; }); 

Je dirais également que son exemple est supérieur car vous n’avez pas à utiliser le new mot-clé dans votre contrôleur. Il est encapsulé dans la méthode getInstance du service.

Une autre méthode consiste à copier un object de service avec angular.extend() .

 app.factory('Person', function(){ return { greet: function() { return "Hello, I'm " + this.name; }, copy: function(name) { return angular.extend({name: name}, this); } }; }); 

et puis, par exemple, dans votre contrôleur

 app.controller('MainCtrl', function ($scope, Person) { michael = Person.copy('Michael'); peter = Person.copy('Peter'); michael.greet(); // Hello I'm Michael peter.greet(); // Hello I'm Peter }); 

Voici un plunk .

Je sais que cet article a déjà reçu une réponse, mais je pense toujours que certains scénarios légitimes nécessiteront un service non singleton. Disons qu’il existe une logique métier réutilisable pouvant être partagée entre plusieurs contrôleurs. Dans ce scénario, le meilleur endroit pour placer la logique serait un service, mais que faire si nous devons garder un état dans notre logique réutilisable? Ensuite, nous avons besoin d’un service non-singleton pour pouvoir être partagé entre différents contrôleurs dans l’application. Voici comment je mettrais en œuvre ces services:

 angular.module('app', []) .factory('nonSingletonService', function(){ var instance = function (name, type){ this.name = name; this.type = type; return this; } return instance; }) .controller('myController', ['$scope', 'nonSingletonService', function($scope, nonSingletonService){ var instanceA = new nonSingletonService('A','ssortingng'); var instanceB = new nonSingletonService('B','object'); console.log(angular.equals(instanceA, instanceB)); }]); 

Voici mon exemple d’un service non-singleton, c’est à partir d’une im ORM travaillant sur. Dans l’exemple, je montre un modèle de base (ModelFactory) que je souhaite que les services («utilisateurs», «documents») héritent et que leur potentiel soit étendu.

Dans mon ORM, ModelFactory injecte d’autres services afin de fournir des fonctionnalités supplémentaires (requête, persistance, mappage de schéma) qui sont mises en sandbox à l’aide du système de module.

Dans l’exemple, le service utilisateur et le service de document ont les mêmes fonctionnalités mais ont leurs propres étendues indépendantes.

 /* A class which which we want to have multiple instances of, it has two attrs schema, and classname */ var ModelFactory; ModelFactory = function($injector) { this.schema = {}; this.className = ""; }; Model.prototype.klass = function() { return { className: this.className, schema: this.schema }; }; Model.prototype.register = function(className, schema) { this.className = className; this.schema = schema; }; angular.module('model', []).factory('ModelFactory', [ '$injector', function($injector) { return function() { return $injector.instantiate(ModelFactory); }; } ]); /* Creating multiple instances of ModelFactory */ angular.module('models', []).service('userService', [ 'ModelFactory', function(modelFactory) { var instance; instance = new modelFactory(); instance.register("User", { name: 'Ssortingng', username: 'Ssortingng', password: 'Ssortingng', email: 'Ssortingng' }); return instance; } ]).service('documentService', [ 'ModelFactory', function(modelFactory) { var instance; instance = new modelFactory(); instance.register("Document", { name: 'Ssortingng', format: 'Ssortingng', fileSize: 'Ssortingng' }); return instance; } ]); /* Example Usage */ angular.module('controllers', []).controller('exampleController', [ '$scope', 'userService', 'documentService', function($scope, userService, documentService) { userService.klass(); /* returns { className: "User" schema: { name : 'Ssortingng' username : 'Ssortingng' password: 'Ssortingng' email: 'Ssortingng' } } */ return documentService.klass(); /* returns { className: "User" schema: { name : 'Ssortingng' format : 'Ssortingng' formatileSize: 'Ssortingng' } } */ } ]); 

angular ne donne qu’une option de service / fabrique singleton . Une façon de contourner le problème consiste à avoir un service d’usine qui créera une nouvelle instance pour vous au sein de votre contrôleur ou d’autres instances de consommateurs. La seule chose qui est injectée est la classe qui crée de nouvelles instances. c’est un bon endroit pour injecter d’autres dépendances ou pour initialiser votre nouvel object à la spécification de l’utilisateur (ajout de services ou config)

 namespace admin.factories { 'use ssortingct'; export interface IModelFactory { build($log: ng.ILogService, connection: ssortingng, collection: ssortingng, service: admin.services.ICollectionService): IModel; } class ModelFactory implements IModelFactory { // any injection of services can happen here on the factory constructor... // I didnt implement a constructor but you can have it contain a $log for example and save the injection from the build funtion. build($log: ng.ILogService, connection: ssortingng, collection: ssortingng, service: admin.services.ICollectionService): IModel { return new Model($log, connection, collection, service); } } export interface IModel { // query(connection: ssortingng, collection: ssortingng): ng.IPromise; } class Model implements IModel { constructor( private $log: ng.ILogService, private connection: ssortingng, private collection: ssortingng, service: admin.services.ICollectionService) { }; } angular.module('admin') .service('admin.services.ModelFactory', ModelFactory); } 

ensuite, dans votre instance consommateur, vous avez besoin du service d’usine et appelez la méthode de génération en usine pour obtenir une nouvelle instance lorsque vous en avez besoin

  class CollectionController { public model: admin.factories.IModel; static $inject = ['$log', '$routeParams', 'admin.services.Collection', 'admin.services.ModelFactory']; constructor( private $log: ng.ILogService, $routeParams: ICollectionParams, private service: admin.services.ICollectionService, factory: admin.factories.IModelFactory) { this.connection = $routeParams.connection; this.collection = $routeParams.collection; this.model = factory.build(this.$log, this.connection, this.collection, this.service); } } 

vous pouvez voir que cela fournit l’opportunité de injecter des services spécifiques qui ne sont pas disponibles dans la phase d’usine. vous pouvez toujours faire en sorte que l’injection se produise sur l’instance de fabrique à utiliser par toutes les instances du modèle.

Note J’ai dû enlever du code pour que je puisse faire des erreurs de contexte … si vous avez besoin d’un exemple de code qui fonctionne, faites le moi savoir.

Je crois que NG2 aura la possibilité d’injecter une nouvelle instance de votre service au bon endroit dans votre DOM afin de ne pas avoir à créer votre propre implémentation d’usine. devra attendre et voir 🙂

Je crois qu’il y a de bonnes raisons de créer une nouvelle instance d’un object dans un service. Nous devrions garder l’esprit ouvert plutôt que de simplement dire que nous ne devrions jamais faire une telle chose, mais le singleton a été fait pour une raison . Les contrôleurs sont créés et détruits souvent au cours du cycle de vie de l’application, mais les services doivent être persistants.

Je pense à un cas d’utilisation où vous avez un stream de travail quelconque, comme accepter un paiement et plusieurs propriétés définies, mais vous devez maintenant modifier leur type de paiement car la carte de crédit du client a échoué et doit fournir un autre type de paiement. Paiement. Bien sûr, cela a beaucoup à voir avec la façon dont vous créez votre application. Vous pouvez réinitialiser toutes les propriétés de l’object de paiement ou créer une nouvelle instance d’un object dans le service . Mais, vous ne souhaitez pas une nouvelle instance du service, et vous ne souhaitez pas non plus actualiser la page.

Je pense qu’une solution consiste à fournir un object au sein du service que vous pouvez créer une nouvelle instance de et définir. Mais pour être clair, l’instance unique du service est importante car un contrôleur peut être créé et détruit plusieurs fois, mais les services ont besoin de persistance. Ce que vous recherchez n’est peut-être pas une méthode directe dans Angular, mais un modèle d’object que vous pouvez gérer dans votre service.

Par exemple, j’ai créé un bouton de réinitialisation . (Ceci n’est pas testé, c’est juste une idée rapide d’un cas d’utilisation pour créer un nouvel object au sein d’un service.

 app.controller("PaymentController", ['$scope','PaymentService',function($scope, PaymentService) { $scope.utility = { reset: PaymentService.payment.reset() }; }]); app.factory("PaymentService", ['$http', function ($http) { var paymentURL = "https://www.paymentserviceprovider.com/servicename/token/" function PaymentObject(){ // this.user = new User(); /** Credit Card*/ // this.paymentMethod = ""; //... } var payment = { options: ["Cash", "Check", "Existing Credit Card", "New Credit Card"], paymentMethod: new PaymentObject(), getService: function(success, fail){ var request = $http({ method: "get", url: paymentURL } ); return ( request.then(success, fail) ); } //... } return { payment: { reset: function(){ payment.paymentMethod = new PaymentObject(); }, request: function(success, fail){ return payment.getService(success, fail) } } } }]); 

Voici une autre approche du problème qui m’a beaucoup satisfait, en particulier lorsqu’il est utilisé en combinaison avec Closure Comstackr avec des optimisations avancées activées:

 var MyFactory = function(arg1, arg2) { this.arg1 = arg1; this.arg2 = arg2; }; MyFactory.prototype.foo = function() { console.log(this.arg1, this.arg2); // You have static access to other injected services/factories. console.log(MyFactory.OtherService1.foo()); console.log(MyFactory.OtherService2.foo()); }; MyFactory.factory = function(OtherService1, OtherService2) { MyFactory.OtherService1_ = OtherService1; MyFactory.OtherService2_ = OtherService2; return MyFactory; }; MyFactory.create = function(arg1, arg2) { return new MyFactory(arg1, arg2); }; // Using MyFactory. MyCtrl = function(MyFactory) { var instance = MyFactory.create('bar1', 'bar2'); instance.foo(); // Outputs "bar1", "bar2" to console, plus whatever static services do. }; angular.module('app', []) .factory('MyFactory', MyFactory) .controller('MyCtrl', MyCtrl);