Layanan Non-Singleton di AngularJS

90

AngularJS dengan jelas menyatakan dalam dokumentasinya bahwa Services is Singletons:

AngularJS services are singletons

Berlawanan dengan intuisi, module.factoryjuga mengembalikan instance Singleton.

Mengingat bahwa ada banyak kasus penggunaan untuk layanan non-tunggal, apa cara terbaik untuk mengimplementasikan metode pabrik untuk mengembalikan contoh Layanan, sehingga setiap kali sebuah ExampleServiceketergantungan dideklarasikan, itu dipenuhi oleh contoh yang berbeda ExampleService?

Tidak ada gangguan
sumber
1
Dengan asumsi Anda bisa melakukan ini, bukan? Pengembang Angular lainnya tidak akan mengharapkan pabrik yang diinjeksi ketergantungan akan mengembalikan instans baru setiap saat.
Mark Rajcok
1
Saya kira itu masalah dokumentasi. Saya pikir itu memalukan bahwa ini tidak didukung keluar dari gerbang karena sekarang ada harapan bahwa semua Layanan akan menjadi Lajang, tetapi saya tidak melihat alasan untuk membatasi mereka ke Lajang.
gangguan

Jawaban:

44

Saya tidak berpikir kita harus memiliki newfungsi yang dapat dikembalikan pabrik karena ini mulai merusak injeksi ketergantungan dan perpustakaan akan berperilaku canggung, terutama untuk pihak ketiga. Singkatnya, saya tidak yakin ada kasus penggunaan yang sah untuk layanan non-tunggal.

Cara yang lebih baik untuk mencapai hal yang sama adalah dengan menggunakan pabrik sebagai API untuk mengembalikan kumpulan objek dengan metode pengambil dan penyetel yang dilampirkan padanya. Berikut ini beberapa pseudo-code yang menunjukkan bagaimana penggunaan layanan semacam itu dapat bekerja:

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

Ini hanyalah pseudo-code untuk mencari widget berdasarkan ID dan kemudian dapat menyimpan perubahan yang dibuat pada record.

Berikut beberapa pseudo-code untuk layanan tersebut:

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

Meskipun tidak termasuk dalam contoh ini, jenis layanan fleksibel ini juga dapat dengan mudah mengelola status.


Saya tidak punya waktu sekarang, tetapi jika itu akan membantu saya bisa mengumpulkan Plunker sederhana nanti untuk didemonstrasikan.

Josh David Miller
sumber
Ini sangat menarik. Sebuah contoh akan sangat membantu. Terima kasih banyak.
gangguan
Ini menarik. Sepertinya itu akan berfungsi mirip dengan sudut $resource.
Jonathan Palumbo
@JonathanPalumbo Anda benar - sangat mirip dengan ngResource. Faktanya, Pedr dan saya memulai diskusi ini secara bersinggungan di pertanyaan lain di mana saya menyarankan untuk mengambil pendekatan yang mirip dengan ngResource. Untuk contoh sesederhana ini, tidak ada keuntungan melakukannya secara manual - ngResource atau Restangular akan bekerja dengan lancar . Tetapi untuk kasus yang tidak terlalu umum, pendekatan ini masuk akal.
Josh David Miller
4
@Pedr Maaf, saya lupa tentang ini. Ini demo yang sangat sederhana: plnkr.co/edit/Xh6pzd4HDlLRqITWuz8X
Josh David Miller
15
@JoshDavidMiller dapatkah Anda menentukan mengapa / apa yang akan "memecah injeksi ketergantungan dan [mengapa / apa] perpustakaan akan berperilaku canggung"?
okigan
77

Saya tidak sepenuhnya yakin kasus penggunaan apa yang Anda coba penuhi. Tetapi dimungkinkan untuk memiliki contoh pengembalian pabrik dari suatu objek. Anda harus dapat memodifikasi ini agar sesuai dengan kebutuhan Anda.

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

Diperbarui

Pertimbangkan permintaan berikut untuk layanan non-tunggal . Di mana Brian Ford mencatat:

