У меня есть сервис, скажем:
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
, я объединил этот шаблон, где я добавляю aService к контроллеру $scope
, а затем используйте $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
};
$destory
в области и добавьте незарегистрированный метод в aService
В сценарии, подобном этому, где несколько /unkown объектов могут быть заинтересованы в изменениях, используйте $rootScope.$broadcast
из изменяемого элемента.
Вместо того, чтобы создавать собственный реестр слушателей (которые нужно очистить при различных $-двоих), вы должны иметь возможность $broadcast
от рассматриваемой службы.
Вы должны все-таки кодировать обработчики $on
в каждом слушателе, но шаблон отделяется от нескольких вызовов до $digest
и, таким образом, избегает риска длительных наблюдателей.
Таким образом, слушатели могут приходить и уходить из DOM и/или разных дочерних областей без изменения службы, изменяющей ее поведение.
** update: примеры **
Трансляции будут иметь наибольший смысл в "глобальных" сервисах, которые могут повлиять на множество других вещей в вашем приложении. Хорошим примером является пользовательский сервис, в котором есть несколько событий, которые могут иметь место, например, вход в систему, выход из системы, обновление, простоя и т.д. Я считаю, что это означает, что трансляции имеют наибольший смысл, поскольку любая область может прослушивать событие без даже вводя службу, и не нужно оценивать какие-либо выражения или результаты кэша для проверки изменений. Он просто срабатывает и забывает (поэтому убедитесь, что это уведомление о пожаре и забывании, а не что-то, что требует действия)
.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-представление, переместите широковещательный вызов в обратный вызов, чтобы он срабатывал, когда сервер ответил. Широковещательные передачи подходят именно к этой схеме, особенно потому, что каждый слушатель просто ждет события без необходимости проверки области на каждом сводке $digest. Слушатель будет выглядеть так:
.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 ..
});
}]);
Одним из преимуществ этого является устранение нескольких часов. Если вы комбинировали поля или получали значения, подобные приведенному выше примеру, вам нужно было бы просмотреть свойства firstname и lastname. Просмотр функции getUser() будет работать только в том случае, если пользовательский объект был заменен на обновления, он не срабатывает, если объект пользователя просто обновил свои свойства. В этом случае вам придется делать глубокие часы, и это более интенсивно.
$broadcast отправляет сообщение из области действия, к которой он обращается вниз, в любые дочерние области. Так что вызов его из $rootScope будет срабатывать по каждой области. Например, если вы использовали $broadcast из области вашего контроллера, она срабатывала только в областях, которые наследуются от области вашего контроллера. $emit идет в противоположном направлении и ведет себя аналогично событию DOM, поскольку он пузырится по цепочке областей видимости.
Имейте в виду, что существуют сценарии, в которых широковещательная передача $имеет большой смысл, и есть сценарии, где $watch - лучший вариант, особенно если в области выделения с очень конкретным выражением часов.
Я использую подобный подход как @dtheodot, но используя angular обещание вместо передачи обратных вызовов
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
являются успешными и обратными вызовами ошибок, третий - уведомлением об обратном вызове.
Без часов или обратных вызовов наблюдателя (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 добавляет часы ко всем его свойствам.
Также обратите внимание, что я использую "var self = this", поскольку мне нужно сохранить ссылку на исходный объект, когда выполняется timeout timeout, иначе 'this' будет ссылаться на объект окна.
$scope.count = service.count
не работает.
$scope.data = service.data
<p>Count: {{ data.count }}</p>
Я наткнулся на этот вопрос, ища что-то подобное, но я думаю, что он заслуживает подробного объяснения того, что происходит, а также моего решения.
Когда выражение angular, такое как тот, который вы использовали, присутствует в HTML, angular автоматически устанавливает $watch
для $scope.foo
и обновляет HTML при каждом изменении $scope.foo
.
<div ng-controller="FooCtrl">
<div ng-repeat="item in foo">{{ item }}</div>
</div>
Невысокая проблема здесь заключается в том, что одна из двух вещей влияет на 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="/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>
Как вы можете видеть, ng-repeat, предположительно прикрепленный к aService.foo
, не обновляется, когда изменяется aService.foo
, но ng-repeat, прикрепленный к aService2.foo
. Это связано с тем, что наша ссылка на aService.foo
устарела, но наша ссылка на aService2.foo
не является. Мы создали ссылку на начальный массив с $scope.foo = aService.foo;
, который затем был отброшен службой на нем следующим обновлением, то есть $scope.foo
больше не ссылался на массив, который мы хотели больше.
Однако, хотя есть несколько способов убедиться, что начальная ссылка хранится в такте, иногда может потребоваться изменить объект или массив. Или что, если свойство службы ссылается на примитив, например, String
или Number
? В этих случаях мы не можем просто полагаться на ссылку. Итак, что мы можем сделать?
Некоторые из приведенных ранее ответов уже дают некоторые решения этой проблемы. Тем не менее, я лично выступаю за использование простого метода, предложенного thetallweeks в комментариях:
просто ссылку aService.foo в разметке html
{service}.{property}
в HTML.Значение, просто выполните следующее:
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="/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
в контроллер, который явно помещает foo
в $scope
всякий раз, когда он изменяется. Вы не нуждаетесь в этом дополнительном $watch
, когда вы прикрепляете aService
вместо aService.foo
к $scope
и привязываетесь явно к aService.foo
в разметке.
Теперь, когда применяется все хорошее и хорошее, предполагающее цикл $digest
. В приведенных выше примерах я использовал службу angular $interval
для обновления массивов, которая автоматически запускает цикл $digest
после каждого Обновить. Но что, если служебные переменные (по какой-либо причине) не обновляются внутри "мира w503". Другими словами, мы не запускаем цикл $digest
, который автоматически активируется при изменении свойства службы?
$digest
Многие из решений здесь помогут решить эту проблему, но я согласен с itcouldevenbeaboat:
Причина, по которой мы используем фреймворк вроде angular, состоит в том, чтобы не создавать собственные шаблоны наблюдателей
Поэтому я предпочел бы продолжать использовать ссылку aService.foo
в разметке HTML, как показано во втором примере выше, и не должен регистрировать дополнительный обратный вызов внутри Контроллера.
$rootScope.$apply()
Я был удивлен, что никто еще не предложил использовать setter и геттер, Эта возможность была внедрена в ECMAScript5 и, таким образом, существует уже много лет. Конечно, это означает, что по какой-то причине вам нужно поддерживать действительно старые браузеры, тогда этот метод не сработает, но я чувствую, что в JavaScript 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="/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
. Это обновляется и восстанавливается с помощью функций get foo()
и set foo()
соответственно в объекте service
.
Обратите внимание на использование $rootScope.$apply()
в заданной функции. Это гарантирует, что angular будет знать о любых изменениях в service.foo
. Если вы получите ошибки inprog, см. эту полезную справочную страницу, или если вы используете angular >= 1.3, вы можете просто использовать $rootScope.$applyAsync()
.
Также будьте осторожны, если aService.foo
обновляется очень часто, поскольку это может значительно повлиять на производительность. Если производительность будет проблемой, вы можете настроить шаблон наблюдателя, аналогичный другим ответам здесь, используя установщик.
services
не свойства, принадлежащие самой службе.
Насколько я могу судить, вам не нужно делать что-то столь же сложное. Вы уже назначили foo из службы в свою область действия, и поскольку foo - это массив (и, в свою очередь, объект назначается ссылкой!). Итак, все, что вам нужно сделать, это что-то вроде этого:
function FooCtrl($scope, aService) {
$scope.foo = aService.foo;
}
Если какая-то другая переменная в этом же Ctrl зависит от изменения foo, то да, вам понадобятся часы для наблюдения foo и внесения изменений в эту переменную. Но до тех пор, пока это простое наблюдение, не нужно. Надеюсь это поможет.
$watch
?
$watch
работать с примитивом. Вместо этого я определил метод в службе, который будет возвращать примитивное значение: somePrimitive() = function() { return somePrimitive }
и назначил этому методу свойство $ scope: $scope.somePrimitive = aService.somePrimitive;
, Затем я использовал метод области действия в HTML: <span>{{somePrimitive()}}</span>
Вы можете вставить услугу в $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 +
Вы можете наблюдать изменения одного, многих или всех атрибутов объекта.
Пример:
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");
Вы можете просматривать изменения внутри самого factory и затем транслировать изменение
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
});
}]);
Таким образом вы помещаете весь связанный код factory в его описание, тогда вы можете полагаться только на трансляцию извне
== == ОБНОВЛЕНО
Очень просто сейчас в $watch.
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 вы можете использовать что-то похожее на нижеследующее, чтобы не забыть отменить регистрацию обратного вызова... Некоторые могут возражать против передачи $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;
};
Я нашел действительно отличное решение в другом потоке с аналогичной проблемой, но совершенно другой подход. Источник: AngularJS: $watch внутри директивы не работает, когда изменяется значение $rootScope
В основном решение там говорит НЕ TO использовать $watch
, поскольку это очень тяжелое решение. Вместо они предлагают использовать $emit
и $on
.
Моя проблема состояла в том, чтобы наблюдать за переменной в моей службе и реагировать на директиву. И с помощью вышеуказанного метода это очень просто!
Мой пример модуля/службы:
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;
},
...
};
});
Итак, в основном я смотрю мой user
- всякий раз, когда он установлен на новое значение я $emit
a user:change
.
Теперь в моем случае в директиве я использовал:
angular.module('xxx').directive('directive', function (Auth, $rootScope) {
return {
...
link: function (scope, element, attrs) {
...
$rootScope.$on('user:change', update);
}
};
});
Теперь в директиве я слушаю $rootScope
и on данное изменение - я реагирую соответственно. Очень легкий и элегантный!
Я пришел к этому вопросу, но оказалось, что моя проблема заключалась в том, что я использовал setInterval, когда я должен был использовать поставщик angular $interval. Это также относится к setTimeout (вместо этого используется $timeout). Я знаю, что это не ответ на вопрос ОП, но он может помочь некоторым, поскольку это помогло мне.
setTimeout
или любую другую неангулярную функцию, но только не забудьте обернуть код в обратный вызов с помощью $scope.$apply()
.
столкнувшись с очень похожими проблемами, я просмотрел функцию в области видимости и возвратил функцию в переменную службы. Я создал js fiddle. вы можете найти код ниже.
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'];
//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;
});
});
Для таких, как я, просто ищет простое решение, это почти то, что вы ожидаете от использования обычных $watch в контроллерах. Единственное различие заключается в том, что он оценивает строку в контексте javascript, а не в определенной области. Вам нужно будет ввести $rootScope в свою службу, хотя он используется только для правильного подключения к дайджесту.
function watch(target, callback, deep) {
$rootScope.$watch(function () {return eval(target);}, callback, deep);
};
Немного уродливый, но я добавил регистрацию переменных области для моей службы для переключения:
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');
}
this
из этого служения вместо self
?
Посмотрите на этот плункер:: это самый простой пример, который я мог бы придумать
<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;
});
Я написал две простые служебные службы, которые помогают мне отслеживать изменения свойств службы.
Если вы хотите пропустить длинное объяснение, вы можете перейти к 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 $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;
};
}
Эта служба используется в контроллере следующим образом: Предположим, у вас есть служба "testService" со свойствами "prop1", "prop2", "prop3". Вы хотите посмотреть и присвоить области "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));
}
Итак, все это будет выглядеть так (вы можете запустить его или открыть скрипку):
// 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="/jquery.min.js"></script>
<script src="/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>
Я опаздываю на эту часть, но я нашел более удобный способ сделать это, чем ответ, опубликованный выше. Вместо того, чтобы назначать переменную для хранения значения служебной переменной, я создал функцию, прикрепленную к области, которая возвращает служебную переменную.
контроллер
$scope.foo = function(){
return aService.foo;
}
Я думаю, что это будет делать то, что вы хотите. Мой контроллер продолжает проверять ценность моего сервиса с этой реализацией. Честно говоря, это намного проще, чем выбранный ответ.
Я видел здесь несколько ужасных шаблонов наблюдателей, которые вызывают утечку памяти в больших приложениях.
Возможно, я немного опоздаю, но это так просто, как это.
Функция часов отслеживает изменения ссылок (примитивные типы), если вы хотите посмотреть что-то вроде push массива, просто используйте:
someArray.push(someObj); someArray = someArray.splice(0);
Это обновит ссылку и обновит часы из любого места. Включая метод getter услуг. Все, что примитив будет обновляться автоматически.