Natknąłem się na to pytanie, szukając czegoś podobnego, ale myślę, że zasługuje na dokładne wyjaśnienie tego, co się dzieje, a także kilka dodatkowych rozwiązań.
Gdy wyrażenie HTML, takie jak użyte, jest obecne w kodzie HTML, Angular automatycznie konfiguruje $watchfor $scope.fooi będzie aktualizować HTML przy każdej $scope.foozmianie.
<div ng-controller="FooCtrl">
<div ng-repeat="item in foo">{{ item }}</div>
</div>
Niewypowiedzianym problemem jest to, że jedna z dwóch rzeczy wpływa na to aService.foo , że zmiany nie są wykrywane. Te dwie możliwości to:
aService.foo ustawia się za każdym razem do nowej tablicy, powodując, że odwołanie do niej jest nieaktualne.
aService.foojest aktualizowany w taki sposób, że $digestcykl nie jest uruchamiany podczas aktualizacji.
Problem 1: Nieaktualne referencje
Biorąc pod uwagę pierwszą możliwość, zakładając, że $digeststosuje się a, jeśli aService.foozawsze była to ta sama tablica, zestaw automatycznie $watchwykryłby zmiany, jak pokazano w poniższym fragmencie kodu.
Rozwiązanie 1-a: Upewnij się, że tablica lub obiekt jest tym samym obiektem przy każdej aktualizacji
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>
Jak widać, NG-repeat rzekomo przypisane do aService.foonie aktualizuje kiedy aService.foozmiany, ale ng-repeat dołączone do aService2.foo robi . Jest tak, ponieważ nasze odniesienie do aService.foojest nieaktualne, ale nasze odniesienie do aService2.foonie jest. Utworzyliśmy odwołanie do początkowej tablicy $scope.foo = aService.foo;, która została następnie odrzucona przez usługę przy następnej aktualizacji, co oznacza, że $scope.foonie odnosi się już do tablicy, której chcieliśmy.
Jednakże, chociaż istnieje kilka sposobów, aby upewnić się, że początkowe odniesienie jest utrzymywane w takcie, czasami może być konieczna zmiana obiektu lub tablicy. A co jeśli właściwość usługi odwołuje się do prymitywu takiego jak Stringlub Number? W takich przypadkach nie możemy po prostu polegać na referencji. Więc co można zrobić?
Kilka podanych wcześniej odpowiedzi już daje pewne rozwiązania tego problemu. Jednak osobiście opowiadam się za zastosowaniem prostej metody sugerowanej przez Jina i thetallweeks w komentarzach:
po prostu odwołaj aService.foo w znaczniku HTML
Rozwiązanie 1-b: Dołącz usługę do zakresu i odwołanie {service}.{property}w kodzie HTML.
Czyli po prostu zrób to:
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>
W ten sposób $watchzostanie rozwiązany aService.fookażdy $digest, który uzyska poprawnie zaktualizowaną wartość.
Jest to rodzaj tego, co próbujesz zrobić ze swoim obejściem, ale w znacznie mniejszym stopniu. Dodałeś niepotrzebne $watchw kontrolerze, który jawnie nakłada foosię za $scopekażdym razem, gdy się zmienia. Nie potrzebujesz tego dodatkowego, $watchgdy dołączasz aServicezamiast aService.foodo $scopei przypisujesz się bezpośrednio do aService.fooznaczników.
To wszystko dobrze i dobrze, jeśli zastosuje się $digestcykl. W powyższych przykładach użyłem $intervalusługi Angulara do aktualizacji tablic, która automatycznie uruchamia $digestpętlę po każdej aktualizacji. Ale co, jeśli zmienne serwisowe (z jakiegokolwiek powodu) nie są aktualizowane w „świecie Angular”. Innymi słowy, czy nie mamy $digestcyklu aktywowanego automatycznie przy każdej zmianie właściwości usługi?
Problem 2: Brak $digest
Wiele rozwiązań tutaj rozwiązuje ten problem, ale zgadzam się z Code Whisperer :
Powodem, dla którego używamy frameworku takiego jak Angular, jest nie gotowanie własnych wzorców obserwatorów
Dlatego wolałbym nadal używać aService.fooodwołania w znacznikach HTML, jak pokazano w drugim przykładzie powyżej, i nie musiałbym rejestrować dodatkowego wywołania zwrotnego w kontrolerze.
Rozwiązanie 2: Użyj setera i gettera z $rootScope.$apply()
Byłem zaskoczony, że nikt jeszcze nie zasugerował użycia setera i gettera . Ta funkcja została wprowadzona w ECMAScript5 i dlatego istnieje już od lat. Oczywiście oznacza to, że jeśli z jakiegoś powodu musisz obsługiwać naprawdę stare przeglądarki, ta metoda nie będzie działać, ale wydaje mi się, że JavaScript i gettery są znacznie słabo wykorzystywane. W tym konkretnym przypadku mogą być bardzo przydatne:
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>
Tutaj dodałem „prywatny” zmienną w funkcji serwisowej: realFoo. Ten plik jest aktualizowany i pobierany przy użyciu odpowiednio funkcji get foo()i set foo()na serviceobiekcie.
Zwróć uwagę na użycie $rootScope.$apply()funkcji set. Zapewnia to, że Angular będzie świadomy wszelkich zmian w service.foo. Jeśli pojawią się błędy „inprog”, zobacz tę przydatną stronę referencyjną lub jeśli używasz Angular> = 1.3, możesz po prostu użyć $rootScope.$applyAsync().
Uważaj na to, jeśli aService.foojest bardzo często aktualizowany, ponieważ może to znacząco wpłynąć na wydajność. Jeśli wydajność byłaby problemem, możesz ustawić wzorzec obserwatora podobny do innych odpowiedzi tutaj za pomocą setera.