Mi sono imbattuto in questa domanda alla ricerca di qualcosa di simile, ma penso che meriti una spiegazione approfondita di ciò che sta accadendo, nonché alcune soluzioni aggiuntive.
Quando un'espressione angolare come quella che hai usato è presente nell'HTML, Angular imposta automaticamente un $watchper $scope.fooe aggiornerà l'HTML ogni volta che $scope.foocambia.
<div ng-controller="FooCtrl">
<div ng-repeat="item in foo">{{ item }}</div>
</div>
Il problema non detto qui è che una delle due cose sta interessando in modo aService.foo tale che i cambiamenti non vengano rilevati. Queste due possibilità sono:
aService.foo viene impostato su un nuovo array ogni volta, rendendo obsoleto il riferimento ad esso.
aService.fooviene aggiornato in modo tale che $digestnon venga attivato un ciclo sull'aggiornamento.
Problema 1: riferimenti obsoleti
Considerando la prima possibilità, supponendo che $digestvenga applicato a, se aService.foofosse sempre lo stesso array, l'impostazione automatica $watchrileverà le modifiche, come mostrato nello snippet di codice di seguito.
Soluzione 1-a: assicurarsi che l'array o l'oggetto sia lo stesso oggetto su ogni aggiornamento
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>
Come puoi vedere, la ng-repeat presumibilmente allegata aService.foonon si aggiorna quando aService.foocambia, ma la ng-repeat allegata aService2.foo fa . Questo perché il nostro riferimento a non aService.fooè aggiornato, ma il nostro riferimento aService2.foonon lo è. Abbiamo creato un riferimento all'array iniziale con $scope.foo = aService.foo;, che è stato quindi scartato dal servizio al suo prossimo aggiornamento, il che significa che $scope.foonon faceva più riferimento all'array che desideravamo più.
Tuttavia, mentre ci sono diversi modi per assicurarsi che il riferimento iniziale sia mantenuto intatto, a volte può essere necessario cambiare l'oggetto o l'array. O se la proprietà del servizio fa riferimento a una primitiva come a Stringo Number? In questi casi, non possiamo semplicemente fare affidamento su un riferimento. Allora, cosa possiamo fare?
Molte delle risposte fornite in precedenza forniscono già alcune soluzioni a questo problema. Tuttavia, sono personalmente favorevole all'utilizzo del metodo semplice suggerito da Jin e dalle settimane successive nei commenti:
fai semplicemente riferimento aService.foo nel markup html
Soluzione 1-b: collegare il servizio all'ambito e fare riferimento {service}.{property}in HTML.
Significato, basta fare questo:
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>
In questo modo, $watchverrà risolto aService.foosu ciascuno $digest, che otterrà il valore aggiornato correttamente.
Questo è un po 'quello che stavi cercando di fare con la soluzione alternativa, ma in modo molto meno circolare. È stato aggiunto un elemento superfluo $watchnel controller che fooattiva esplicitamente $scopeogni volta che cambia. Non hai bisogno di quel extra $watchquando ti colleghi aServiceinvece aService.fooal $scope, e ti leghi esplicitamente al aService.foomarkup.
Ora va tutto bene, supponendo che $digestvenga applicato un ciclo. Nei miei esempi precedenti, ho usato il $intervalservizio di Angular per aggiornare gli array, che avvia automaticamente un $digestloop dopo ogni aggiornamento. Ma cosa succede se le variabili di servizio (per qualsiasi motivo) non vengono aggiornate all'interno del "mondo angolare". In altre parole, non$digest viene attivato automaticamente un ciclo ogni volta che cambia la proprietà del servizio?
Problema 2: mancante $digest
Molte delle soluzioni qui risolveranno questo problema, ma sono d'accordo con Code Whisperer :
Il motivo per cui stiamo usando un framework come Angular è di non preparare i nostri schemi di osservatori
Pertanto, preferirei continuare a utilizzare il aService.fooriferimento nel markup HTML come mostrato nel secondo esempio sopra e non dovrei registrare un callback aggiuntivo all'interno del controller.
Soluzione 2: utilizzare un setter e getter con $rootScope.$apply()
Sono stato sorpreso che nessuno abbia ancora suggerito l'uso di un setter e getter . Questa funzionalità è stata introdotta in ECMAScript5 e esiste da anni ormai. Naturalmente, ciò significa che, per qualsiasi motivo, è necessario supportare browser molto vecchi, questo metodo non funzionerà, ma mi sento come se i getter e i setter fossero ampiamente sottoutilizzati in JavaScript. In questo caso particolare, potrebbero essere abbastanza utili:
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>
Qui ho aggiunto una variabile 'privato' nella funzione di servizio: realFoo. Questo viene aggiornato e recuperato usando rispettivamente le funzioni get foo()e sull'oggetto.set foo()service
Notare l'uso di $rootScope.$apply()nella funzione impostata. Ciò garantisce che Angular sia a conoscenza di eventuali modifiche a service.foo. Se ricevi errori 'inprog', vedi questa utile pagina di riferimento o se usi Angular> = 1.3 puoi semplicemente usare $rootScope.$applyAsync().
Diffidare anche di questo se aService.fooviene aggiornato molto frequentemente, poiché ciò potrebbe influire in modo significativo sulle prestazioni. Se le prestazioni rappresentano un problema, è possibile impostare un modello di osservatore simile alle altre risposte qui utilizzando il setter.