नॉन-सिंगलटन सर्विसेज एंगुलरजस में


90

AngularJS अपने प्रलेखन में स्पष्ट रूप से बताता है कि सेवाएँ एकल हैं:

AngularJS services are singletons

काउंटरिंटुइलाइटली, module.factoryएक सिंगलटन उदाहरण भी देता है।

यह देखते हुए कि गैर-सिंगलटन सेवाओं के लिए बहुत सारे उपयोग-मामले हैं, किसी सेवा के उदाहरणों को वापस करने के लिए फ़ैक्टरी विधि को लागू करने का सबसे अच्छा तरीका क्या है, ताकि हर बार एक ExampleServiceनिर्भरता घोषित होने पर, यह एक अलग उदाहरण से संतुष्ट हो ExampleService?


1
क्या आप ऐसा कर सकते हैं, यह मानकर? अन्य कोणीय डेवलपर्स एक निर्भरता-इंजेक्शन कारखाने से हर समय नए उदाहरणों की वापसी की उम्मीद नहीं करेंगे।
मार्क राजकोक

1
मुझे लगता है कि यह प्रलेखन के लिए एक मामला है। मुझे लगता है कि यह शर्म की बात है कि यह गेट से बाहर का समर्थन नहीं था क्योंकि अब एक उम्मीद है कि सभी सेवाएँ सिंग्लेटन्स होंगी, लेकिन मुझे उन्हें सिंग्लेटों तक सीमित करने का कोई कारण नहीं दिखता है।
रात ist:४

जवाबों:


44

मुझे नहीं लगता कि हमें कभी भी एक कारखाने को एक newसक्षम फ़ंक्शन वापस करना चाहिए क्योंकि यह निर्भरता इंजेक्शन को तोड़ने के लिए शुरू होता है और पुस्तकालय अजीब रूप से व्यवहार करेगा, खासकर तीसरे पक्ष के लिए। संक्षेप में, मुझे यकीन नहीं है कि गैर-सिंगलटन सेविंग्स के लिए कोई वैध उपयोग के मामले हैं।

एक ही चीज़ को पूरा करने का एक बेहतर तरीका फैक्ट्री का उपयोग एपीआई के रूप में वस्तुओं के संग्रह को वापस करने के लिए एक एपीआई के रूप में उपयोग करने के लिए है और उनसे जुड़ी सेटर विधियां हैं। यहाँ कुछ छद्म कोड दिखाया गया है कि इस तरह की सेवा का उपयोग करना कैसे काम कर सकता है:

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

यह केवल आईडी द्वारा विजेट देखने और फिर रिकॉर्ड में किए गए परिवर्तनों को सहेजने में सक्षम होने के लिए केवल छद्म कोड है।

यहाँ सेवा के लिए कुछ छद्म कोड दिए गए हैं:

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

हालांकि इस उदाहरण में शामिल नहीं है, इस प्रकार की लचीली सेवाएं भी आसानी से राज्य का प्रबंधन कर सकती हैं।


मेरे पास अभी समय नहीं है, लेकिन अगर यह मददगार होगा तो मैं एक सरल प्लंकर को बाद में प्रदर्शित करने के लिए एक साथ रख सकता हूं।


यह वाकई दिलचस्प है। एक उदाहरण वास्तव में मददगार होगा। बहुत बहुत धन्यवाद।

यह दिलचस्प है। ऐसा लगता है कि यह एक कोणीय के समान कार्य करेगा $resource
जोनाथन पालुम्बो

@JonathanPalumbo आप सही हैं - ngResource के समान। वास्तव में, पेड्र और मैंने इस चर्चा की शुरुआत एक और प्रश्न में की, जहाँ मैंने ngResource के समान एक दृष्टिकोण लेने का सुझाव दिया। एक उदाहरण के लिए यह इतना आसान है, इसे मैन्युअल रूप से करने का कोई फायदा नहीं है - ngResource या Restangular तैराकी काम करेगा। लेकिन ऐसे मामलों के लिए जो पूरी तरह से विशिष्ट नहीं हैं, यह दृष्टिकोण समझ में आता है।
जोश डेविड मिलर

