Non-Singleton Services at Angular

Angular clearly states in its documentation that services are singlets:

Angular services are singletons 

Counterintuitively, module.factory also returns a Singleton instance.

Given that there are many use cases for services other than singleton, the best way to implement the factory method is to return instances of the service, so every time an ExampleService dependency is declared, is it satisfied with another instance of ExampleService ?

+83
angularjs singleton instance service factory
May 18 '13 at 15:46
source share
8 answers

I don’t think that ever the factory will return the new function, as this will start to break the dependency injection, and the library will be awkward, especially for third parties. In short, I'm not sure if there are any legitimate use cases for non-singleton services.

The best way to do the same is to use factory as an API to return a collection of objects with getter and setter methods attached to them. Here is some kind of pseudo code showing how to use such a 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(); }; }); 

This is just a pseudo-code for finding a widget by ID, and then the ability to save changes made to the record.

Here is some pseudo code for the service:

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

Although this example is not included in this example, these flexible services can also easily manage state.




I don’t have time right now, but if it is useful, I can later put together a simple Plunker to demonstrate.

+41
May 18 '13 at 17:12
source share

I'm not quite sure what precedent you are trying to satisfy. But it is possible to have instances of factory object return. You should be able to change this to suit your needs.

 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

Update

Consider the following non-singleton service request. In which Brian Ford notes:

The idea that all services are single does not stop you from writing singleton factories that can create new objects.

and his example of returning instances from factories:

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

I would also argue that his example is excellent because you do not need to use the new keyword in your controller. It is enclosed in the getInstance method of the service.

+71
May 18 '13 at 16:27
source share

Another way is to copy the service object using angular.extend() .

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

and then for example in your controller

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

Here is the plunk .

+20
Oct 24 '13 at 9:37 on
source share

I know that this post has already been answered, but I still think that there will be some legitimate scripts that you need in order to have a non-singleton service. Let's say there are several reusable business logic that can be shared between multiple controllers. In this scenario, service would be the best place for logic, but what if we need to maintain some state in our reusable logic? Then we need a non-singleton service, so it can be divided into different controllers in the application. Here is how I could implement these 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','string'); var instanceB = new nonSingletonService('B','object'); console.log(angular.equals(instanceA, instanceB)); }]); 
+7
Dec 03 '14 at 11:24
source share

Here is my non singleton service example, this is from ORM im working on. In the example, I show the base model (ModelFactory), in which I want services ("users", "documents") to inherit and potentially expand.

Other services are being implemented in my ORM ModelFactory to provide additional functionality (query, persistence, pattern matching) that is sandboxed using a modular system.

In this example, the user and document services have the same functionality, but have their own independent areas.

 /* 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' } } */ } ]); 
+2
02 Sep '14 at 19:45
source share

angular provides only the singleton service / factory parameter. One way is to have a factory service that builds a new instance for you inside your controller or other client instances. the only thing that is introduced is the class that creates the new instances. this is a good place to enter other dependencies or to initialize your new object with a user specification (adding services or configuration).

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

then in your consumer instance you need the factory service and call the build method in factory to get a new instance when you need it

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

you can see that it provides responsiveness to implement some specific services that are not available in the factory step. you can always have an injection on a factory instance that will be used by all instances of the model.

Note. I had to disable some code so that I could make some contextual errors ... if you need some sample code that works, let me know.

I believe that NG2 will be able to implement a new instance of your service at the right place in your DOM, so you do not need to create your own factory implementation. you have to wait and see :)

+1
Apr 12 '16 at 9:30
source share

I believe there is a good reason to create a new instance of the object inside the service. We should also keep an open mind, and not just say that we should never do this, but singleton was created for this very reason . Controllers are created and destroyed frequently throughout the application life cycle, but services must be persistent.

I can present an example of use when you have some kind of workflow, for example, accepting a payment, and you have several properties, but now you have to change your type of payment because the customer’s credit card has failed and they need to be provided with another payment method. Of course, this has a lot to do with how you build your application. You could reset all the properties for the payment object, or you could create a new instance of the object in the service . But you do not need a new instance of the service, and you do not want to refresh the page.

I believe that the solution provides an object inside the service that you can create and install a new instance. But to be clear, a single instance of a service is important because a controller can be created and destroyed many times, but services require persistence. What you are looking for may not be a direct method in Angular, but a template for an object that you can manage inside your service.

As an example, I have a reset button. (This is not verified, it is really just a brief introduction to creating a new object in the 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) } } } }]); 
+1
May 01 '16 at a.m.
source share

Here is another approach to the problem that suits me perfectly, especially when used in conjunction with the Closure Compiler with advanced optimizations:

 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); 
0
Jul 06 '15 at 20:10
source share



All Articles