web-dev-qa-db-ja.com

AngularJS:サービス変数を監視する方法

私はサービスを持っている、と言う:

factory('aService', ['$rootScope', '$resource', function ($rootScope, $resource) {
  var service = {
    foo: []
  };

  return service;
}]);

そして私はfooを使ってHTMLでレンダリングされるリストを制御したいと思います。

<div ng-controller="FooCtrl">
  <div ng-repeat="item in foo">{{ item }}</div>
</div>

aService.fooがいつ更新されるかをコントローラが検出できるようにするために、このパターンをまとめてコントローラの$scopeにaServiceを追加してから$scope.$watch()を使用します。

function FooCtrl($scope, aService) {                                                                                                                              
  $scope.aService = aService;
  $scope.foo = aService.foo;

  $scope.$watch('aService.foo', function (newVal, oldVal, scope) {
    if(newVal) { 
      scope.foo = newVal;
    }
  });
}

これは長年に渡り、私はサービスの変数を使用するすべてのコントローラでそれを繰り返してきました。シェア変数を監視するためのより良い方法はありますか?

404
berto

$watchの専制政治とオーバーヘッドを避けたいのなら、いつでも古き良き観察者パターンを使うことができます。

サービスでは:

factory('aService', function() {
  var observerCallbacks = [];

  //register an observer
  this.registerObserverCallback = function(callback){
    observerCallbacks.Push(callback);
  };

  //call this when you know 'foo' has been changed
  var notifyObservers = function(){
    angular.forEach(observerCallbacks, function(callback){
      callback();
    });
  };

  //example of when you may want to notify observers
  this.foo = someNgResource.query().$then(function(){
    notifyObservers();
  });
});

そしてコントローラーで:

function FooCtrl($scope, aService){
  var updateFoo = function(){
    $scope.foo = aService.foo;
  };

  aService.registerObserverCallback(updateFoo);
  //service now in control of updating foo
};
275
dtheodor

このようなシナリオでは、複数の未知のオブジェクトが変更に関心を持つ可能性があり、変更されているアイテムから$rootScope.$broadcastを使用します。

リスナーの独自のレジストリを作成するのではなく(さまざまな$ destroysでクリーンアップする必要があります)、問題のサービスから$broadcastを取得できるようにする必要があります。

それでも、各リスナーに$onハンドラーをコーディングする必要がありますが、パターンは$digestへの複数の呼び出しから切り離されているので、長時間実行されるウォッチャーのリスクを回避できます。

このようにしても、サービスがその振る舞いを変えることなしに、リスナーは _ dom _ および/または異なる子スコープから出入りすることができます。

**更新:例**

ブロードキャストは、「グローバル」サービスで最も理にかなっており、アプリの無数の他のものに影響を与える可能性があります。良い例としては、ログイン、ログアウト、更新、アイドルなどのイベントがいくつも発生する可能性があるUserサービスがあります。これはブロードキャストが最も理にかなっていると考えられるためです。サービスを注入しても、変更を検査するために式を評価したり結果をキャッシュしたりする必要はありません。それはただ発砲し忘れてしまいます(だからそれが行動を必要とするものではなく、忘れ去られた通知であることを確認してください)

.factory('UserService', [ '$rootScope', function($rootScope) {
   var service = <whatever you do for the object>

   service.save = function(data) {
     .. validate data and update model ..
     // notify listeners and provide the data that changed [optional]
     $rootScope.$broadcast('user:updated',data);
   }

   // alternatively, create a callback function and $broadcast from there if making an ajax call

   return service;
}]);

上記のサービスは、save()関数が完了してデータが有効になったときにすべてのスコープにメッセージをブロードキャストします。あるいは、$ resourceまたはajaxの送信であれば、ブロードキャスト呼び出しをコールバックに移動して、サーバーが応答したときに起動するようにします。すべてのリスナーがそれぞれの$ダイジェストごとにスコープを調べる必要なしにイベントを待つだけなので、ブロードキャストは特にそのパターンに適しています。リスナーは次のようになります。

.controller('UserCtrl', [ 'UserService', '$scope', function(UserService, $scope) {

  var user = UserService.getUser();

  // if you don't want to expose the actual object in your scope you could expose just the values, or derive a value for your purposes
   $scope.name = user.firstname + ' ' +user.lastname;

   $scope.$on('user:updated', function(event,data) {
     // you could inspect the data to see if what you care about changed, or just update your own scope
     $scope.name = user.firstname + ' ' + user.lastname;
   });

   // different event names let you group your code and logic by what happened
   $scope.$on('user:logout', function(event,data) {
     .. do something differently entirely ..
   });

 }]);