4
@Pedr क्षमा करें, मैं इस बारे में भूल गया। यहाँ एक सुपर-सरल डेमो है: plnkr.co/edit/Xh6pzd4HDlLRqITWuz8X
जोश डेविड मिलर

15
@JoshDavidMiller आप निर्दिष्ट कर सकते हैं कि क्यों / क्या "निर्भरता इंजेक्शन को तोड़ देगा और [क्यों / क्या] पुस्तकालय अजीब व्यवहार करेगा"?
ओकेगन

77

मुझे पूरी तरह से यकीन नहीं है कि आप किस मामले में संतुष्ट करने की कोशिश कर रहे हैं। लेकिन किसी वस्तु का फ़ैक्टरी रिटर्न इंस्टेंस होना संभव है। आपको अपनी आवश्यकताओं के अनुरूप इसे संशोधित करने में सक्षम होना चाहिए।

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

अपडेट किया गया

गैर-एकल सेवाओं के लिए निम्नलिखित अनुरोध पर विचार करें । जिसमें ब्रायन फोर्ड नोट करते हैं:

यह विचार कि सभी सेवाएं एकल हैं, आपको सिंगलटन फैक्ट्रियों को लिखने से नहीं रोकती है जो नई वस्तुओं को तुरंत भेज सकती हैं।

और कारखानों से लौटने के उदाहरणों का उनका उदाहरण:

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

मैं यह भी तर्क दूंगा कि उनका उदाहरण इस तथ्य से बेहतर है कि आपको newअपने नियंत्रक में कीवर्ड का उपयोग करने की आवश्यकता नहीं है । यह getInstanceसेवा की विधि के भीतर समझाया गया है।


उदाहरण के लिए धन्यवाद। इसलिए DI कंटेनर को एक उदाहरण के साथ निर्भरता को संतुष्ट करने का कोई तरीका नहीं है। एकमात्र तरीका यह है कि यह एक प्रदाता के साथ निर्भरता को संतुष्ट करता है जिसे फिर उदाहरण के लिए उपयोग किया जा सकता है?
अंडरस्टैंडिंग

धन्यवाद। मैं मानता हूं कि किसी सेवा में नए का उपयोग करने से बेहतर है, हालांकि मुझे लगता है कि यह अभी भी कम है। वर्ग को उस सेवा पर निर्भर क्यों होना चाहिए जो यह जानता है या देखभाल करता है कि जिस सेवा के साथ आपूर्ति की जा रही है वह सिंग्लटन है या नहीं? ये दोनों समाधान इस तथ्य को अमूर्त करने में विफल रहते हैं और इस बात पर जोर दे रहे हैं कि मेरा मानना ​​है कि डीआई कंटेनर पर निर्भर में आंतरिक होना चाहिए। जब आप एक सेवा बनाते हैं, तो मुझे पता है कि निर्माता को यह तय करने की अनुमति देने में कोई हर्ज है कि वे इसे एकल के रूप में या अलग-अलग उदाहरणों के रूप में आपूर्ति करना चाहते हैं या नहीं।

+1 बहुत मदद। मैं इस दृष्टिकोण के साथ ngInfiniteScrollऔर एक कस्टम खोज सेवा का उपयोग कर रहा हूं ताकि मैं कुछ क्लिक इवेंट तक प्रारंभिककरण में देरी कर सकूं। JSFiddle 1 उत्तर के दूसरे समाधान के साथ अपडेट किया गया: jsfiddle.net/gavinfoley/G5ku5
GFoley83

4
नए ऑपरेटर का उपयोग खराब क्यों है? मुझे लगता है कि यदि आपका लक्ष्य एक गैर एकल है, तो newघोषणात्मक का उपयोग करना और इसके बारे में तुरंत बताना आसान है कि क्या सेवा एकल हैं और क्या नहीं है। अगर किसी वस्तु को नया बनाया जा रहा है तो उसके आधार पर।
j_walker_dev

ऐसा लगता है कि यह उत्तर होना चाहिए क्योंकि यह बचाता है जो प्रश्न के लिए पूछा गया है - विशेष रूप से "अपडेटेड" परिशिष्ट।
लुकिया

