AngularJS'de Tekil Olmayan Hizmetler


90

AngularJS, belgelerinde Hizmetlerin Tekil olduğunu açıkça belirtir:

AngularJS services are singletons

Tersine, module.factorybir Singleton örneği de döndürür.

Tekil olmayan hizmetler için çok sayıda kullanım durumu olduğu göz önüne alındığında, bir Hizmetin örneklerini döndürmek için fabrika yöntemini uygulamanın en iyi yolu nedir, böylece bir ExampleServicebağımlılık her bildirildiğinde, farklı bir örnekle karşılanır ExampleService?


1
Bunu yapabileceğinizi varsayarsak, değil mi? Diğer Angular geliştiricileri, bağımlılık eklenmiş bir fabrikanın her zaman yeni örnekler döndürmesini beklemiyorlardı.
Mark Rajcok

1
Sanırım bu bir dokümantasyon meselesi. Sanırım bunun kapı dışında desteklenmemesi utanç verici çünkü şu anda tüm Hizmetlerin Singleton olacağı beklentisi var, ancak onları Singleton'larla sınırlamak için bir neden görmüyorum.
Undistraction

Yanıtlar:


44

newBağımlılık enjeksiyonunu bozmaya başladığından ve kütüphane, özellikle üçüncü şahıslar için beceriksizce davranacağından, fabrikada başarılı bir işlev döndürmemiz gerektiğini düşünmüyorum . Kısacası, tekil olmayan hizmetler için herhangi bir yasal kullanım durumu olup olmadığından emin değilim.

Aynı şeyi başarmanın daha iyi bir yolu, fabrikayı, alıcı ve ayarlayıcı yöntemleri eklenmiş bir nesne koleksiyonunu döndürmek için bir API olarak kullanmaktır. İşte bu tür bir hizmetin nasıl çalıştığını gösteren bazı sözde kodlar:

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

Bu, bir widget'ı kimliğe göre aramak ve daha sonra kayda yapılan değişiklikleri kaydetmek için kullanılan sözde koddur.

İşte hizmet için bazı sözde kodlar:

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

Bu örneğe dahil edilmemesine rağmen, bu tür esnek hizmetler de durumu kolayca yönetebilir.


Şu anda vaktim yok, ama eğer yardımcı olacaksa daha sonra göstermek için basit bir Plunker oluşturabilirim.


Bu gerçekten ilginç. Bir örnek gerçekten yardımcı olacaktır. Çok teşekkürler.
Undistraction

Bu ilginç. Bir köşeye benzer şekilde işleyecek gibi görünüyor $resource.
Jonathan Palumbo

@JonathanPalumbo Haklısın - ngResource'a çok benziyor. Aslında, Pedr ve ben bu tartışmaya, ngResource'a benzer bir yaklaşım benimsemeyi önerdiğim başka bir soruda teğet olarak başladık. NgResource veya - Bu kadar basit bir örnek için elle yapıyor hiçbir yararı yoktur Restangular tıkırında çalışacak. Ancak tamamen tipik olmayan durumlar için bu yaklaşım mantıklı.
Josh David Miller

4
@Pedr Üzgünüm, bunu unuttum. İşte süper basit bir demo: plnkr.co/edit/Xh6pzd4HDlLRqITWuz8X
Josh David Miller

15
@JoshDavidMiller "bağımlılık enjeksiyonunu neden / neyin bozacağını ve [neden / ne] kitaplığın tuhaf davranacağını" belirtebilir misiniz?
okigan

77

Hangi kullanım durumunu tatmin etmeye çalıştığından tam olarak emin değilim. Ancak bir nesnenin fabrikada iade örneklerine sahip olmak mümkündür. Bunu ihtiyaçlarınıza uyacak şekilde değiştirebilmelisiniz.

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

Güncellenmiş

Tekil olmayan hizmetler için aşağıdaki isteği göz önünde bulundurun . Brian Ford'un not ettiği:

Tüm hizmetlerin tekil olduğu fikri, sizi yeni nesneler oluşturabilen tekil fabrikalar yazmaktan alıkoymaz.

ve fabrikalardan örnekleri iade etme örneği:

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

