AngularJSの非シングルトンサービス


90

AngularJSのドキュメントには、サービスはシングルトンであると明記されています。

AngularJS services are singletons

直観に反して、module.factoryシングルトンインスタンスも返します。

シングルトン以外のサービスには多くのユースケースがあることを考えると、Serviceのインスタンスを返すファクトリメソッドを実装して、ExampleService依存関係が宣言されるたびに、異なるインスタンスによって満たされるようにするための最良の方法は何ExampleServiceですか?


1
あなたがこれを行うことができると仮定して、あなたはすべきですか?他のAngular開発者は、依存関係が注入されたファクトリが常に新しいインスタンスを返すことを期待していませんでした。
Mark Rajcok 2013年

1
ドキュメントの問題だと思います。すべてのサービスがシングルトンになると予想されているので、これが最初からサポートされていなかったのは残念ですが、サービスをシングルトンに制限する理由はありません。
2013年

回答:


44

newこれが依存性注入を分解し始め、ライブラリが特にサードパーティにとって不自然に動作するようになるので、ファクトリーが有効な関数を返す必要があるとは思いません。つまり、シングルトンサービス以外のサービスの正当な使用例があるかどうかはわかりません。

同じことを実現するより良い方法は、ファクトリをAPIとして使用して、getterメソッドとsetterメソッドがアタッチされたオブジェクトのコレクションを返すことです。以下は、その種のサービスの使用がどのように機能するかを示すいくつかの疑似コードです。

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

これは、ウィジェットをIDで検索し、レコードに加えられた変更を保存できるようにするための単なる擬似コードです。

サービスの擬似コードは次のとおりです。

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

この例には含まれていませんが、この種の柔軟なサービスでも状態を簡単に管理できます。


現時点では時間はありませんが、役立つと思われる場合は、後で簡単なPlunkerをまとめてデモを行うことができます。


これは本当に面白いです。例は本当に役に立ちます。どうもありがとう。
気晴らし

これは面白い。それは角度のように機能するよう$resourceです。
ジョナサンパルンボ2013年

@JonathanPalumbo正解です-ngResourceによく似ています。実際、Pedrと私は、ngResourceと同様のアプローチを取ることを提案した別の質問で、この議論を接線的に開始しました。このように単純な例の場合、手動で実行する利点はありません-ngResourceまたはRestangularスムーズに動作します。しかし、それほど典型的ではないケースでは、このアプローチは理にかなっています。
Josh David Miller

4
@Pedr申し訳ありませんが、私はこれを忘れていました。これは非常にシンプルなデモです:plnkr.co/edit/Xh6pzd4HDlLRqITWuz8X
Josh David Miller

15
@JoshDavidMillerは、なぜ/何が「依存性注入を分解し、[なぜ/何]ライブラリが不自然に動作するのか」を特定できますか?
オキガン2014年

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コンテナにインスタンスとの依存関係を満たす方法はありません。唯一の方法は、インスタンスの生成に使用できるプロバイダーとの依存関係を満たすことです。
2013年

ありがとう。サービスでnewを使用する必要があるよりはましだと私は同意しますが、それでもまだ不十分だと思います。サービスに依存しているクラスが、それが提供されているサービスがシングルトンであるかどうかを認識または注意する必要があるのはなぜですか?これらのソリューションはどちらも、この事実を抽象化できず、DIコンテナーの内部にあるべきだと私が信じているものを依存関係に押し込んでいます。サービスを作成すると、作成者がシングルトンとして、または個別のインスタンスとして提供されるようにするかどうかを作成者が決定できるようにすることに害があることがわかります。
2013年

+1非常に役立ちます。この方法とngInfiniteScrollカスタム検索サービスを使用しているため、クリックイベントが発生するまで初期化を遅らせることができます。2番目のソリューションで更新された最初の回答のJSFiddle:jsfiddle.net/gavinfoley/G5ku5
GFoley83 '19

