私はサービスを持っている、と言う:
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;
}
});
}
これは長年に渡り、私はサービスの変数を使用するすべてのコントローラでそれを繰り返してきました。シェア変数を監視するためのより良い方法はありますか?
$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
};
このようなシナリオでは、複数の未知のオブジェクトが変更に関心を持つ可能性があり、変更されているアイテムから$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がより良い選択肢であるシナリオがあることを覚えておいてください。
私は@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コールバックです。
時計やオブザーバのコールバックなし( 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'はウィンドウオブジェクトを参照します。
私は似たようなものを探すためにこの質問に出くわしました、しかし私はそれが何が起こっているのかについての徹底的な説明といくつかの追加の解決策に値すると思います。
あなたが使ったような角度表現が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
に影響を及ぼしているため、変更が検出されないことです。これら二つの可能性は:
aService.foo
は毎回新しい配列に設定されているため、その配列への参照は古くなっています。aService.foo
は、$digest
サイクルが更新時にトリガーされないように更新されています。最初の可能性を考慮して、$digest
が適用されていると仮定して、aService.foo
が常に同じ配列である場合、以下のコードスニペットに示すように、自動的に設定された$watch
が変更を検出します。
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
はもう必要としていた配列を参照しなくなりました。
ただし、初期参照を確実に維持するにはいくつかの方法がありますが、場合によってはオブジェクトまたは配列を変更する必要があります。あるいは、サービスプロパティがString
やNumber
のようなプリミティブを参照しているとしたらどうでしょうか。そのような場合、私たちは単に参照に頼ることはできません。だから can できることは?
以前に与えられた答えのいくつかはすでにその問題に対するいくつかの解決策を与えています。しかし、私は個人的には Jin および thetallweeks によって提案されている簡単な方法を使用することに賛成です。
hTMLマークアップでaService.fooを参照するだけです。
{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
の代わりにaService
をaService.foo
にアタッチし、マークアップで明示的に$scope
にバインドする場合、その余分なaService.foo
は必要ありません。
$digest
サイクルが適用されていると仮定すれば、これですべてうまくいきました。上記の例では、Angularの $interval
サービスを使用して配列を更新しました。これにより、更新のたびに$digest
ループが自動的に開始されます。しかし、サービス変数が(何らかの理由で)「Angular world」の中で更新されていない場合はどうなりますか。言い換えれば、 dont サービスプロパティが変更されるたびに$digest
サイクルが自動的に有効になります。
$digest
ここでの解決策の多くはこの問題を解決するでしょうが、私は Code Whispererに同意します :
Angularのようなフレームワークを使用しているのは、独自のオブザーバーパターンを作成しないためです。
したがって、上記の2番目の例に示すように、HTMLマークアップでaService.foo
参照を引き続き使用し、Controller内で追加のコールバックを登録する必要はありません。
$rootScope.$apply()
とsetterとgetterを使うsetter と getter の使用を提案している人はまだいませんでした。この機能は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
が非常に頻繁に更新されている場合は、これにも注意してください。パフォーマンスに大きな影響を与える可能性があります。パフォーマンスが問題になる場合は、ここで他の答えに似たオブザーバパターンを設定メソッドを使って設定できます。
私が言うことができる限り、あなたはそれと同じくらい精巧な何かをする必要はありません。あなたはすでにサービスからあなたのスコープにfooを割り当てています、そしてfooは配列なので(そして順番にオブジェクトとして参照されて割り当てられます!)だから、あなたがする必要があるのはこのようなものです。
function FooCtrl($scope, aService) {
$scope.foo = aService.foo;
}
これと同じCtrl内の他の変数がfooの変更に依存している場合は、fooを監視してその変数を変更するための監視が必要になります。しかし、それが単純な参照である限り、視聴は必要ありません。お役に立てれば。
あなたは$ 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
});
}]);
このように、あなたはその説明の中にすべての関連する工場コードを入れて、あなたは外部からの放送に頼ることができるだけです
==更新==
非常にシンプルになりました。
ここにペン 。
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 + '!');
});
}]);
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;
};
非常によく似た問題に直面しながら、私はスコープ内の関数を見て、その関数がサービス変数を返すようにしました。 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++;
});
});
これが私の一般的なアプローチです。
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'];
私は他のスレッドでも同様の問題を抱えているがまったく異なるアプローチで本当にすばらしい解決策を見つけました。出典: 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
- それが新しい値に設定される時はいつでも私は$emit
user:change
ステータスです。
今私の場合は、directiveで使用しました。
angular.module('xxx').directive('directive', function (Auth, $rootScope) {
return {
...
link: function (scope, element, attrs) {
...
$rootScope.$on('user:change', update);
}
};
});
さて、directiveの中で、私は$rootScope
と on に与えられた変更に耳を傾けています - 私はそれぞれ反応します。とても簡単でエレガント!
私のような単純な解決策を探している人のために、これはあなたがコントローラーで通常の$ watchを使うことから期待することとほとんど正確に同じことをします。唯一の違いは、JavaScriptのコンテキストで文字列を評価し、特定のスコープでは評価しないことです。あなたのサービスに$ rootScopeを注入する必要がありますが、それはダイジェストサイクルを正しくフックするためにのみ使用されます。
function watch(target, callback, deep) {
$rootScope.$watch(function () {return eval(target);}, callback, deep);
};
私はこの問題に遭遇しましたが、私の問題は、$ intervalプロバイダーを使用しているはずだったときにsetIntervalを使用していたことです。これはsetTimeoutの場合も同様です(代わりに$ timeoutを使用してください)。それはOPの質問に対する答えではないことを私は知っていますが、それは私を助けたので、それはいくつかを助けるかもしれません。
ちょっと醜いですが、トグルのためにスコープ変数の登録を私のサービスに追加しました:
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');
}
この略奪者を見てください:これは私が考えることができる最も簡単な例です
<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;
});
// 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;
});
});
私はその部分に遅れていますが、私は上記の答えよりもこれを行うためのより良い方法を見つけました。サービス変数の値を保持するために変数を代入する代わりに、スコープにアタッチされ、サービス変数を返す関数を作成しました。
コントローラー
$scope.foo = function(){
return aService.foo;
}
私はこれがあなたが望むことをすると思います。私のコントローラはこの実装で私のサービスの価値をチェックし続けます。正直なところ、これは選択した答えよりもはるかに簡単です。
サービスプロパティの変更を追跡するのに役立つ2つの簡単なユーティリティサービスを書きました。
長い説明をスキップしたい場合は、 jsfiddle に直行してください。
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);
}
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>
大規模なアプリケーションでメモリリークを引き起こす、いくつかのひどいオブザーバパターンをここで見ました。
私は少し遅れるかもしれませんが、それはこれと同じくらい簡単です。
配列プッシュのようなものを見たい場合、watch関数は参照の変更(基本型)を監視します。
someArray.Push(someObj); someArray = someArray.splice(0);
これは参照を更新し、どこからでも時計を更新します。サービスゲッターメソッドを含みます。プリミティブなものはすべて自動的に更新されます。