Я наткнулся на этот вопрос в поисках чего-то похожего, но думаю, что оно заслуживает подробного объяснения происходящего, а также некоторых дополнительных решений.
Когда угловое выражение, такое как то, которое вы использовали, присутствует в 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
что применяется a , если бы 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>
Как вы можете видеть, ng-repeat, предположительно прикрепленный к aService.foo
, не обновляется при aService.foo
изменениях, но ng-repeat, прикрепленный к, aService2.foo
делает . Это потому, что наша ссылка на aService.foo
устарела, а наша ссылка на aService2.foo
нет. Мы создали ссылку на исходный массив with $scope.foo = aService.foo;
, который затем был отброшен службой при следующем обновлении, что означает, что мы $scope.foo
больше не ссылаемся на массив, который нам нужен.
Однако, хотя есть несколько способов убедиться, что исходная ссылка сохраняется в такте, иногда может потребоваться изменить объект или массив. Или что, если свойство service ссылается на примитив, такой как String
или Number
? В этих случаях мы не можем просто полагаться на ссылку. Так что можно делать?
Несколько ответов, приведенных ранее, уже дают некоторые решения этой проблемы. Тем не менее, я лично за использование простого метода, предложенного Jin и 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="angular.js@1.4.7" 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
цикл. В моих примерах выше я использовал $interval
сервис Angular для обновления массивов, который автоматически запускает $digest
цикл после каждого обновления. Но что, если служебные переменные (по какой-либо причине) не обновляются в «угловом мире». Другими словами, мы DonT есть $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 foo()
и set foo()
функции соответственно на service
объекте.
Обратите внимание на использование $rootScope.$apply()
в функции set. Это гарантирует, что Angular будет в курсе любых изменений service.foo
. Если вы получаете ошибки 'inprog', посмотрите эту полезную справочную страницу , или если вы используете Angular> = 1.3, вы можете просто использовать $rootScope.$applyAsync()
.
Также будьте осторожны с этим, если aService.foo
обновляется очень часто, так как это может значительно повлиять на производительность. Если производительность будет проблемой, вы можете установить шаблон наблюдателя, подобный другим ответам здесь, используя установщик.