web-dev-qa-db-ja.com

サービス内の角度の$ rootScopeへのオブジェクトのバインドは悪いですか?

角度では、サービスを介してアプリケーション全体で公開されるオブジェクトがあります。

そのオブジェクトのフィールドの一部は動的であり、サービスを使用するコントローラーのバインディングによって通常どおり更新されます。ただし、一部のフィールドは計算されたプロパティであり、他のフィールドに依存しているため、動的に更新する必要があります。

以下に簡単な例を示します(jsbin here で機能しています)。私のサービスモデルは、フィールドab、およびcを公開しています。ここで、cは、calcC()a + Bから計算されます。実際のアプリケーションでは、計算ははるかに複雑ですが、本質はここにあります。

これを機能させる唯一の方法は、サービスモデルを$rootScopeにバインドし、$rootScope.$watchを使用して、aまたはbを変更するコントローラーを監視することです。 cを再計算します。しかし、それはugいようです。 これを行うより良い方法はありますか?


2番目の懸念事項はパフォーマンスです。私の完全なアプリケーションでは、abはオブジェクトの大きなリストであり、cに集約されます。これは、$rootScope.$watch関数が多くの詳細な配列チェックを実行することを意味し、パフォーマンスを損なうように聞こえます。

これはすべて、BackBoneのイベントアプローチで機能します。これにより、再計算が可能な限り削減されますが、angularはイベントアプローチではうまく機能しないようです。それについてのどんな考えも素晴らしいでしょう。


これがアプリケーションの例です。

var myModule = angular.module('myModule', []);

//A service providing a model available to multiple controllers
myModule.factory('aModel', function($rootScope) {
  var myModel = {
    a: 10,
    b: 10,
    c: null
  };

  //compute c from a and b
  calcC = function() {
    myModel.c = parseInt(myModel.a, 10) * parseInt(myModel.b, 10);
  };

  $rootScope.myModel = myModel;
  $rootScope.$watch('myModel.a', calcC);
  $rootScope.$watch('myModel.b', calcC);

  return myModel;
});


myModule.controller('oneCtrl', function($scope, aModel) {
  $scope.aModel = aModel;
});

myModule.controller('twoCtrl', function($scope, aModel) {
  $scope.anotherModel = aModel;
});
32
latentflip

高いレベルからは、bmleiteによる回答に同意しますが($ rootScopeが使用されているため、$ watchを使用することはユースケースに適しているようです)、別のアプローチを提案したいと思います。

$rootScope.$broadcastを使用して$rootScope.$onリスナーに変更をプッシュすると、c値が再計算されます。

これは、手動で行うことができます-aまたはbの値を積極的に変更する場合、または場合によっては更新の頻度を調整するための短いタイムアウトでも可能です。それからさらに一歩進むと、サービスに「ダーティ」フラグが作成され、cが必要な場合にのみ計算されます。

明らかに、このようなアプローチは、コントローラー、ディレクティブなどの再計算により多くの関与を意味します-しかし、aまたはbのすべての可能な変更に更新をバインドしたくない場合、問題「線を引く場所」の問題になります。

7
Alex Osborn

認めなければならない、あなたの質問を初めて読み、自分の考えた例を見て「これはただ間違っている」再び、私は思ったほど悪くないことに気付きました。

事実に直面しましょう。アプリケーション全体で何かを共有したい場合は、$rootScopeが使用されるので、それを置くのに最適な場所です。もちろん、注意する必要があります。これはすべてのスコープ間で共有されているため、不注意で変更したくないためです。しかし、それに直面してみましょう、それは本当の問題ではありません。ネストされたコントローラー(子スコープは親スコーププロパティを継承するため)と非分離スコープディレクティブを使用するときは、すでに注意する必要があります。 「問題」はすでにそこにあり、このアプローチに従わない言い訳として使用すべきではありません。

$watchを使用することも良い考えのようです。フレームワークが既に無料で提供しているものであり、まさに必要なことを行います。では、なぜ車輪を再発明したのでしょうか?この考え方は、基本的に「変更」イベントアプローチと同じです。

