90

AngularJS clearly states in its documentation that Services are Singletons:

AngularJS services are singletons

Counterintuitively, module.factory also returns a Singleton instance.

Given that there are plenty of use-cases for non-singleton services, what is the best way to implement the factory method to return instances of a Service, so that each time an ExampleService dependency is declared, it is satisfied by a different instance of ExampleService?

mdewitt
  • 2,526
  • 19
  • 23
Undistraction
  • 42,754
  • 56
  • 195
  • 331
  • 1
    Assuming you could do this, should you? Other Angular developers would not be expecting a dependency-injected factory to be returning new instances all the time. – Mark Rajcok May 18 '13 at 16:52
  • 1
    I guess that's a matter for documentation. I think it's a shame that this wasn't supported out of the gate as there is now an expectation that all Services will be Singletons, but I see no reason to limit them to Singletons. – Undistraction May 20 '13 at 08:49

8 Answers8

78

I'm not entirely sure what use case you are trying to satisfy. But it is possible to have a factory return instances of an object. You should be able to modify 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

Updated

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

The idea that all services are singletons does not stop you from writing singleton factories that can instantiate 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 his example is superior due to the fact that you do not have to use the new keyword in your controller. It is encapsulated within the getInstance method of the service.

Jonathan Palumbo
  • 6,851
  • 1
  • 29
  • 40
  • Thanks for the example. So there is no way to have the DI Container satisfy the dependency with an instance. The only way is to have it satisfy the dependency with a provider which can then be used to generate the instance? – Undistraction May 18 '13 at 16:34
  • Thanks. I agree that it is better than having to use new in a service, however I think it still falls short. Why should the class that is dependent on the service know or care that the service it's being supplied with is or isn't a Singleton? Both these solutions fail to abstract this fact and are pushing something that I believe should be internal to the DI container into the dependent. When you create a Service, I see know harm in allowing the creator to decide whether or not they would like it to be supplied as a singleton or as separate instances. – Undistraction May 18 '13 at 18:11
  • +1 Very help. I'm using this approach with `ngInfiniteScroll` and a custom search service so I can delay initialisation until some click event. JSFiddle of 1st answer updated with second solution: http://jsfiddle.net/gavinfoley/G5ku5 – GFoley83 Aug 19 '13 at 03:29
  • 4
    Why is using the new operator bad? I feel like if your goal is a non singleton then using `new` is declarative and its easy to tell right away what services are singletons and what are not. Based on if an object is being newed up. – j_walker_dev Apr 10 '14 at 07:13
  • seems like this should be the answer because it delivers what the question asked for - especially the "Updated" appendix. – lukkea Apr 27 '15 at 12:39
  • I run into this all the time. If you want to use any kind of inheritance in an Angular app, but don't want to add things to the global namespace, then you need to inject either the constructor or a way to instantiate a new object. I was actually a little surprised when I found that the "service" provider "recipe" was returning singletons. What's the point, then, really? We could have used `return new Instance();` in the factory. `module.service()` saves us two lines of code. – nbering Sep 06 '15 at 04:54
44

I don't think we should ever have a factory return a newable function as this begins to break down dependency injection and the library will behave awkwardly, especially for third parties. In short, I am not sure there are any legitimate use cases for non-singleton sevices.

A better way to accomplish the same thing is to use the factory as an API to return a collection of objects with getter and setter methods attached to them. Here is some pseudo-code showing how using that kind of service might work:

.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 pseudo-code for looking up a widget by ID and then being able to save changes made to the record.

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

Though not included in this example, these kinds of flexible services could also easily manage state.


I don't have time right now, but if it will be helpful I can put together a simple Plunker later to demonstrate.