20

एक अन्य तरीका है सेवा ऑब्जेक्ट को कॉपी करना angular.extend()

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

और फिर, उदाहरण के लिए, अपने नियंत्रक में

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

यहां एक प्लांक है


वास्तव में साफ! क्या आप इस ट्रिक के पीछे किसी खतरे के बारे में जानते हैं? सब के बाद, यह सिर्फ एक वस्तु है। मुझे लगता है कि हम ठीक होना चाहिए। फिर भी, सेवा की दर्जनों प्रतियां बनाने से डर लगता है।
वुकालुर

9

मुझे पता है कि यह पोस्ट पहले ही उत्तर दे दी गई है लेकिन मुझे अभी भी लगता है कि कुछ वैध परिदृश्य होंगे जो आपको गैर-सिंगलटन सेवा के लिए चाहिए। मान लीजिए कि कुछ पुन: प्रयोज्य व्यवसाय तर्क हैं, जिन्हें कई नियंत्रकों के बीच साझा किया जा सकता है। इस परिदृश्य में तर्क रखने के लिए सबसे अच्छी जगह एक सेवा होगी, लेकिन क्या होगा अगर हमें अपने पुन: प्रयोज्य तर्क में कुछ राज्य रखने की आवश्यकता है? फिर हमें गैर-सिंगलटन सेवा की आवश्यकता होती है, ताकि ऐप में विभिन्न नियंत्रकों को साझा किया जा सके। यह है कि मैं इन सेवाओं को कैसे लागू करूंगा:

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

    }]);

यह बहुत ही जोनाथन पालुम्बो के जवाब के समान है, सिवाय इसके कि जोनाथन अपने "अपडेटेड" परिशिष्ट के साथ सब कुछ संलग्न करता है।
लुकिया

1
क्या आप कह रहे हैं कि एक गैर सिंगलटन सेवा लगातार बनी रहेगी। और राज्य रखना चाहिए, थोड़े अन्य तरह से चारों ओर लगता है।
इरान ओटज़ैप

2

यहाँ एक गैर एकल सेवा का मेरा उदाहरण है, यह एक ORM im से काम कर रहा है। उदाहरण में मैं एक बेस मॉडल (मॉडलफैक्टिव) दिखाता हूं जिसे मैं चाहता हूं कि सेवाओं ('उपयोगकर्ता', 'दस्तावेज') को विरासत और संभावित विस्तार के लिए।

मेरे ORM ModelFactory में अतिरिक्त कार्यक्षमता (क्वेरी, दृढ़ता, स्कीमा मैपिंग) प्रदान करने के लिए अन्य सेवाओं को इंजेक्ट किया गया है जो मॉड्यूल सिस्टम का उपयोग करके सैंडबॉक्स किया गया है।

उदाहरण में उपयोगकर्ता और दस्तावेज़ सेवा दोनों की कार्यक्षमता समान है लेकिन उनकी अपनी स्वतंत्र स्कोप हैं।

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

1

कोणीय केवल एक सिंगलटन सेवा / फैक्टरी विकल्प देता है। इसके चारों ओर एक तरह से एक कारखाना सेवा है जो आपके नियंत्रक या अन्य उपभोक्ता उदाहरणों के अंदर आपके लिए एक नया उदाहरण बनाएगी। केवल एक चीज जिसे इंजेक्ट किया जाता है वह वह वर्ग है जो नए उदाहरण बनाता है। यह अन्य निर्भरताओं को इंजेक्ट करने के लिए या उपयोगकर्ता के विनिर्देश में अपनी नई वस्तु को आरंभ करने के लिए एक अच्छी जगह है (सेवाओं या कॉन्फ़िगरेशन को जोड़ना)

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

}

तब आपके उपभोक्ता उदाहरण में आपको फ़ैक्टरी सेवा की आवश्यकता होती है और ज़रूरत पड़ने पर नया उदाहरण प्राप्त करने के लिए फ़ैक्टरी पर निर्मित विधि को कॉल करें

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

  }

