Tôi vấp phải câu hỏi này để tìm kiếm một cái gì đó tương tự, nhưng tôi nghĩ rằng nó xứng đáng được giải thích kỹ lưỡng về những gì đang xảy ra, cũng như một số giải pháp bổ sung.
Khi một biểu thức góc như biểu thức bạn đã sử dụng có trong HTML, Angular sẽ tự động thiết lập một $watch
for $scope.foo
và sẽ cập nhật HTML bất cứ khi nào $scope.foo
thay đổi.
<div ng-controller="FooCtrl">
<div ng-repeat="item in foo">{{ item }}</div>
</div>
Vấn đề chưa được giải quyết ở đây là một trong hai điều đang ảnh hưởng đến aService.foo
mức những thay đổi không bị phát hiện. Hai khả năng này là:
aService.foo
đang được thiết lập thành một mảng mới mỗi lần, khiến tham chiếu đến nó bị lỗi thời.
aService.foo
đang được cập nhật theo cách mà một $digest
chu kỳ không được kích hoạt trên bản cập nhật.
Vấn đề 1: Tài liệu tham khảo lỗi thời
Xem xét khả năng đầu tiên, giả sử một $digest
đang được áp dụng, nếu aService.foo
luôn luôn là cùng một mảng, tự động được đặt$watch
sẽ phát hiện các thay đổi, như được hiển thị trong đoạn mã dưới đây.
Giải pháp 1-a: Đảm bảo mảng hoặc đối tượng là cùng một đối tượng trên mỗi bản cập nhật
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>
Như bạn có thể thấy, ng-repeat được cho là gắn liền với aService.foo
không cập nhật khi aService.foo
thay đổi, nhưng những ng-repeat gắn liền với aService2.foo
thực hiện . Điều này là do tham chiếu của chúng tôi aService.foo
đã lỗi thời, nhưng tham chiếu của chúng tôi aService2.foo
thì không. Chúng tôi đã tạo một tham chiếu đến mảng ban đầu $scope.foo = aService.foo;
, sau đó bị dịch vụ loại bỏ trên bản cập nhật tiếp theo, nghĩa là$scope.foo
không còn tham chiếu mảng chúng tôi muốn nữa.
Tuy nhiên, trong khi có một số cách để đảm bảo tham chiếu ban đầu được giữ trong chiến thuật, đôi khi có thể cần phải thay đổi đối tượng hoặc mảng. Hoặc nếu tài sản dịch vụ tham chiếu một nguyên thủy như một String
hoặc Number
? Trong những trường hợp đó, chúng ta không thể chỉ dựa vào một tài liệu tham khảo. Vậy những gì có thể chúng ta làm gì?
Một số câu trả lời được đưa ra trước đây đã đưa ra một số giải pháp cho vấn đề đó. Tuy nhiên, cá nhân tôi ủng hộ việc sử dụng phương pháp đơn giản được đề xuất bởi Jin và thetallweek trong các bình luận:
chỉ cần tham khảo aService.foo trong đánh dấu html
Giải pháp 1-b: Đính kèm dịch vụ vào phạm vi và tham chiếu {service}.{property}
trong HTML.
Có nghĩa là, chỉ cần làm điều này:
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>
Bằng cách đó, ý $watch
chí sẽ giải quyết aService.foo
trên mỗi$digest
, sẽ nhận được giá trị cập nhật chính xác.
Đây là loại những gì bạn đã cố gắng thực hiện với cách giải quyết của bạn, nhưng theo cách ít vòng hơn nhiều. Bạn đã thêm không cần thiết $watch
trong bộ điều khiển mà rõ ràng đặt foo
trên $scope
bất cứ khi nào nó thay đổi. Bạn không cần phải thêm rằng $watch
khi bạn đính kèm aService
thay vì aService.foo
đến $scope
, và ràng buộc rõ ràng để aService.foo
trong đánh dấu.
Bây giờ tất cả đều tốt và tốt khi giả định một $digest
chu kỳ đang được áp dụng. Trong các ví dụ của tôi ở trên, tôi đã sử dụng $interval
dịch vụ của Angular để cập nhật các mảng, tự động khởi động một $digest
vòng lặp sau mỗi lần cập nhật. Nhưng điều gì xảy ra nếu các biến dịch vụ (vì bất kỳ lý do gì) không được cập nhật bên trong "Thế giới góc cạnh". Nói cách khác, chúng ta không có $digest
chu trình được kích hoạt tự động mỗi khi thuộc tính dịch vụ thay đổi?
Vấn đề 2: Mất tích $digest
Nhiều giải pháp ở đây sẽ giải quyết vấn đề này, nhưng tôi đồng ý với Code Whisperer :
Lý do tại sao chúng tôi sử dụng một khung như Angular là để không nấu các mẫu quan sát riêng của chúng tôi
Vì vậy, tôi muốn tiếp tục sử dụng aService.foo
tham chiếu trong đánh dấu HTML như trong ví dụ thứ hai ở trên và không phải đăng ký một cuộc gọi lại bổ sung trong Bộ điều khiển.
Giải pháp 2: Sử dụng setter và getter với $rootScope.$apply()
Tôi đã ngạc nhiên khi chưa có ai đề nghị sử dụng setter và getter . Khả năng này đã được giới thiệu trong ECMAScript5, và do đó đã có từ nhiều năm nay. Tất nhiên, điều đó có nghĩa là nếu vì bất kỳ lý do gì, bạn cần hỗ trợ các trình duyệt thực sự cũ, thì phương pháp này sẽ không hoạt động, nhưng tôi cảm thấy như getters và setters được sử dụng rất nhiều trong JavaScript. Trong trường hợp cụ thể này, chúng có thể khá hữu ích:
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>
Ở đây tôi đã thêm một biến 'riêng tư' trong chức năng dịch vụ : realFoo
. Điều này nhận được cập nhật và truy xuất bằng cách sử dụng get foo()
và các set foo()
chức năng tương ứng trênservice
đối tượng.
Lưu ý việc sử dụng $rootScope.$apply()
trong chức năng thiết lập. Điều này đảm bảo rằng Angular sẽ nhận thức được bất kỳ thay đổi nào service.foo
. Nếu bạn gặp lỗi 'inprog', hãy xem trang tham khảo hữu ích này hoặc nếu bạn sử dụng Angular> = 1.3, bạn chỉ có thể sử dụng $rootScope.$applyAsync()
.
Ngoài ra, hãy cảnh giác với điều này nếu aService.foo
được cập nhật rất thường xuyên, vì điều đó có thể ảnh hưởng đáng kể đến hiệu suất. Nếu hiệu suất là một vấn đề, bạn có thể thiết lập một mẫu quan sát tương tự như các câu trả lời khác ở đây bằng cách sử dụng bộ cài đặt.