Я наткнулся на этот вопрос в поисках чего-то похожего, но я думаю, что он заслуживает подробного объяснения того, что происходит, а также некоторых дополнительных решений.
Когда выражение 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
не запускается при обновлении.
Проблема 1: устаревшие ссылки
Рассматривая первую возможность, предполагая, что применяется $digest
, если aService.foo
всегда был одним и тем же массивом, автоматически установленный $watch
обнаружит изменения, как показано в фрагменте кода ниже.
Решение 1-а: убедитесь, что массив или объект является одним и тем же объектом при каждом обновлении.
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>
Как видите, 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
Решение 1-b: прикрепите службу к области и укажите {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="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
в контроллер, который явно помещает foo
в $scope
при каждом изменении. Вам не понадобится этот дополнительный $watch
, когда вы прикрепляете aService
вместо aService.foo
к $scope
и явно привязываете к aService.foo
в разметке.
Теперь все хорошо при условии, что применяется $digest
цикл. В моих примерах выше я использовал службу Angular $interval
для обновления массивов, который автоматически запускает цикл $digest
после каждого обновления. Но что, если служебные переменные (по какой-либо причине) не обновляются в «мире Angular». Другими словами, у нас нет автоматической активации $digest
цикла при изменении свойства службы?
Проблема 2. Отсутствует $digest
Многие из представленных здесь решений решают эту проблему, но я согласен с Code Whisperer:
Причина, по которой мы используем такой фреймворк, как Angular, заключается в том, чтобы не придумывать собственные шаблоны наблюдателя.
Поэтому я бы предпочел продолжать использовать ссылку aService.foo
в разметке HTML, как показано во втором примере выше, и не регистрировать дополнительный обратный вызов в контроллере.
Решение 2. Используйте сеттер и получатель с $rootScope.$apply()
Я был удивлен, что никто еще не предложил использовать сеттер и получатель. Эта возможность была введена в ECMAScript5 и, таким образом, существует уже много лет. Конечно, это означает, что если по какой-либо причине вам нужно поддерживать действительно старые браузеры, этот метод не будет работать, но я чувствую, что геттеры и сеттеры в 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>
Здесь я добавил в служебную функцию «частную» переменную: realFoo
. Это get обновляется и извлекается с помощью функций get foo()
и set foo()
соответственно для объекта service
.
Обратите внимание на использование $rootScope.$apply()
в функции set. Это гарантирует, что Angular будет знать обо всех изменениях в service.foo
. Если вы получаете сообщения об ошибках inprog, перейдите на эту полезную справочную страницу, или если вы используете Angular> = 1.3 вы можете просто использовать $rootScope.$applyAsync()
.
Также будьте осторожны, если aService.foo
обновляется очень часто, поскольку это может значительно повлиять на производительность. Если производительность будет проблемой, вы можете настроить шаблон наблюдателя, аналогичный другим ответам здесь, с помощью установщика.
person
NanoWizard
schedule
11.11.2015
aService.foo
в разметке html? (например: plnkr.co/edit/aNrw5Wo4Q0IxR2loipl5?p=preview) - person thetallweeks   schedule 01.10.2014