Gagasan bahwa semua layanan adalah lajang tidak menghentikan Anda dari menulis pabrik tunggal yang dapat membuat contoh objek baru.

dan contoh pengembalian contoh dari pabrik:

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

Saya juga berpendapat bahwa contohnya lebih unggul karena fakta bahwa Anda tidak harus menggunakan newkata kunci dalam pengontrol Anda. Itu dikemas dalam getInstancemetode layanan.

Jonathan Palumbo
sumber
Terima kasih atas contohnya. Jadi tidak ada cara agar DI Container memenuhi ketergantungan dengan sebuah instance. Satu-satunya cara adalah membuatnya memenuhi ketergantungan dengan penyedia yang kemudian dapat digunakan untuk menghasilkan instance?
gangguan
Terima kasih. Saya setuju bahwa itu lebih baik daripada harus menggunakan yang baru dalam suatu layanan, namun saya pikir itu masih kurang. Mengapa kelas yang bergantung pada layanan mengetahui atau peduli bahwa layanan yang disuplai adalah Singleton atau bukan? Kedua solusi ini gagal mengabstraksi fakta ini dan mendorong sesuatu yang saya yakini harus internal ke wadah DI menjadi dependen. Ketika Anda membuat Layanan, saya melihat ada salahnya mengizinkan pencipta untuk memutuskan apakah mereka ingin itu diberikan sebagai tunggal atau sebagai contoh terpisah.
gangguan
+1 Sangat membantu. Saya menggunakan pendekatan ini dengan ngInfiniteScrolldan layanan pencarian khusus sehingga saya dapat menunda inisialisasi hingga beberapa peristiwa klik. JSFiddle jawaban pertama diperbarui dengan solusi kedua: jsfiddle.net/gavinfoley/G5ku5
GFoley83
4
Mengapa menggunakan operator baru itu buruk? Saya merasa jika tujuan Anda adalah non-tunggal maka menggunakan newdeklaratif dan mudah untuk langsung mengetahui layanan apa yang lajang dan apa yang tidak. Berdasarkan apakah suatu objek sedang diperbarui.
j_walker_dev
sepertinya ini harus menjadi jawaban karena menyampaikan apa yang ditanyakan - terutama lampiran "Diperbarui".
lukkea
20

Cara lain adalah dengan menyalin objek layanan dengan angular.extend().

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

dan kemudian, misalnya, di pengontrol Anda

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

Berikut adalah memetik .

Evgenii
sumber
Sangat rapi! Apakah Anda mengetahui bahaya di balik trik ini? Bagaimanapun, itu hanya sudut. Memperpanjang suatu objek, jadi saya kira kita harus baik-baik saja. Namun demikian, membuat lusinan salinan layanan terdengar agak menakutkan.
vucalur
9

Saya tahu posting ini telah dijawab tetapi saya masih berpikir akan ada beberapa skenario yang sah bahwa Anda perlu memiliki layanan non-tunggal. Katakanlah ada beberapa logika bisnis yang dapat digunakan kembali yang dapat dibagi antara beberapa pengontrol. Dalam skenario ini, tempat terbaik untuk meletakkan logika adalah layanan, tetapi bagaimana jika kita perlu mempertahankan beberapa status dalam logika yang dapat digunakan kembali? Kemudian kita membutuhkan layanan non-tunggal sehingga dapat dibagikan di berbagai pengontrol dalam aplikasi. Beginilah cara saya menerapkan layanan ini:

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.dll
sumber
Ini sangat mirip dengan jawaban Jonathan Palumbo kecuali Jonathan merangkum semuanya dengan lampiran "Diperbarui".
lukkea
1
Apakah Anda mengatakan bahwa layanan non Singleton akan tetap ada. Dan harus menjaga keadaan,, sepertinya seperti sebaliknya.
eran otzap
2

Inilah contoh saya dari layanan non tunggal, Ini dari ORM yang sedang saya kerjakan. Dalam contoh saya menunjukkan Model Dasar (ModelFactory) yang saya ingin layanan ('pengguna', 'dokumen') untuk mewarisi dan potensi untuk diperluas.

