Bu soruyu benzer bir şey aramak için tökezledim, ancak neler olduğunu ve bazı ek çözümleri tam olarak açıklamayı hak ettiğini düşünüyorum.
HTML'de kullandığınız gibi bir açısal ifade mevcut olduğunda, Angular otomatik olarak bir $watch
for ayarlar $scope.foo
ve her $scope.foo
değiştiğinde HTML'yi günceller .
<div ng-controller="FooCtrl">
<div ng-repeat="item in foo">{{ item }}</div>
</div>
Buradaki söylenmeyen sorun, iki şeyden birinin aService.foo
, değişikliklerin tespit edilmeyeceği şekilde etkilemesidir . Bu iki olasılık:
aService.foo
her seferinde yeni bir diziye ayarlanıyor ve referansın eski olmasına neden oluyor.
aService.foo
güncellemede bir $digest
döngü tetiklenmeyecek şekilde güncellenmektedir.
Sorun 1: Eski Referanslar
İlk olasılık göz önüne alındığında, a'nın $digest
uygulandığı varsayılarak aService.foo
, her zaman aynı dizi $watch
olsaydı , otomatik ayarlanan , aşağıdaki kod snippet'inde gösterildiği gibi değişiklikleri algılar.
Çözüm 1-a: Dizi veya nesnenin her güncellemede aynı nesne olduğundan emin olun
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>
Gördüğünüz gibi, sözde bağlı ng-repeat aService.foo
zaman güncelleme yok aService.foo
değişiklikleri, ancak bağlı ng-repeat aService2.foo
yapar . Bunun nedeni, referansımızın aService.foo
modası geçmiş olmasıdır, ancak referansımız aService2.foo
değildir. İlk diziye bir başvuru oluşturduk $scope.foo = aService.foo;
; bu, daha sonra bir sonraki güncellemesinde hizmet tarafından atıldı, yani $scope.foo
artık istediğimiz diziye artık başvurulmuyor.
Bununla birlikte, ilk referansın inceliğini koruduğundan emin olmanın birkaç yolu olsa da, bazen nesneyi veya diziyi değiştirmek gerekebilir. Veya service özelliği, a String
veya gibi bir ilkeye başvuruyorsa ne olur Number
? Bu gibi durumlarda, sadece bir referansa güvenemeyiz. Peki ne yapabiliriz ?
Daha önce verilen cevapların birçoğu zaten bu soruna bazı çözümler veriyor. Ancak, ben şahsen Jin ve thetallweeks tarafından yorumlarda önerilen basit yöntemi kullanmaktan yanayım :
html biçimlendirmesinde aService.foo dosyasına başvurmanız yeterlidir
Çözüm 1-b: Hizmeti kapsama ekleyin {service}.{property}
ve HTML'ye bakın.
Anlamı, sadece şunu yapın:
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>
Bu şekilde, her biri üzerinde doğru şekilde güncellenen değeri alacak olan $watch
çözülecektir .aService.foo
$digest
Bu, geçici çözümünüzle yapmaya çalıştığınız şeydir, ancak çok daha az bir şekilde. $watch
Denetleyiciye foo
, $scope
her değiştiğinde açıkça atan bir gereksiz eklediniz . O ekstra gerekmez $watch
Eklemek zaman aService
yerine aService.foo
kadar $scope
bağlamak açıkça etmek ve aService.foo
işaretlemesinde.
Şimdi bir $digest
döngü uygulandığı varsayılarak her şey yolunda ve güzel . Yukarıdaki örneklerimde, her güncellemeden sonra $interval
otomatik olarak bir $digest
döngüyü başlatan dizileri güncellemek için Angular'ın hizmetini kullandım . Peki ya hizmet değişkenleri (herhangi bir nedenle) "Açısal dünya" içinde güncellenmiyorsa. Başka bir deyişle, service özelliği her değiştiğinde otomatik olarak etkinleştirilen bir döngümüz yok$digest
mu?
Sorun 2: Eksik $digest
Buradaki çözümlerin çoğu bu sorunu çözecektir, ancak Code Whisperer'a katılıyorum :
Angular gibi bir çerçeve kullanmamızın nedeni, kendi gözlemci kalıplarımızı pişirmemek.
Bu nedenle, aService.foo
referansı yukarıdaki ikinci örnekte gösterildiği gibi HTML biçimlendirmesinde kullanmaya devam etmeyi tercih ediyorum ve Denetleyici içinde ek bir geri arama kaydetmek zorunda değilim.
Çözüm 2: Bir ayarlayıcı ve alıcı kullanın. $rootScope.$apply()
Kimse henüz bir pasör ve alıcı kullanımını önermedi şaşırdı . Bu yetenek ECMAScript5'te tanıtıldı ve bu nedenle yıllardır varlığını sürdürüyor. Tabii ki, bu, hangi nedenle olursa olsun, gerçekten eski tarayıcıları desteklemeniz gerekiyorsa, o zaman bu yöntem işe yaramaz, ancak alıcıların ve ayarlayıcıların JavaScript'te çok az kullanıldığını hissediyorum. Bu özel durumda, oldukça yararlı olabilirler:
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>
Burada hizmet fonksiyonunda bir 'özel' değişkeni eklendi: realFoo
. Bu , nesnede sırasıyla get foo()
ve set foo()
işlevleri kullanılarak güncellenir ve alınır service
.
$rootScope.$apply()
Set fonksiyonunda kullanımına dikkat edin. Bu, Angular'ın herhangi bir değişiklikten haberdar olmasını sağlar service.foo
. Eğer 'inprog' hataları alıyorsanız, bu faydalı referans sayfasına bakın ya da Açısal> = 1.3 kullanıyorsanız sadece kullanabilirsiniz $rootScope.$applyAsync()
.
Ayrıca aService.foo
çok sık güncelleniyorsa buna dikkat edin , çünkü bu performansı önemli ölçüde etkileyebilir. Performans bir sorun teşkil ediyorsa, ayarlayıcıyı kullanarak diğer yanıtlara benzer bir gözlemci modeli ayarlayabilirsiniz.