Serviços Não-Singleton em AngularJS

AngularJS afirma claramente em sua documentação que os Serviços são Singletons:

AngularJS services are singletons 

Contraintuitivamente, module.factory também retorna uma instância Singleton.

Dado que há muitos casos de uso para serviços não-singleton, qual é a melhor maneira de implementar o método factory para retornar instâncias de um Serviço, para que cada vez que uma dependência ExampleService seja declarada, seja satisfeita por uma instância diferente de ExampleService

Eu não acho que nós deveríamos ter uma fábrica retornando uma new function capaz, pois isso começa a quebrar a injeção de dependência e a biblioteca se comportará de forma estranha, especialmente para terceiros. Em suma, não tenho certeza se existem casos de uso legítimo para sevices não-singleton.

Uma maneira melhor de realizar a mesma coisa é usar a factory como uma API para retornar uma coleção de objects com methods getter e setter anexados a eles. Aqui está algum pseudo-código mostrando como usar esse tipo de serviço pode funcionar:

 .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(); }; }); 

Esse é apenas um pseudocódigo para procurar um widget por ID e, em seguida, salvar as alterações feitas no registro.

Aqui está algum pseudo-código para o serviço:

 .factory( 'widgetService', function ( $http ) { function Widget( json ) { angular.extend( this, json ); } Widget.prototype = { $save: function () { // TODO: strip 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 // ... }; }); 

Embora não estejam incluídos neste exemplo, esses tipos de serviços flexíveis também podem gerenciar facilmente o estado.


Eu não tenho tempo agora, mas se for útil eu posso montar um Plunker simples mais tarde para demonstrar.

Não tenho certeza de qual caso de uso você está tentando satisfazer. Mas é possível ter uma instância de retorno de fábrica de um object. Você deve poder modificar isso para atender às suas necessidades.

 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','string'), instanceB = new InstancedService.Instance('B','object'); console.log(angular.equals(instanceA, instanceB)); }); 

JsFiddle

Atualizada

Considere a seguinte solicitação para serviços não-singleton . Em que Brian Ford observa:

A ideia de que todos os serviços são singletons não o impede de escrever fábricas singleton que possam instanciar novos objects.

e seu exemplo de retornar instâncias de fábricas:

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

Eu também argumentaria que seu exemplo é superior devido ao fato de que você não precisa usar a new palavra-chave em seu controlador. Ele é encapsulado no método getInstance do serviço.

Outra maneira é copiar o object de serviço com angular.extend() .

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

e depois, por exemplo, no seu controlador

 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 }); 

Aqui está um plunk .

Eu sei que este post já foi respondido, mas eu ainda acho que haveria alguns cenários legítimos que você precisa ter serviço não-singleton. Digamos que existam algumas lógicas de negócios reutilizáveis ​​que podem ser compartilhadas entre vários controladores. Nesse cenário, o melhor lugar para colocar a lógica seria um serviço, mas e se precisarmos manter algum estado em nossa lógica reutilizável? Então, precisamos de um serviço não-singleton para que possamos compartilhar em diferentes controladores no aplicativo. É assim que eu implementaria esses serviços:

 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','string'); var instanceB = new nonSingletonService('B','object'); console.log(angular.equals(instanceA, instanceB)); }]); 

Aqui está o meu exemplo de um serviço não singleton, é de um ORM estou trabalhando. No exemplo, mostro um modelo de base (ModelFactory) que desejo que os serviços (‘usuários’, ‘documentos’) herdem e se estendam em potencial.

No meu ORM ModelFactory injeta outros serviços para fornecer funcionalidade extra (consulta, persistência, mapeamento de esquema), que é sandboxed usando o sistema de módulos.

No exemplo, tanto o usuário quanto o serviço de documento têm a mesma funcionalidade, mas possuem seus próprios escopos independentes.

 /* 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: 'String', username: 'String', password: 'String', email: 'String' }); return instance; } ]).service('documentService', [ 'ModelFactory', function(modelFactory) { var instance; instance = new modelFactory(); instance.register("Document", { name: 'String', format: 'String', fileSize: 'String' }); return instance; } ]); /* Example Usage */ angular.module('controllers', []).controller('exampleController', [ '$scope', 'userService', 'documentService', function($scope, userService, documentService) { userService.klass(); /* returns { className: "User" schema: { name : 'String' username : 'String' password: 'String' email: 'String' } } */ return documentService.klass(); /* returns { className: "User" schema: { name : 'String' format : 'String' formatileSize: 'String' } } */ } ]); 

angular só dá uma opção de serviço / fábrica singleton . uma maneira de contornar isso é ter um serviço de fábrica que criará uma nova instância para você dentro de seu controlador ou outras instâncias do consumidor. A única coisa que é injetada é a class que cria novas instâncias. este é um bom lugar para injetar outras dependencies ou para inicializar seu novo object para a especificação do usuário (adicionando serviços ou config)

 namespace admin.factories { 'use strict'; export interface IModelFactory { build($log: ng.ILogService, connection: string, collection: string, 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: string, collection: string, service: admin.services.ICollectionService): IModel { return new Model($log, connection, collection, service); } } export interface IModel { // query(connection: string, collection: string): ng.IPromise; } class Model implements IModel { constructor( private $log: ng.ILogService, private connection: string, private collection: string, service: admin.services.ICollectionService) { }; } angular.module('admin') .service('admin.services.ModelFactory', ModelFactory); } 

em seguida, na instância do consumidor, você precisa do serviço de fábrica e chama o método de construção na fábrica para obter uma nova instância quando precisar dela

  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); } } 

Você pode ver que isso oferece uma oportunidade para injetar alguns serviços específicos que não estão disponíveis na etapa de fábrica. você sempre pode fazer com que a injeção aconteça na instância de fábrica para ser usada por todas as instâncias do Modelo.

Note que eu tive que despir algum código para que eu pudesse fazer alguns erros de contexto … se você precisar de um exemplo de código que funcione, me avise.

Acredito que o NG2 terá a opção de injetar uma nova instância de seu serviço no lugar certo em seu DOM, para que você não precise construir sua própria implementação de fábrica. Vai ter que esperar e ver 🙂

Eu acredito que há boas razões para criar uma nova instância de um object dentro de um serviço. Devemos manter a mente aberta também, ao invés de apenas dizer que nunca devemos fazer uma coisa dessas, mas o singleton foi feito dessa forma por uma razão . Os controladores são criados e destruídos com frequência no ciclo de vida do aplicativo, mas os serviços devem ser persistentes.

Posso pensar em um caso de uso em que você tem um stream de trabalho de algum tipo, como aceitar um pagamento e várias propriedades definidas, mas agora deve alterar o tipo de pagamento porque o cartão de crédito do cliente falhou e eles precisam fornecer uma forma diferente de Forma de pagamento. Claro, isso tem muito a ver com a maneira como você cria seu aplicativo. Você pode redefinir todas as propriedades do object de pagamento ou criar uma nova instância de um object no serviço . Mas você não desejaria uma nova instância do serviço nem atualizaria a página.

Eu acredito que uma solução é fornecer um object dentro do serviço que você pode criar uma nova instância e definir. Mas, só para ficar claro, a instância única do serviço é importante porque um controlador pode ser criado e destruído muitas vezes, mas os serviços precisam de persistência. O que você está procurando pode não ser um método direto dentro do Angular, mas um padrão de object que você pode gerenciar dentro do seu serviço.

Por exemplo, eu fiz um botão de reset . (Isso não é testado, é apenas uma idéia rápida de um caso de uso para criar um novo object dentro de um serviço.

 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) } } } }]); 

Aqui está outra abordagem para o problema que eu estava bastante satisfeito, especificamente quando usado em combinação com o Closure Compiler com otimizações avançadas habilitadas:

 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);