これの利点の1つは多数の腕時計の除去です。上記の例のようにフィールドを結合したり値を派生させたりする場合は、firstnameプロパティとlastnameプロパティの両方を監視する必要があります。 getUser()関数を監視しても、更新時にユーザーオブジェクトが置き換えられた場合にのみ機能します。ユーザーオブジェクトのプロパティが更新されただけでは起動しません。その場合、あなたは深い監視をしなければならないでしょう、そしてそれはより集中的です。

$ broadcastは、呼び出されたスコープからのメッセージを任意の子スコープに送ります。そのため、$ rootScopeから呼び出すと、すべてのスコープで起動します。例えば、あなたがあなたのコントローラのスコープから$ broadcastするならば、それはあなたのコントローラのスコープから継承するスコープでのみ発生するでしょう。 $ emitは逆の方向を向いており、スコープチェーンをバブルアップさせるという点でDOMイベントと同様に振る舞います。

$ broadcastが多くの意味を持つシナリオがあること、そして$ watchがより良い選択肢であるシナリオがあることを覚えておいてください。

224
Matt Pileggi

私は@dtheodotと同じようなアプローチを使っていますが、コールバックを渡す代わりに角度付きの約束を使っています

app.service('myService', function($q) {
    var self = this,
        defer = $q.defer();

    this.foo = 0;

    this.observeFoo = function() {
        return defer.promise;
    }

    this.setFoo = function(foo) {
        self.foo = foo;
        defer.notify(self.foo);
    }
})

それから、myService.setFoo(foo)メソッドを使って、サービス中にfooを更新するだけです。あなたのコントローラでは、次のように使うことができます。

myService.observeFoo().then(null, null, function(foo){
    $scope.foo = foo;
})

thenの最初の2つの引数は成功とエラーのコールバック、3番目の引数はnotifyコールバックです。

$ qの参照。

44
Krym

