Saya menemukan pertanyaan ini mencari sesuatu yang serupa, tetapi saya pikir itu layak mendapatkan penjelasan menyeluruh tentang apa yang terjadi, serta beberapa solusi tambahan.
Ketika ekspresi sudut seperti yang Anda gunakan ada dalam HTML, Angular secara otomatis menyiapkan a $watch
untuk $scope.foo
, dan akan memperbarui HTML setiap kali ada $scope.foo
perubahan.
<div ng-controller="FooCtrl">
<div ng-repeat="item in foo">{{ item }}</div>
</div>
Masalah yang tidak terungkap di sini adalah bahwa satu dari dua hal mempengaruhi aService.foo
sehingga perubahan tidak terdeteksi. Dua kemungkinan ini adalah:
aService.foo
semakin diatur ke array baru setiap kali, menyebabkan referensi untuk itu menjadi usang.
aService.foo
sedang diperbarui sedemikian rupa sehingga $digest
siklus tidak dipicu pada pembaruan.
Masalah 1: Referensi yang sudah ketinggalan zaman
Mempertimbangkan kemungkinan pertama, dengan asumsi a $digest
sedang diterapkan, jika aService.foo
selalu array yang sama, set otomatis $watch
akan mendeteksi perubahan, seperti yang ditunjukkan dalam cuplikan kode di bawah ini.
Solusi 1-a: Pastikan array atau objek adalah objek yang sama pada setiap pembaruan
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>
Seperti yang Anda lihat, ng-repeat yang seharusnya dilampirkan aService.foo
tidak memperbarui ketika aService.foo
perubahan, tetapi ng-repeat yang dilampirkan aService2.foo
tidak . Ini karena referensi kami kepada aService.foo
sudah usang, tetapi referensi kami untuk aService2.foo
tidak. Kami membuat referensi ke array awal dengan $scope.foo = aService.foo;
, yang kemudian dibuang oleh layanan pada pembaruan berikutnya, yang berarti $scope.foo
tidak lagi mereferensikan array yang kami inginkan lagi.
Namun, sementara ada beberapa cara untuk memastikan referensi awal disimpan dalam kebijaksanaan, kadang-kadang mungkin perlu untuk mengubah objek atau array. Atau bagaimana jika properti layanan merujuk ke primitif seperti String
atau Number
? Dalam kasus itu, kita tidak bisa hanya mengandalkan referensi. Jadi apa yang bisa kita lakukan?
Beberapa jawaban yang diberikan sebelumnya sudah memberikan beberapa solusi untuk masalah itu. Namun, secara pribadi saya mendukung penggunaan metode sederhana yang disarankan oleh Jin dan semuanya dalam komentar:
cukup referensi aService.foo di markup html
Solusi 1-b: Pasang layanan ke lingkup, dan referensi {service}.{property}
dalam HTML.
Artinya, lakukan saja ini:
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>
Dengan begitu, $watch
akan menyelesaikan aService.foo
masing-masing $digest
, yang akan mendapatkan nilai yang diperbarui dengan benar.
Ini adalah jenis apa yang Anda coba lakukan dengan solusi Anda, tetapi dalam cara yang jauh lebih sedikit. Anda menambahkan yang tidak perlu $watch
di controller yang secara eksplisit menempatkan foo
pada $scope
setiap kali perubahan. Anda tidak perlu ekstra $watch
saat melampirkan aService
daripada aService.foo
ke $scope
, dan mengikat secara eksplisit ke aService.foo
dalam markup.
Nah, itu semua baik dan bagus dengan asumsi $digest
siklus sedang diterapkan. Dalam contoh saya di atas, saya menggunakan layanan Angular $interval
untuk memperbarui array, yang secara otomatis memulai $digest
loop setelah setiap pembaruan. Tetapi bagaimana jika variabel layanan (untuk alasan apa pun) tidak diperbarui di "dunia sudut". Dengan kata lain, kami tidak memiliki $digest
siklus yang diaktifkan secara otomatis setiap kali properti layanan berubah?
Masalah 2: Hilang $digest
Banyak solusi di sini akan menyelesaikan masalah ini, tetapi saya setuju dengan Code Whisperer :
Alasan mengapa kita menggunakan kerangka kerja seperti Angular adalah untuk tidak memasak pola pengamat kita sendiri
Oleh karena itu, saya lebih suka untuk terus menggunakan aService.foo
referensi dalam markup HTML seperti yang ditunjukkan pada contoh kedua di atas, dan tidak harus mendaftarkan panggilan balik tambahan dalam Controller.
Solusi 2: Gunakan setter dan pengambil $rootScope.$apply()
Saya terkejut belum ada yang menyarankan penggunaan setter dan pengambil . Kemampuan ini diperkenalkan di ECMAScript5, dan telah ada selama bertahun-tahun sekarang. Tentu saja, itu berarti jika, untuk alasan apa pun, Anda perlu mendukung browser yang sangat lama, maka metode ini tidak akan berfungsi, tetapi saya merasa seperti getter dan setter sangat kurang digunakan dalam JavaScript. Dalam kasus khusus ini, mereka bisa sangat berguna:
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>
Di sini saya menambahkan variabel 'pribadi' dalam fungsi pelayanan: realFoo
. Get ini diperbarui dan diambil menggunakan get foo()
dan set foo()
fungsi masing-masing pada service
objek.
Perhatikan penggunaan $rootScope.$apply()
dalam fungsi yang ditetapkan. Ini memastikan bahwa Angular akan mengetahui adanya perubahan service.foo
. Jika Anda mendapatkan kesalahan 'inprog' lihat halaman referensi yang berguna ini , atau jika Anda menggunakan Angular> = 1.3 Anda bisa menggunakan $rootScope.$applyAsync()
.
Berhati-hatilah dengan hal ini jika aService.foo
diperbarui dengan sangat sering, karena hal itu dapat berdampak signifikan terhadap kinerja. Jika kinerja akan menjadi masalah, Anda bisa mengatur pola pengamat yang mirip dengan jawaban lain di sini menggunakan setter.