Ayrıca, newanahtar kelimeyi denetleyicinizde kullanmak zorunda olmadığınız için örneğinin daha üstün olduğunu iddia ediyorum . getInstanceHizmet yöntemi içinde özetlenmiştir .


Örnek için teşekkürler. Dolayısıyla, DI Container'ın bir örnekle bağımlılığı karşılamasını sağlamanın bir yolu yoktur. Tek yol, daha sonra örneği oluşturmak için kullanılabilecek bir sağlayıcıyla olan bağımlılığı tatmin etmesini sağlamaktır.
Undistraction

Teşekkürler. Bir hizmette yeniyi kullanmaktan daha iyi olduğuna katılıyorum, ancak yine de yetersiz olduğunu düşünüyorum. Hizmete bağlı olan sınıf, sağladığı hizmetin Singleton olup olmadığını neden bilmeli veya önemsemelidir? Her iki çözüm de bu gerçeği soyutlamakta başarısız oluyor ve DI kapsayıcısının içsel olması gerektiğine inandığım bir şeyi bağımlı olana itiyor. Bir Hizmet oluşturduğunuzda, oluşturucunun hizmetin tekli olarak mı yoksa ayrı örnekler olarak mı sunulmasını isteyip istemediğine karar vermesine izin vermenin zarar gördüğünü biliyorum.
Undistraction

+1 Çok yardımcı. Bu yaklaşımı ngInfiniteScrollve özel bir arama hizmetiyle kullanıyorum, böylece başlatma işlemini bir tıklama olayına kadar erteleyebilirim. JSFiddle 1. yanıt ikinci çözümle güncellendi: jsfiddle.net/gavinfoley/G5ku5
GFoley83

4
Yeni operatörü kullanmak neden kötü? Sanki amacınız tekil değilse, o zaman kullanmak newaçıklayıcıdır ve hangi hizmetlerin tekil olup neyin olmadığını hemen söylemek kolaydır. Bir nesnenin yeni olup olmadığına göre.
j_walker_dev

Cevap bu olmalı gibi görünüyor çünkü sorunun sorulduğu şeyi sunuyor - özellikle "Güncellenmiş" eki.
lukkea

20

Başka bir yol da servis nesnesini kopyalamaktır angular.extend().

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

ve sonra, örneğin, oyun kumandanızda

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

İşte bir serseri .


Gerçekten temiz! Bu numaranın arkasında herhangi bir tehlike olduğunu biliyor musunuz? Sonuçta, bir nesneyi genişletmek sadece köşeli, bu yüzden iyi olmalıyız. Yine de, bir hizmetin düzinelerce kopyasını çıkarmak biraz korkutucu geliyor.
vucalur

9

Bu yazının zaten yanıtlandığını biliyorum, ancak yine de tekil olmayan hizmete sahip olmanız gereken bazı meşru senaryolar olacağını düşünüyorum. Diyelim ki birkaç denetleyici arasında paylaşılabilen bazı yeniden kullanılabilir iş mantığı var. Bu senaryoda mantığı koymak için en iyi yer bir hizmet olacaktır, ancak ya yeniden kullanılabilir mantığımızda bir durumu korumamız gerekirse? O zaman, uygulamadaki farklı denetleyiciler arasında paylaşılabilmesi için tekil olmayan bir hizmete ihtiyacımız var. Bu hizmetleri şu şekilde uygulayacağım:

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

    }]);

Bu Jonathan Palumbo'nun cevabına çok benzer, ancak Jonathan her şeyi "Güncellenmiş" ekiyle özetlemektedir.
lukkea

1
Singleton olmayan bir hizmetin kalıcı olacağını mı söylüyorsunuz? Ve durumu korumalıyım, tam tersi gibi görünüyor.
eran otzap

2

İşte tekil olmayan bir hizmet örneğim, üzerinde çalıştığım bir ORM'den. Örnekte, hizmetlerin ('kullanıcılar', 'belgeler') devralmasını ve potansiyel olarak genişletmesini istediğim bir Temel Model (ModelFactory) gösteriyorum.

ORM ModelFactory'mde, modül sistemi kullanılarak sandbox'a alınan ekstra işlevsellik (sorgu, kalıcılık, şema eşleme) sağlamak için diğer hizmetleri enjekte ediyor.

