Я натрапив на це питання, шукаючи щось подібне, але вважаю, що воно заслуговує ґрунтовного пояснення того, що відбувається, а також деяких додаткових рішень.
Коли кутове вираження, таке, яке ви використовували, присутнє в 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
він оновлюється дуже часто, оскільки це може суттєво вплинути на ефективність роботи. Якщо продуктивність буде проблемою, ви можете встановити шаблон спостерігача, подібний до інших відповідей тут, використовуючи сеттер.