4
新しい演算子の使用が悪いのはなぜですか?あなたの目標が非シングルトンである場合、使用newは宣言的であり、どのサービスがシングルトンで、何がそうでないかをすぐにわかりやすいように感じます。オブジェクトが更新されているかどうかに基づいています。
j_walker_dev 2014

これは、質問が求めたもの、特に「更新された」付録を提供するため、これが答えになるはずです。
lukkea 2015

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

こちらがプランクです。


本当にすっきり!このトリックの背後にある危険を知っていますか?結局のところ、これはオブジェクトのangular.extendingにすぎないので、大丈夫だと思います。それにもかかわらず、サービスの数十のコピーを作成することは少し気が遠くなるように聞こえます。
vucalur 2016

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

    }]);

これは、ジョナサンが「更新された」付録ですべてをカプセル化することを除いて、ジョナサンパルンボの回答に非常に似ています。
lukkea 2015

1
シングルトン以外のサービスは永続的だと言っていますか?状態を保持する必要があります。逆のように見えます。
eran otzap

2

シングルトンサービス以外のサービスの例を以下に示します。これは、ORMが取り組んでいるものです。この例では、サービス(「ユーザー」、「ドキュメント」)が継承し、拡張する可能性があるベースモデル(ModelFactory)を示しています。

私の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

angularはシングルトンサービス/工場オプションのみを提供します。それを回避する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);

}

次に、コンシューマインスタンスでファクトリサービスが必要になり、ファクトリでbuildメソッドを呼び出して、必要なときに新しいインスタンスを取得します。

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

  }

ファクトリー・ステップでは使用できないいくつかの特定のサービスを注入する機会を提供することがわかります。すべてのモデルインスタンスで使用されるファクトリインスタンスで常にインジェクションを発生させることができます。

いくつかのコードを取り除く必要があったので、いくつかのコンテキストエラーが発生する可能性があることに注意してください...機能するコードサンプルが必要な場合はお知らせください。

NG2には、サービスの新しいインスタンスをDOMの適切な場所に挿入するオプションがあるため、独自のファクトリ実装を構築する必要がないと思います。待って見てください:)


素敵なアプローチ-$ serviceFactoryをnpmパッケージとして見たいです。あなたが好きなら、私はそれを構築して貢献者として追加できますか?
IamStalker 2016年

1

サービス内にオブジェクトの新しいインスタンスを作成するのには十分な理由があると思います。私たちはそのようなことをしてはならないというだけでなく、オープンマインドも保つべきですが、シングルトンはその理由でそのように作られました。コントローラーはアプリのライフサイクル内で頻繁に作成および破棄されますが、サービスは永続的である必要があります。

支払いを受け入れるなど、何らかのワークフローがあり、複数のプロパティが設定されているが、顧客のクレジットカードが失敗し、別の形式を提供する必要があるため、支払いタイプを変更する必要があるユースケースが考えられます。支払い。もちろん、これはアプリの作成方法に大きく関係します。支払いオブジェクトのすべてのプロパティをリセットするか、サービス内でオブジェクトの新しいインスタンスを作成できます。ただし、サービスの新しいインスタンスも、ページの更新も必要ありません。

ソリューションは、サービス内でオブジェクトを提供し、その新しいインスタンスを作成して設定できると信じています。ただし、明確にするために、コントローラーは何度も作成および破棄される可能性があるため、サービスの単一インスタンスは重要ですが、サービスには永続性が必要です。あなたが探しているのはAngular内の直接的な方法ではなく、サービス内で管理できるオブジェクトパターンかもしれません。

例として、リセットボタンを作りました。(これはテストされていません。実際には、サービス内に新しいオブジェクトを作成するためのユースケースの簡単なアイデアです。

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

これは、特に高度な最適化を有効にしてClosure Compilerと組み合わせて使用​​したときに、私が非常に満足した問題に対する別のアプローチです。

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);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.