パフォーマンスレベルでは、アプローチは実際には「重い」場合がありますが、aプロパティとbプロパティを更新する頻度に常に依存します。たとえば、入力ボックスのng-modelとしてaまたはbを設定した場合(jsbinの例のように)、cは毎回再計算されますユーザーが何かを入力する時...それは明らかに過剰処理です。ソフトアプローチを使用し、必要な場合にのみaおよび/またはbを更新する場合、パフォーマンスの問題はありません。 'change'イベントまたはsetter&getterアプローチを使用してcを再計算するのと同じです。ただし、リアルタイムで(つまり、ユーザーが入力している間)cを再計算する必要がある場合、パフォーマンスの問題は常に存在し、$rootScopeを使用しているという事実ではありません。または$watchは改善に役立ちます。

再開私の意見では、あなたのアプローチは悪くありません(まったく!)、$rootScopeプロパティに注意し、「リアルタイム」処理を避けてください。

4
bmleite

これは1年半後だと思いますが、最近同じ決断を下したので、汚染なしで「私のために働いた」という別の答えを提供すると思いました$rootScope任意の新しい値。

ただし、まだrely on $rootScope。ただし、メッセージをブロードキャストするのではなく、単に$rootScope.$digest

基本的なアプローチは、単一の複雑なモデルオブジェクトをangularサービスのフィールドとして提供することです。適切と思われる以上のものを提供し、同じ基本アプローチに従って、各フィールドを確認します。参照が変更されない複合オブジェクトをホストします。つまり、フィールドに新しい複合オブジェクトを再割り当てせず、代わりにこのモデルオブジェクトのフィールドのみを変更します。

var myModule = angular.module('myModule', []);

//A service providing a model available to multiple controllers
myModule.service('aModel', function($rootScope, $timeout) {
  var myModel = {
    a: 10,
    b: 10,
    c: null
  };

  //compute c from a and b
  calcC = function() {
    myModel.c = parseInt(myModel.a, 10) * parseInt(myModel.b, 10);
  };
  calcC();

  this.myModel = myModel;

  // simulate an asynchronous method that frequently changes the value of myModel. Note that
  // not appending false to the end of the $timeout would simply call $digest on $rootScope anyway
  // but we want to explicitly not do this for the example, since most asynchronous processes wouldn't 
  // be operating in the context of a $digest or $apply call. 
  var delay = 2000; // 2 second delay
  var func = function() {
    myModel.a = myModel.a + 10;
    myModel.b = myModel.b + 5;
    calcC();
    $rootScope.$digest();
    $timeout(func, delay, false);
  };
  $timeout(func, delay, false);
});

サービスのモデルに依存したいコントローラーは、そのモデルをそのスコープに自由に挿入できます。例えば:

$scope.theServiceModel = aModel.myModel;

そして、フィールドに直接バインドします:

<div>A: {{theServiceModel.a}}</div>
<div>B: {{theServiceModel.b}}</div>
<div>C: {{theServiceModel.c}}</div>

また、サービス内で値が更新されると、すべてが自動的に更新されます。

これは、Objectから継承する型(配列、カスタムオブジェクトなど)をスコープに直接注入する場合にのみ機能することに注意してください。文字列や数値などのプリミティブ値をスコープに直接注入する場合(例:$scope.a = aModel.myModel.a)コピーをスコープに入れるため、更新時に新しい値を受け取ることはありません。通常、ベストプラクティスは、私の例で行ったように、モデルオブジェクト全体をスコープに挿入することです。

3
Randolpho

一般的に、これはおそらく良い考えではありません。また、リファクタリングがより困難で面倒になる以外の理由でモデルの実装をすべての呼び出し元に公開することは(一般的に)悪い習慣です。両方を簡単に解決できます。

myModule.factory( 'aModel', function () {
  var myModel = { a: 10, b: 10 };

  return {
    get_a: function () { return myModel.a; },
    get_b: function () { return myModel.a; },
    get_c: function () { return myModel.a + myModel.b; }
  };
});

それがベストプラクティスのアプローチです。それはうまくスケーリングし、必要なときにだけ呼び出され、$rootScopeを汚染しません。

PS:cまたはaが設定されているときにbを更新して、get_cへのすべての呼び出しで再計算を回避することもできます。最適な方法は、実装の詳細に依存します。

2