आप देख सकते हैं कि यह कुछ विशिष्ट सेवाओं को इंजेक्ट करने के लिए opperatiunity प्रदान करता है जो फ़ैक्टरी चरण में उपलब्ध नहीं हैं। आप हमेशा कारखाने उदाहरण पर इंजेक्शन को सभी मॉडल उदाहरणों द्वारा उपयोग किया जा सकता है।

नोट मुझे कुछ कोड छीनने थे इसलिए मुझे कुछ संदर्भ त्रुटियां हो सकती हैं ... यदि आपको एक कोड नमूने की आवश्यकता है जो मुझे बताए।

मेरा मानना ​​है कि NG2 के पास आपके DOM में सही जगह पर अपनी सेवा के एक नए उदाहरण को इंजेक्ट करने का विकल्प होगा, इसलिए आपको अपने कारखाने के कार्यान्वयन का निर्माण करने की आवश्यकता नहीं है। इंतजार करना होगा और देखना होगा :)


अच्छा दृष्टिकोण - मैं उस $ सेवा को एक npm पैकेज के रूप में देखना चाहूंगा। यदि आप चाहें, तो मैं इसे बना सकता हूं और आपको एक योगदानकर्ता के रूप में जोड़ सकता हूं।
IamStalker

1

मेरा मानना ​​है कि किसी सेवा के भीतर किसी वस्तु का नया उदाहरण बनाने का अच्छा कारण है। हमें खुले दिमाग के साथ-साथ केवल यह कहना चाहिए कि हमें ऐसा काम कभी नहीं करना चाहिए, लेकिन एक कारण के लिए सिंगलटन को इस तरह से बनाया गया था । नियंत्रकों को ऐप के जीवनचक्र के भीतर अक्सर बनाया और नष्ट किया जाता है, लेकिन सेवाओं को लगातार होना चाहिए।

मैं एक ऐसे उपयोग के मामले के बारे में सोच सकता हूं, जहां आपके पास किसी प्रकार का कार्य प्रवाह हो, जैसे भुगतान स्वीकार करना और आपके पास कई गुण सेट हैं, लेकिन अब उनका भुगतान प्रकार बदलना होगा क्योंकि ग्राहक का क्रेडिट कार्ड विफल हो गया है और उन्हें एक अलग रूप प्रदान करना होगा भुगतान। बेशक, यह आपके ऐप को बनाने के तरीके के साथ बहुत कुछ करता है। आप भुगतान ऑब्जेक्ट के लिए सभी गुणों को रीसेट कर सकते हैं , या आप सेवा के भीतर किसी ऑब्जेक्ट की एक नई आवृत्ति बना सकते हैं । लेकिन, आप सेवा का नया उदाहरण नहीं चाहेंगे, न ही आप पृष्ठ को रीफ्रेश करना चाहेंगे।

मेरा मानना ​​है कि एक समाधान सेवा के भीतर एक वस्तु प्रदान कर रहा है जिसे आप एक नया उदाहरण बना सकते हैं और सेट कर सकते हैं। लेकिन, बस स्पष्ट होने के लिए, सेवा का एकल उदाहरण महत्वपूर्ण है क्योंकि एक नियंत्रक को कई बार बनाया और नष्ट किया जा सकता है, लेकिन सेवाओं को दृढ़ता की आवश्यकता होती है। आप जो खोज रहे हैं वह एंगुलर के भीतर एक सीधा तरीका नहीं हो सकता है, लेकिन एक वस्तु पैटर्न जिसे आप अपनी सेवा के अंदर प्रबंधित कर सकते हैं।

एक उदाहरण के रूप में, मेरे पास एक रीसेट बटन है। (यह परीक्षण नहीं किया गया है, इसकी वास्तव में एक सेवा के भीतर एक नई वस्तु बनाने के लिए उपयोग के मामले का सिर्फ एक त्वरित विचार है।

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

इस समस्या के बारे में एक और दृष्टिकोण है जिससे मैं काफी संतुष्ट था, विशेषकर जब क्लोजर कंपाइलर के साथ संयोजन में उपयोग किया जाता था जिसमें उन्नत अनुकूलन सक्षम थे:

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);
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.