時計やオブザーバのコールバックなし( http://jsfiddle.net/zymotik/853wvv7s/ ):

JavaScript:

angular.module("Demo", [])
    .factory("DemoService", function($timeout) {

        function DemoService() {
            var self = this;
            self.name = "Demo Service";

            self.count = 0;

            self.counter = function(){
                self.count++;
                $timeout(self.counter, 1000);
            }

            self.addOneHundred = function(){
                self.count+=100;
            }

            self.counter();
        }

        return new DemoService();

    })
    .controller("DemoController", function($scope, DemoService) {

        $scope.service = DemoService;

        $scope.minusOneHundred = function() {
            DemoService.count -= 100;
        }

    });

HTML

<div ng-app="Demo" ng-controller="DemoController">
    <div>
        <h4>{{service.name}}</h4>
        <p>Count: {{service.count}}</p>
    </div>
</div>

このJavaScriptは、値ではなくオブジェクトをサービスから返すので動作します。 JavaScriptオブジェクトがサービスから返されると、Angularはそのすべてのプロパティにウォッチを追加します。

$ timeoutの実行時に元のオブジェクトへの参照を保持する必要があるため、私は 'var self = this'を使用していることにも注意してください。それ以外の場合、 'this'はウィンドウオブジェクトを参照します。

38
Zymotik

私は似たようなものを探すためにこの質問に出くわしました、しかし私はそれが何が起こっているのかについての徹底的な説明といくつかの追加の解決策に値すると思います。

あなたが使ったような角度表現がHTMLにあるとき、Angularは自動的に$watch$scope.fooを設定し、$scope.fooが変わるたびにHTMLを更新します。

<div ng-controller="FooCtrl">
  <div ng-repeat="item in foo">{{ item }}</div>
</div>

ここでの未解決の問題は、2つのうちの1つがaService.fooに影響を及ぼしているため、変更が検出されないことです。これら二つの可能性は:

  1. aService.fooは毎回新しい配列に設定されているため、その配列への参照は古くなっています。
  2. aService.fooは、$digestサイクルが更新時にトリガーされないように更新されています。

問題1:古い参照

最初の可能性を考慮して、$digestが適用されていると仮定して、aService.fooが常に同じ配列である場合、以下のコードスニペットに示すように、自動的に設定された$watchが変更を検出します。

解決策1-a:配列またはオブジェクトが更新ごとに 同じオブジェクト であることを確認する

angular.module('myApp', [])
  .factory('aService', [
    '$interval',
    function($interval) {
      var service = {
        foo: []
      };

      // Create a new array on each update, appending the previous items and 
      // adding one new item each time
      $interval(function() {
        if (service.foo.length < 10) {
          var newArray = []
          Array.prototype.Push.apply(newArray, service.foo);
          newArray.Push(Math.random());
          service.foo = newArray;
        }
      }, 1000);

      return service;
    }
  ])
  .factory('aService2', [
    '$interval',
    function($interval) {
      var service = {
        foo: []
      };

      // Keep the same array, just add new items on each update
      $interval(function() {
        if (service.foo.length < 10) {
          service.foo.Push(Math.random());
        }
      }, 1000);

      return service;
    }
  ])
  .controller('FooCtrl', [
    '$scope',
    'aService',
    'aService2',
    function FooCtrl($scope, aService, aService2) {
      $scope.foo = aService.foo;
      $scope.foo2 = aService2.foo;
    }
  ]);
<!DOCTYPE html>
<html>

<head>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
  <link rel="stylesheet" href="style.css" />
  <script src="script.js"></script>
</head>

<body ng-app="myApp">
  <div ng-controller="FooCtrl">
    <h1>Array changes on each update</h1>
    <div ng-repeat="item in foo">{{ item }}</div>
    <h1>Array is the same on each udpate</h1>
    <div ng-repeat="item in foo2">{{ item }}</div>
  </div>
</body>

</html>

ご覧のとおり、aService.fooに添付されているng-repeatは、aService.fooが変更されても更新されませんが、aService2.foo に添付されているng-repeatは を更新します。これは、aService.fooへの参照が古くなっていますが、aService2.fooへの参照がそうではないためです。最初の配列への参照を$scope.foo = aService.foo;で作成しました。これは次回の更新時にサービスによって破棄されました。つまり、$scope.fooはもう必要としていた配列を参照しなくなりました。

ただし、初期参​​照を確実に維持するにはいくつかの方法がありますが、場合によってはオブジェクトまたは配列を変更する必要があります。あるいは、サービスプロパティがStringNumberのようなプリミティブを参照しているとしたらどうでしょうか。そのような場合、私たちは単に参照に頼ることはできません。だから can できることは?

以前に与えられた答えのいくつかはすでにその問題に対するいくつかの解決策を与えています。しかし、私は個人的には Jin および thetallweeks によって提案されている簡単な方法を使用することに賛成です。

hTMLマークアップでaService.fooを参照するだけです。

解決策1-b:サービスをスコープにアタッチし、HTMLの{service}.{property}を参照します。

つまり、こうしてください。

HTML:

<div ng-controller="FooCtrl">
  <div ng-repeat="item in aService.foo">{{ item }}</div>
</div>

JS:

function FooCtrl($scope, aService) {
    $scope.aService = aService;
}
angular.module('myApp', [])
  .factory('aService', [
    '$interval',
    function($interval) {
      var service = {
        foo: []
      };

      // Create a new array on each update, appending the previous items and 
      // adding one new item each time
      $interval(function() {
        if (service.foo.length < 10) {
          var newArray = []
          Array.prototype.Push.apply(newArray, service.foo);
          newArray.Push(Math.random());
          service.foo = newArray;
        }
      }, 1000);

      return service;
    }
  ])
  .controller('FooCtrl', [
    '$scope',
    'aService',
    function FooCtrl($scope, aService) {
      $scope.aService = aService;
    }
  ]);
<!DOCTYPE html>
<html>

<head>
  <script data-require="[email protected]" data-semver="1.4.7" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.js"></script>
  <link rel="stylesheet" href="style.css" />
  <script src="script.js"></script>
</head>

<body ng-app="myApp">
  <div ng-controller="FooCtrl">
    <h1>Array changes on each update</h1>
    <div ng-repeat="item in aService.foo">{{ item }}</div>
  </div>
</body>

</html>

このようにして、$watchは各aService.foo$digestを解決し、それによって正しく更新された値が得られます。

これはあなたがあなたの回避策でやろうとしていたことの一種ですが、方法についてははるかに少ないラウンドで。不必要な$watchをコントローラーに追加しました。これは、変更されるたびに$scopeに明示的にfooを設定します。 $watchの代わりにaServiceaService.fooにアタッチし、マークアップで明示的に$scopeにバインドする場合、その余分なaService.fooは必要ありません。


$digestサイクルが適用されていると仮定すれば、これですべてうまくいきました。上記の例では、Angularの $interval サービスを使用して配列を更新しました。これにより、更新のたびに$digestループが自動的に開始されます。しかし、サービス変数が(何らかの理由で)「Angular world」の中で更新されていない場合はどうなりますか。言い換えれば、 dont サービスプロパティが変更されるたびに$digestサイクルが自動的に有効になります。


問題2:欠けている $digest

ここでの解決策の多くはこの問題を解決するでしょうが、私は Code Whispererに同意します

Angularのようなフレームワークを使用しているのは、独自のオブザーバーパターンを作成しないためです。

したがって、上記の2番目の例に示すように、HTMLマークアップでaService.foo参照を引き続き使用し、Controller内で追加のコールバックを登録する必要はありません。

解決策2:$rootScope.$apply()とsetterとgetterを使う

settergetter の使用を提案している人はまだいませんでした。この機能はECMAScript 5で導入されたため、ここ数年で使用されています。もちろん、何らかの理由で本当に古いブラウザをサポートする必要がある場合は、この方法では機能しませんが、JavaScriptではゲッターとセッターが非常に使用されているように感じます。この特定のケースでは、それらは非常に役に立つかもしれません:

factory('aService', [
  '$rootScope',
  function($rootScope) {
    var realFoo = [];

    var service = {
      set foo(a) {
        realFoo = a;
        $rootScope.$apply();
      },
      get foo() {
        return realFoo;
      }
    };
  // ...
}
angular.module('myApp', [])
  .factory('aService', [
    '$rootScope',
    function($rootScope) {
      var realFoo = [];

      var service = {
        set foo(a) {
          realFoo = a;
          $rootScope.$apply();
        },
        get foo() {
          return realFoo;
        }
      };

      // Create a new array on each update, appending the previous items and 
      // adding one new item each time
      setInterval(function() {
        if (service.foo.length < 10) {
          var newArray = [];
          Array.prototype.Push.apply(newArray, service.foo);
          newArray.Push(Math.random());
          service.foo = newArray;
        }
      }, 1000);

      return service;
    }
  ])
  .controller('FooCtrl', [
    '$scope',
    'aService',
    function FooCtrl($scope, aService) {
      $scope.aService = aService;
    }
  ]);
<!DOCTYPE html>
<html>

<head>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
  <link rel="stylesheet" href="style.css" />
  <script src="script.js"></script>
</head>

<body ng-app="myApp">
  <div ng-controller="FooCtrl">
    <h1>Using a Getter/Setter</h1>
    <div ng-repeat="item in aService.foo">{{ item }}</div>
  </div>
</body>

</html>

ここで私はサービス関数に 'private'変数を追加しました:realFoo。これは、serviceオブジェクトに対してそれぞれget foo()関数およびset foo()関数を使用して更新および取得されます。

集合関数での$rootScope.$apply()の使用に注意してください。これにより、Angularはservice.fooへの変更を認識します。 'inprog'エラーが発生した場合は この便利なリファレンスページ を参照するか、Angular> = 1.3を使用する場合は$rootScope.$applyAsync()を使用してください。

aService.fooが非常に頻繁に更新されている場合は、これにも注意してください。パフォーマンスに大きな影響を与える可能性があります。パフォーマンスが問題になる場合は、ここで他の答えに似たオブザーバパターンを設定メソッドを使って設定できます。

28
NanoWizard

私が言うことができる限り、あなたはそれと同じくらい精巧な何かをする必要はありません。あなたはすでにサービスからあなたのスコープにfooを割り当てています、そしてfooは配列なので(そして順番にオブジェクトとして参照されて割り当てられます!)だから、あなたがする必要があるのはこのようなものです。

function FooCtrl($scope, aService) {                                                                                                                              
  $scope.foo = aService.foo;

 }

これと同じCtrl内の他の変数がfooの変更に依存している場合は、fooを監視してその変数を変更するための監視が必要になります。しかし、それが単純な参照である限り、視聴は必要ありません。お役に立てれば。

28
ganaraj

あなたは$ rootScopeにサービスを挿入して見ることができます:

myApp.run(function($rootScope, aService){
    $rootScope.aService = aService;
    $rootScope.$watch('aService', function(){
        alert('Watch');
    }, true);
});

あなたのコントローラで:

myApp.controller('main', function($scope){
    $scope.aService.foo = 'change';
});

他のオプションは、のような外部ライブラリを使用することです: https://github.com/melanke/Watch.JS

以下で動作します:IE 9+、FF 4+、SF 5+、WebKit、CH 7+、OP 12+、BESEN、Node.JS、Rhino 1.7+

1つ、多数、またはすべてのオブジェクト属性の変化を観察できます。

例:

var ex3 = {
    attr1: 0,
    attr2: "initial value of attr2",
    attr3: ["a", 3, null]
};   
watch(ex3, function(){
    alert("some attribute of ex3 changes!");
});
ex3.attr3.Push("new value");​

あなたは工場自体の中で変化を見ることができて、そして変化を放送することができます

angular.module('MyApp').factory('aFactory', function ($rootScope) {
    // Define your factory content
    var result = {
        'key': value
    };

    // add a listener on a key        
    $rootScope.$watch(function () {
        return result.key;
    }, function (newValue, oldValue, scope) {
        // This is called after the key "key" has changed, a good idea is to broadcast a message that key has changed
        $rootScope.$broadcast('aFactory:keyChanged', newValue);
    }, true);

    return result;
});

それからあなたのコントローラーで:

angular.module('MyApp').controller('aController', ['$rootScope', function ($rootScope) {

    $rootScope.$on('aFactory:keyChanged', function currentCityChanged(event, value) {
        // do something
    });
}]);

このように、あなたはその説明の中にすべての関連する工場コードを入れて、あなたは外部からの放送に頼ることができるだけです

6
Flavien Volken

==更新==

非常にシンプルになりました。

ここにペン

HTML:

<div class="container" data-ng-app="app">

  <div class="well" data-ng-controller="FooCtrl">
    <p><strong>FooController</strong></p>
    <div class="row">
      <div class="col-sm-6">
        <p><a href="" ng-click="setItems([ { name: 'I am single item' } ])">Send one item</a></p>
        <p><a href="" ng-click="setItems([ { name: 'Item 1 of 2' }, { name: 'Item 2 of 2' } ])">Send two items</a></p>
        <p><a href="" ng-click="setItems([ { name: 'Item 1 of 3' }, { name: 'Item 2 of 3' }, { name: 'Item 3 of 3' } ])">Send three items</a></p>
      </div>
      <div class="col-sm-6">
        <p><a href="" ng-click="setName('Sheldon')">Send name: Sheldon</a></p>
        <p><a href="" ng-click="setName('Leonard')">Send name: Leonard</a></p>
        <p><a href="" ng-click="setName('Penny')">Send name: Penny</a></p>
      </div>
    </div>
  </div>

  <div class="well" data-ng-controller="BarCtrl">
    <p><strong>BarController</strong></p>
    <p ng-if="name">Name is: {{ name }}</p>
    <div ng-repeat="item in items">{{ item.name }}</div>
  </div>

</div>

JavaScript:

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

app.factory('PostmanService', function() {
  var Postman = {};
  Postman.set = function(key, val) {
    Postman[key] = val;
  };
  Postman.get = function(key) {
    return Postman[key];
  };
  Postman.watch = function($scope, key, onChange) {
    return $scope.$watch(
      // This function returns the value being watched. It is called for each turn of the $digest loop
      function() {
        return Postman.get(key);
      },
      // This is the change listener, called when the value returned from the above function changes
      function(newValue, oldValue) {
        if (newValue !== oldValue) {
          // Only update if the value changed
          $scope[key] = newValue;
          // Run onChange if it is function
          if (angular.isFunction(onChange)) {
            onChange(newValue, oldValue);
          }
        }
      }
    );
  };
  return Postman;
});

app.controller('FooCtrl', ['$scope', 'PostmanService', function($scope, PostmanService) {
  $scope.setItems = function(items) {
    PostmanService.set('items', items);
  };
  $scope.setName = function(name) {
    PostmanService.set('name', name);
  };
}]);

app.controller('BarCtrl', ['$scope', 'PostmanService', function($scope, PostmanService) {
  $scope.items = [];
  $scope.name = '';
  PostmanService.watch($scope, 'items');
  PostmanService.watch($scope, 'name', function(newVal, oldVal) {
    alert('Hi, ' + newVal + '!');
  });
}]);
6
hayatbiralem

dtheodor's answerに基づいて、以下のようなものを使用して、コールバックを登録解除することを忘れないようにすることができます... $scopeをサービスに渡すことに反対する人もいます。

factory('aService', function() {
  var observerCallbacks = [];

  /**
   * Registers a function that will be called when
   * any modifications are made.
   *
   * For convenience the callback is called immediately after registering
   * which can be prevented with `preventImmediate` param.
   *
   * Will also automatically unregister the callback upon scope destory.
   */
  this.registerObserver = function($scope, cb, preventImmediate){
    observerCallbacks.Push(cb);

    if (preventImmediate !== true) {
      cb();
    }

    $scope.$on('$destroy', function () {
      observerCallbacks.remove(cb);
    });
  };

  function notifyObservers() {
    observerCallbacks.forEach(function (cb) {
      cb();
    });
  };

  this.foo = someNgResource.query().$then(function(){
    notifyObservers();
  });
});

Array.removeは、次のような拡張メソッドです。

/**
 * Removes the given item the current array.
 *
 * @param  {Object}  item   The item to remove.
 * @return {Boolean}        True if the item is removed.
 */
Array.prototype.remove = function (item /*, thisp */) {
    var idx = this.indexOf(item);

    if (idx > -1) {
        this.splice(idx, 1);

        return true;
    }
    return false;
};
4
Jamie

非常によく似た問題に直面しながら、私はスコープ内の関数を見て、その関数がサービス変数を返すようにしました。 jsフィドル を作成しました。あなたは以下のコードを見つけることができます。

    var myApp = angular.module("myApp",[]);

myApp.factory("randomService", function($timeout){
    var retValue = {};
    var data = 0;

    retValue.startService = function(){
        updateData();
    }

    retValue.getData = function(){
        return data;
    }

    function updateData(){
        $timeout(function(){
            data = Math.floor(Math.random() * 100);
            updateData()
        }, 500);
    }

    return retValue;
});

myApp.controller("myController", function($scope, randomService){
    $scope.data = 0;
    $scope.dataUpdated = 0;
    $scope.watchCalled = 0;
    randomService.startService();

    $scope.getRandomData = function(){
        return randomService.getData();    
    }

    $scope.$watch("getRandomData()", function(newValue, oldValue){
        if(oldValue != newValue){
            $scope.data = newValue;
            $scope.dataUpdated++;
        }
            $scope.watchCalled++;
    });
});
2
chandings

これが私の一般的なアプローチです。

mainApp.service('aService',[function(){
        var self = this;
        var callbacks = {};

        this.foo = '';

        this.watch = function(variable, callback) {
            if (typeof(self[variable]) !== 'undefined') {
                if (!callbacks[variable]) {
                    callbacks[variable] = [];
                }
                callbacks[variable].Push(callback);
            }
        }

        this.notifyWatchersOn = function(variable) {
            if (!self[variable]) return;
            if (!callbacks[variable]) return;

            angular.forEach(callbacks[variable], function(callback, key){
                callback(self[variable]);
            });
        }

        this.changeFoo = function(newValue) {
            self.foo = newValue;
            self.notifyWatchersOn('foo');
        }

    }]);

あなたのコントローラーで

function FooCtrl($scope, aService) {
    $scope.foo;

    $scope._initWatchers = function() {
        aService.watch('foo', $scope._onFooChange);
    }

    $scope._onFooChange = function(newValue) {
        $scope.foo = newValue;
    }

    $scope._initWatchers();

}

FooCtrl.$inject = ['$scope', 'aService'];
2
elitechance

私は他のスレッドでも同様の問題を抱えているがまったく異なるアプローチで本当にすばらしい解決策を見つけました。出典: AngularJS:$ rootScopeの値を変更すると、ディレクティブ内の$ watchが機能しない

基本的に そこにある解決策は NOT TO に伝えますそれは非常に重い解決策であるので$watchを使います。 代わりに 彼らは$emit$onを使うことを提案します。

私の問題はwatch my service の中の変数で、 directive の中で反応することでした。そして上記の方法でそれはとても簡単です!

私のモジュール/サービスの例:

angular.module('xxx').factory('example', function ($rootScope) {
    var user;

    return {
        setUser: function (aUser) {
            user = aUser;
            $rootScope.$emit('user:change');
        },
        getUser: function () {
            return (user) ? user : false;
        },
        ...
    };
});

だから基本的に私はwatch私のuser - それが新しい値に設定される時はいつでも私は$emituser:changeステータスです。

今私の場合は、directiveで使用しました。

angular.module('xxx').directive('directive', function (Auth, $rootScope) {
    return {
        ...
        link: function (scope, element, attrs) {
            ...
            $rootScope.$on('user:change', update);
        }
    };
});

さて、directiveの中で、私は$rootScope on に与えられた変更に耳を傾けています - 私はそれぞれ反応します。とても簡単でエレガント!

2
Atais

私のような単純な解決策を探している人のために、これはあなたがコントローラーで通常の$ watchを使うことから期待することとほとんど正確に同じことをします。唯一の違いは、JavaScriptのコンテキストで文字列を評価し、特定のスコープでは評価しないことです。あなたのサービスに$ rootScopeを注入する必要がありますが、それはダイジェストサイクルを正しくフックするためにのみ使用されます。

function watch(target, callback, deep) {
    $rootScope.$watch(function () {return eval(target);}, callback, deep);
};
2
Justus Wingert

私はこの問題に遭遇しましたが、私の問題は、$ intervalプロバイダーを使用しているはずだったときにsetIntervalを使用していたことです。これはsetTimeoutの場合も同様です(代わりに$ timeoutを使用してください)。それはOPの質問に対する答えではないことを私は知っていますが、それは私を助けたので、それはいくつかを助けるかもしれません。

2
honkskillet

ちょっと醜いですが、トグルのためにスコープ変数の登録を私のサービスに追加しました:

myApp.service('myService', function() {
    var self = this;
    self.value = false;
    self.c2 = function(){};
    self.callback = function(){
        self.value = !self.value; 
       self.c2();
    };

    self.on = function(){
        return self.value;
    };

    self.register = function(obj, key){ 
        self.c2 = function(){
            obj[key] = self.value; 
            obj.$apply();
        } 
    };

    return this;
});

そして、コントローラで:

function MyCtrl($scope, myService) {
    $scope.name = 'Superhero';
    $scope.myVar = false;
    myService.register($scope, 'myVar');
}
1
nclu

この略奪者を見てください:これは私が考えることができる最も簡単な例です

http://jsfiddle.net/HEdJF/

<div ng-app="myApp">
    <div ng-controller="FirstCtrl">
        <input type="text" ng-model="Data.FirstName"><!-- Input entered here -->
        <br>Input is : <strong>{{Data.FirstName}}</strong><!-- Successfully updates here -->
    </div>
    <hr>
    <div ng-controller="SecondCtrl">
        Input should also be here: {{Data.FirstName}}<!-- How do I automatically updated it here? -->
    </div>
</div>



// declare the app with no dependencies
var myApp = angular.module('myApp', []);
myApp.factory('Data', function(){
   return { FirstName: '' };
});

myApp.controller('FirstCtrl', function( $scope, Data ){
    $scope.Data = Data;
});

myApp.controller('SecondCtrl', function( $scope, Data ){
    $scope.Data = Data;
});
1
anandharshan

// service :(特別なことは何もない)

myApp.service('myService', function() {
  return { someVariable:'abc123' };
});

// ctrl:

myApp.controller('MyCtrl', function($scope, myService) {

  $scope.someVariable = myService.someVariable;

  // watch the service and update this ctrl...
  $scope.$watch(function(){
    return myService.someVariable;
  }, function(newValue){
    $scope.someVariable = newValue;
  });
});
1
Nawlbergs

私はその部分に遅れていますが、私は上記の答えよりもこれを行うためのより良い方法を見つけました。サービス変数の値を保持するために変数を代入する代わりに、スコープにアタッチされ、サービス変数を返す関数を作成しました。

コントローラー

$scope.foo = function(){
 return aService.foo;
}

私はこれがあなたが望むことをすると思います。私のコントローラはこの実装で私のサービスの価値をチェックし続けます。正直なところ、これは選択した答えよりもはるかに簡単です。

0
alaboudi

サービスプロパティの変更を追跡するのに役立つ2つの簡単なユーティリティサービスを書きました。

長い説明をスキップしたい場合は、 jsfiddle に直行してください。

  1. WatchObj
mod.service('WatchObj', ['$rootScope', WatchObjService]);

function WatchObjService($rootScope) {
  // returns watch function
  // obj: the object to watch for
  // fields: the array of fields to watch
  // target: where to assign changes (usually it's $scope or controller instance)
  // $scope: optional, if not provided $rootScope is use
  return function watch_obj(obj, fields, target, $scope) {
    $scope = $scope || $rootScope;
    //initialize watches and create an array of "unwatch functions"
    var watched = fields.map(function(field) {
      return $scope.$watch(
        function() {
          return obj[field];
        },
        function(new_val) {
          target[field] = new_val;
        }
      );
    });
    //unregister function will unregister all our watches
    var unregister = function unregister_watch_obj() {
      watched.map(function(unregister) {
        unregister();
      });
    };
    //automatically unregister when scope is destroyed
    $scope.$on('$destroy', unregister);
    return unregister;
  };
}

このサービスは、コントローラで次のように使用されます。プロパティ「prop1」、「prop2」、「prop3」を持つサービス「testService」があるとします。監視してスコープ 'prop1'と 'prop2'に割り当てたいとします。監視サービスでは、次のようになります。

app.controller('TestWatch', ['$scope', 'TestService', 'WatchObj', TestWatchCtrl]);

function TestWatchCtrl($scope, testService, watch) {
  $scope.prop1 = testService.prop1;
  $scope.prop2 = testService.prop2;
  $scope.prop3 = testService.prop3;
  watch(testService, ['prop1', 'prop2'], $scope, $scope);
}
  1. watch objは素晴らしいですが、サービスに非同期コードがある場合はそれだけでは不十分です。その場合、私はそのように見える2番目のユーティリティを使います。
mod.service('apply', ['$timeout', ApplyService]);

function ApplyService($timeout) {
  return function apply() {
    $timeout(function() {});
  };
}

$ digestループをトリガーするために、非同期コードの最後にそれをトリガーします。そのように:

app.service('TestService', ['apply', TestService]);

function TestService(apply) {
  this.apply = apply;
}
TestService.prototype.test3 = function() {
  setTimeout(function() {
    this.prop1 = 'changed_test_2';
    this.prop2 = 'changed2_test_2';
    this.prop3 = 'changed3_test_2';
    this.apply(); //trigger $digest loop
  }.bind(this));
}

だから、それらすべてが一緒になったように見えるでしょう(あなたはそれを実行するか open fiddle )できます:

// TEST app code

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

app.controller('TestWatch', ['$scope', 'TestService', 'WatchObj', TestWatchCtrl]);

function TestWatchCtrl($scope, testService, watch) {
  $scope.prop1 = testService.prop1;
  $scope.prop2 = testService.prop2;
  $scope.prop3 = testService.prop3;
  watch(testService, ['prop1', 'prop2'], $scope, $scope);
  $scope.test1 = function() {
    testService.test1();
  };
  $scope.test2 = function() {
    testService.test2();
  };
  $scope.test3 = function() {
    testService.test3();
  };
}

app.service('TestService', ['apply', TestService]);

function TestService(apply) {
  this.apply = apply;
  this.reset();
}
TestService.prototype.reset = function() {
  this.prop1 = 'unchenged';
  this.prop2 = 'unchenged2';
  this.prop3 = 'unchenged3';
}
TestService.prototype.test1 = function() {
  this.prop1 = 'changed_test_1';
  this.prop2 = 'changed2_test_1';
  this.prop3 = 'changed3_test_1';
}
TestService.prototype.test2 = function() {
  setTimeout(function() {
    this.prop1 = 'changed_test_2';
    this.prop2 = 'changed2_test_2';
    this.prop3 = 'changed3_test_2';
  }.bind(this));
}
TestService.prototype.test3 = function() {
  setTimeout(function() {
    this.prop1 = 'changed_test_2';
    this.prop2 = 'changed2_test_2';
    this.prop3 = 'changed3_test_2';
    this.apply();
  }.bind(this));
}
//END TEST APP CODE

//WATCH UTILS
var mod = angular.module('watch_utils', []);

mod.service('apply', ['$timeout', ApplyService]);

function ApplyService($timeout) {
  return function apply() {
    $timeout(function() {});
  };
}

mod.service('WatchObj', ['$rootScope', WatchObjService]);

function WatchObjService($rootScope) {
  // target not always equals $scope, for example when using bindToController syntax in 
  //directives
  return function watch_obj(obj, fields, target, $scope) {
    // if $scope is not provided, $rootScope is used
    $scope = $scope || $rootScope;
    var watched = fields.map(function(field) {
      return $scope.$watch(
        function() {
          return obj[field];
        },
        function(new_val) {
          target[field] = new_val;
        }
      );
    });
    var unregister = function unregister_watch_obj() {
      watched.map(function(unregister) {
        unregister();
      });
    };
    $scope.$on('$destroy', unregister);
    return unregister;
  };
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div class='test' ng-app="app" ng-controller="TestWatch">
  prop1: {{prop1}}
  <br>prop2: {{prop2}}
  <br>prop3 (unwatched): {{prop3}}
  <br>
  <button ng-click="test1()">
    Simple props change
  </button>
  <button ng-click="test2()">
    Async props change
  </button>
  <button ng-click="test3()">
    Async props change with apply
  </button>
</div>
0
Max

大規模なアプリケーションでメモリリークを引き起こす、いくつかのひどいオブザーバパターンをここで見ました。

私は少し遅れるかもしれませんが、それはこれと同じくらい簡単です。

配列プッシュのようなものを見たい場合、watch関数は参照の変更(基本型)を監視します。

someArray.Push(someObj); someArray = someArray.splice(0);

これは参照を更新し、どこからでも時計を更新します。サービスゲッターメソッドを含みます。プリミティブなものはすべて自動的に更新されます。

0
Oliver Dixon