Örnekte hem kullanıcı hem de belge hizmeti aynı işleve sahiptir ancak kendi bağımsız kapsamlarına sahiptir.

/*
    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

açısal, yalnızca tek bir servis / fabrika seçeneği verir. Bunun bir yolu, denetleyicinizin veya diğer tüketici örneklerinin içinde sizin için yeni bir örnek oluşturacak bir fabrika hizmetine sahip olmaktır. enjekte edilen tek şey, yeni örnekler yaratan sınıftır. burası, diğer bağımlılıkları enjekte etmek veya yeni nesnenizi kullanıcının özelliklerine göre başlatmak için iyi bir yerdir (servis veya yapılandırma ekleme)

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

}

daha sonra tüketici örneğinizde fabrika servisine ihtiyacınız var ve ihtiyacınız olduğunda yeni bir örnek almak için fabrikada oluşturma yöntemini çağırın

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

  }

Fabrika adımında bulunmayan bazı özel hizmetleri enjekte etmek için fırsat sağladığını görebilirsiniz. tüm Model örnekleri tarafından kullanılmak üzere fabrika örneğinde her zaman enjeksiyon gerçekleştirebilirsiniz.

Not Bazı kodlardan kurtulmak zorunda kaldım, böylece bazı bağlam hataları yapabilirim ... Eğer işe yarayan bir kod örneğine ihtiyacınız varsa bana bildirin.

NG2'nin, DOM'nuzda doğru yere hizmetinizin yeni bir örneğini enjekte etme seçeneğine sahip olacağına inanıyorum, bu nedenle kendi fabrika uygulamanızı oluşturmanıza gerek kalmayacak. beklemek ve görmek zorunda kalacak :)


güzel bir yaklaşım - bu $ serviceFactory'yi bir npm paketi olarak görmek istiyorum. İsterseniz onu inşa edip sizi katkıda bulunan olarak ekleyebilir miyim?
IamStalker

1

Bir hizmet içinde bir nesnenin yeni bir örneğini yaratmak için iyi bir neden olduğuna inanıyorum. Açık fikirli olmalıyız, sadece böyle bir şeyi asla yapmamamız gerektiğini söylemekten ziyade, tekli bir sebepten dolayı bu şekilde yapıldı . Denetleyiciler genellikle uygulamanın yaşam döngüsü içinde oluşturulur ve yok edilir, ancak hizmetlerin kalıcı olması gerekir.

Bir ödemeyi kabul etmek gibi bir tür iş akışınızın olduğu ve birden fazla mülkünüzün olduğu, ancak müşterinin kredi kartı başarısız olduğu ve müşterinin farklı bir yöntem sunması gerektiği için şimdi ödeme türünü değiştirmeniz gereken bir kullanım örneği düşünebilirim. ödeme. Elbette, bunun uygulamanızı oluşturma şeklinizle çok ilgisi var. Ödeme nesnesinin tüm özelliklerini sıfırlayabilir veya hizmet içindeki bir nesnenin yeni bir örneğini oluşturabilirsiniz . Ancak, hizmetin yeni bir örneğini istemezsiniz veya sayfayı yenilemek istemezsiniz.

Bir çözümün, hizmet içinde yeni bir örnek oluşturup ayarlayabileceğiniz bir nesne sağladığına inanıyorum. Ancak, daha açık olmak gerekirse, hizmetin tek örneği önemlidir, çünkü bir denetleyici birçok kez oluşturulabilir ve yok edilebilir, ancak hizmetlerin sürekliliği gerekir. Aradığınız şey, Angular içinde doğrudan bir yöntem değil, hizmetinizin içinde yönetebileceğiniz bir nesne kalıbı olabilir.

Örnek olarak, bir sıfırlama düğmesi yaptım . (Bu test edilmemiştir, bir hizmet içinde yeni bir nesne oluşturmak için gerçekten hızlı bir kullanım durumu fikridir.

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

İşte, özellikle gelişmiş optimizasyonların etkinleştirildiği Closure Compiler ile birlikte kullanıldığında, soruna oldukça memnun olduğum başka bir yaklaşım:

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);
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.