Я натрапив на це питання, шукаючи щось подібне, але вважаю, що воно заслуговує ґрунтовного пояснення того, що відбувається, а також деяких додаткових рішень.
Коли кутове вираження, таке, яке ви використовували, присутнє в 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>
Як ви можете бачити, нг-повтор нібито додається до aService.fooне оновлюється , коли aService.fooзміни, але нг повтор додається до aService2.foo робить . Це тому, що наше посилання на aService.fooзастаріле, але наше посилання на aService2.fooце не так. Ми створили посилання на початковий масив з $scope.foo = aService.foo;, який потім було відкинуто службою під час наступного оновлення, тобто $scope.fooбільше не посилався на масив, на який ми хотіли.
Однак, хоча існує кілька способів переконатися, що початкове посилання зберігається в такті, іноді може знадобитися змінити об'єкт або масив. Або що робити, якщо властивість служби посилається на примітив типу a 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="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()в заданій функції. Це гарантує, що Angular буде в курсі будь-яких змін service.foo. Якщо у вас виникли помилки "inprog", перегляньте цю корисну довідкову сторінку , або якщо ви використовуєте Angular> = 1.3, ви можете просто скористатися $rootScope.$applyAsync().
Також будьте обережні, якщо aService.fooвін оновлюється дуже часто, оскільки це може суттєво вплинути на ефективність роботи. Якщо продуктивність буде проблемою, ви можете встановити шаблон спостерігача, подібний до інших відповідей тут, використовуючи сеттер.