web-dev-qa-db-fra.com

Services non-singleton dans AngularJS

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

AngularJS services are singletons

Contre-intuitivement, module.factory renvoie également une instance Singleton.

Etant donné qu'il existe de nombreux cas d'utilisation pour des services non singleton, quel est le meilleur moyen d'implémenter la méthode fabrique pour renvoyer des instances d'un service, de sorte que chaque fois qu'une dépendance ExampleService soit déclarée, elle soit satisfaite par une instance différente de ExampleService?

89
Undistraction

Je ne pense pas que nous devrions jamais avoir une usine retournant une fonction newable car celle-ci commence à rompre l'injection de dépendance et la bibliothèque se comportera maladroitement, en particulier pour les tiers. En bref, je ne suis pas sûr qu'il existe des cas d'utilisation légitimes pour des services autres que des singleton.

Un meilleur moyen d'accomplir la même chose consiste à utiliser la fabrique en tant qu'API pour renvoyer une collection d'objets avec les méthodes getter et setter qui leur sont associées. Voici un pseudo-code montrant 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();
  };
});

Il ne s'agit que d'un pseudo-code permettant de rechercher un widget par ID, puis de sauvegarder les modifications apportées à 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: 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
    // ...
  };
});

Bien que non inclus dans cet exemple, ces types de services flexibles pourraient également gérer facilement l'état.


Je n'ai pas le temps, mais si cela peut être utile, je pourrai vous présenter un simple Plunker plus tard.

43

Je ne suis pas tout à fait sûr du cas d'utilisation que vous essayez de satisfaire. Mais il est possible d'avoir une usine renvoyant des instances d'un objet. 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','string'),
           instanceB = new InstancedService.Instance('B','object');

           console.log(angular.equals(instanceA, instanceB));

});

JsFiddle

Mis à jour

Considérez la demande suivante pour 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 de singleton pouvant instancier de nouveaux objets.

et son exemple de renvoi 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 en raison du fait qu'il n'est pas nécessaire d'utiliser le mot clé new dans votre contrôleur. Il est encapsulé dans la méthode getInstance du service.

76
Jonathan Palumbo

Une autre méthode consiste à copier un objet 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 ensuite, 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 .

20
Evgenii

Je sais que ce message a déjà reçu une réponse, mais je pense toujours que certains scénarios légitimes nécessiteraient un service non-singleton. Supposons 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 mettre la logique est un service, mais que se passe-t-il si nous devons conserver 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','string');
       var instanceB = new nonSingletonService('B','object');

       console.log(angular.equals(instanceA, instanceB));

    }]);
8
msoltany

Voici mon exemple de service non singleton, Il provient d’un ORM sur lequel je travaille. Dans l'exemple, je montre un modèle de base (ModelFactory) dont je veux que les services ('utilisateurs', 'documents') héritent et soient étendus.

Dans mon ORM, ModelFactory injecte d'autres services pour fournir une fonctionnalité supplémentaire (requête, persistance, mappage de schéma) qui est mis en sandbox à l'aide du système de modules.

Dans l'exemple, l'utilisateur et le service de document ont la même fonctionnalité 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: '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
Nath

angular ne donne qu'une option singleton service/factory. Un moyen de contourner ce problème consiste à avoir un service d'usine qui construira 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 objet à la spécification de l'utilisateur (ajout de services ou 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);

}

puis, dans votre instance consommateur, vous avez besoin du service d'usine et appelez la méthode de construction de l'usine pour obtenir une nouvelle instance quand 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 qu'il offre l'opportunité d'injecter certains services spécifiques qui ne sont pas disponibles en usine. vous pouvez toujours avoir une injection sur l'instance d'usine pour qu'elle soit utilisée par toutes les instances de Model.

Remarque: je devais enlever du code afin de pouvoir faire des erreurs de contexte ... si vous avez besoin d'un exemple de code qui fonctionne, faites-le moi savoir.

Je pense que NG2 aura l'option d'injecter une nouvelle instance de votre service au bon endroit dans votre DOM afin que vous n'ayez pas besoin de construire votre propre implémentation d'usine. devra attendre et voir :)

1
Gadi

Je pense qu'il y a de bonnes raisons de créer une nouvelle instance d'un objet dans un service. Nous devrions également garder un esprit ouvert plutôt que de simplement dire que nous ne devrions jamais faire une telle chose, mais le singleton a été créé de cette manière 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 peux penser à un cas d'utilisation où vous avez un flux de travail quelconque, comme accepter un paiement et que vous avez plusieurs propriétés, mais que vous devez maintenant modifier leur type de paiement, car la carte de crédit du client a échoué et il doit fournir un autre formulaire 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'objet de paiement ou créer une nouvelle instance d'un objet dans le service . Mais, vous ne voudriez pas d'une nouvelle instance du service, vous ne voudriez pas non plus actualiser la page.

Je crois qu'une solution fournit un objet dans le service que vous pouvez créer une nouvelle instance et définir. Mais, pour être clair, la seule instance 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 peut ne pas être une méthode directe dans Angular, mais un modèle d'objet que vous pouvez gérer dans votre service.

A titre d’exemple, j’ai fait un bouton reset . (Ceci n’est pas testé, c’est vraiment une rapide idée d’un cas d’utilisation pour la création d’un nouvel objet dans 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)
            }
        }
    }
}]);
1
Creative Slave

Voici une autre approche du problème que je suis tout à fait satisfaite, en particulier lorsqu'elle est associée à Closure Compiler avec les 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);
0
James Wilson