Josh David Miller
  • 120,525
  • 16
  • 127
  • 95
  • This is really interesting. An example would be really helpful. Thanks a lot. – Undistraction May 18 '13 at 17:16
  • This is interesting. It seems like it would function similar to an angular `$resource`. – Jonathan Palumbo May 18 '13 at 17:20
  • @JonathanPalumbo You're right - very similar to ngResource. In fact, Pedr and I started this discussion tangentially in another question where I suggested taking an approach similar to ngResource. For an example so simple as this, there's no advantage to doing it manually - ngResource or [Restangular](https://github.com/mgonto/restangular) would work swimmingly. But for cases not so completely typical, this approach makes sense. – Josh David Miller May 18 '13 at 18:07
  • Restangular is new to me, looks very promising almost like a client side version of linq. – Jonathan Palumbo May 19 '13 at 00:39
  • 4
    @Pedr Sorry, I forgot about this. Here's a super-simple demo: http://plnkr.co/edit/Xh6pzd4HDlLRqITWuz8X – Josh David Miller May 24 '13 at 05:11
  • 15
    @JoshDavidMiller could you specify why/what would "break down dependency injection and [why/what] the library will behave awkwardly"? – okigan May 08 '14 at 17:32
  • @JoshDavidMiller This is the only resource I could find regarding returning a new-able function in an Angular factory "breaking down dependency injection and making the library behave awkwardly", do you have a concrete example or a link to some docs that I could read up on about it? I've been searching everywhere and this is the only thing I could find. Thanks. – Willwsharp Mar 13 '17 at 12:40
20

Another way is to copy service object with 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 a plunk.

Evgenii
  • 36,389
  • 27
  • 134
  • 170
  • Really neat! Do you know of any dangers behind this trick? After all, it's just angular.extend'ing an object, so I guess we should be fine. Nevertheless, making dozens copies of a service sounds a bit intimidating. – vucalur Aug 28 '16 at 22:28
9

I know this post has already been answered but I still think there would be some legitimate scenarios that you need to have non-singleton service. Let's say there are some reusable business logic which can be shared between several controllers. In this scenario the best place to put the logic would be a service, but what if we need to keep some state in our reusable logic? Then we need non-singleton service so can be shared across different controllers in app. This is how I would 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));

    }]);
msoltany
  • 91
  • 1
  • 1
  • This is very similar to Jonathan Palumbo's answer except Jonathan encapsulates everything with his "Updated" appendix. – lukkea Apr 27 '15 at 12:42
  • 1
    Are you saying that a non Singleton service would be persistent. And should keep state , ,kinda seems like the other way around . – eran otzap Dec 22 '15 at 06:50
2

Here's my example of a non singleton service, It's from a ORM im working on. In the example I show a Base Model (ModelFactory) which I want services('users','documents') to inherit and potential extend.

In my ORM ModelFactory injects other services to provide extra functionality(query,persistence,schema mapping) which is sandboxed using the module system.

In the example both user and document service have the same functionality but have their own independent scopes.

/*
    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' 
            }
        }
     */
  }
]);
Nath
  • 6,774
  • 2
  • 28
  • 22
1

angular only gives a singleton service/factory option. one way around it is to have a factory service that will build a new instance for you inside your controller or other consumer instances. the only thing that is injected is the class that creates new instances. this is a good place to inject other dependencies or to initialize your new object to the specification of the user (adding services or 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<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 on the 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 it provides opperatiunity to inject some specific services that are not available in the factory step. you can always have injection happen on the factory instance to be used by all Model instances.

Note I had to strip off some code so I might made some context errors... if you need a code sample that works let me know.

I believe that NG2 will have the option to inject a new instance of your service in the right place in your DOM so you dont need to build your own factory implementation. will have to wait and see :)

Gadi
  • 1,539
  • 22
  • 37
  • nice approach - I would like to see that $serviceFactory as an npm package. If you like I can build it and add you as a contributor? – hackp0int Nov 07 '16 at 10:25
1

I believe there is good reason to create a new instance of an object within a service. We should keep an open mind as well rather than just say we ought never do such a thing, but the singleton was made that way for a reason. Controllers are created and destroyed often within the lifecycle of the app, but the services must be persistent.

I can think of a use case where you have a work flow of some kind, like accepting a payment and you have multiple properties set, but must now change their payment type because the customer's credit card failed and they need to provide a different form of payment. Of course, this does have a lot to do with the way you create your app. You could reset all properties for the payment object, or you could create a new instance of an object within the service. But, you would not want a new instance of the service, nor would you want to refresh the page.

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

As an example, I have a made a reset button. (This is not tested, its really just a quick idea of a use case for creating a new object within a 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)
            }
        }
    }
}]);
0

Here's another approach to the problem that I was quite satisfied with, specifically when used in combination with Closure Compiler with advanced optimizations enabled:

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