Dalam ORM ModelFactory saya menyuntikkan layanan lain untuk menyediakan fungsionalitas tambahan (kueri, ketekunan, pemetaan skema) yang di-sandbox menggunakan sistem modul.

Dalam contoh, baik pengguna dan layanan dokumen memiliki fungsi yang sama tetapi memiliki cakupannya sendiri-sendiri.

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

angular hanya memberikan opsi layanan / pabrik tunggal . salah satu cara untuk mengatasinya adalah dengan memiliki layanan pabrik yang akan membuat instans baru untuk Anda di dalam pengontrol atau instans konsumen lainnya. satu-satunya hal yang dimasukkan adalah kelas yang membuat instance baru. ini adalah tempat yang baik untuk memasukkan dependensi lain atau untuk menginisialisasi objek baru Anda ke spesifikasi pengguna (menambahkan layanan atau konfigurasi)

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

}

maka dalam contoh konsumen Anda, Anda memerlukan layanan pabrik dan memanggil metode pembangunan di pabrik untuk mendapatkan contoh baru saat Anda membutuhkannya

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

  }

Anda dapat melihatnya memberikan kesempatan untuk menyuntikkan beberapa layanan tertentu yang tidak tersedia di langkah pabrik. Anda selalu dapat mengalami injeksi pada instans pabrik untuk digunakan oleh semua instans Model.

Catatan Saya harus melepas beberapa kode jadi saya mungkin membuat beberapa kesalahan konteks ... jika Anda memerlukan sampel kode yang berfungsi, beri tahu saya.

Saya percaya bahwa NG2 akan memiliki opsi untuk memasukkan contoh baru dari layanan Anda di tempat yang tepat di DOM Anda sehingga Anda tidak perlu membangun implementasi pabrik Anda sendiri. harus menunggu dan melihat :)

Gadi
sumber
pendekatan yang bagus - Saya ingin melihat bahwa $ serviceFactory sebagai paket npm. Jika Anda suka, saya dapat membangunnya dan menambahkan Anda sebagai kontributor?
IamStalker
1

Saya yakin ada alasan bagus untuk membuat instance baru dari sebuah objek dalam layanan. Kita juga harus tetap berpikiran terbuka daripada hanya mengatakan kita tidak boleh melakukan hal seperti itu, tetapi singleton dibuat seperti itu karena suatu alasan . Pengontrol sering dibuat dan dihancurkan dalam siklus aplikasi, tetapi layanan harus tetap ada.

Saya dapat memikirkan kasus penggunaan di mana Anda memiliki beberapa jenis alur kerja, seperti menerima pembayaran dan Anda memiliki beberapa properti yang ditetapkan, tetapi sekarang harus mengubah jenis pembayaran mereka karena kartu kredit pelanggan gagal dan mereka perlu memberikan bentuk lain dari pembayaran. Tentu saja, ini banyak hubungannya dengan cara Anda membuat aplikasi. Anda dapat mengatur ulang semua properti untuk objek pembayaran, atau Anda dapat membuat contoh baru dari objek dalam layanan . Namun, Anda tidak ingin layanan baru, atau menyegarkan halaman.

Saya yakin solusi menyediakan objek dalam layanan yang dapat Anda buat dan setel instance baru. Namun, untuk memperjelas, satu contoh layanan penting karena pengontrol dapat dibuat dan dimusnahkan berkali-kali, tetapi layanan tersebut membutuhkan ketekunan. Apa yang Anda cari mungkin bukan metode langsung dalam Angular, tetapi pola objek yang dapat Anda kelola di dalam layanan Anda.

Sebagai contoh, saya telah membuat tombol reset . (Ini belum diuji, ini benar-benar hanya gagasan singkat tentang kasus penggunaan untuk membuat objek baru dalam layanan.

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)
            }
        }
    }
}]);
Budak Kreatif
sumber
0

Berikut pendekatan lain untuk masalah yang saya cukup puas, khususnya saat digunakan dalam kombinasi dengan Closure Compiler dengan pengoptimalan lanjutan diaktifkan:

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);
James Wilson
sumber