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 $watchfor $scope.foovà sẽ cập nhật HTML bất cứ khi nào $scope.foothay đổ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 $digestchu 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.fooluô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.fookhông cập nhật khi aService.foothay đổ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.foothì 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 Stringhoặ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 đó, ý $watchchí sẽ giải quyết aService.footrê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 $watchtrong bộ điều khiển mà rõ ràng đặt footrên $scopebất cứ khi nào nó thay đổi. Bạn không cần phải thêm rằng $watchkhi bạn đính kèm aServicethay vì aService.foođến $scope, và ràng buộc rõ ràng để aService.footrong đánh dấu.
Bây giờ tất cả đều tốt và tốt khi giả định một $digestchu kỳ đang được áp dụng. Trong các ví dụ của tôi ở trên, tôi đã sử dụng $intervaldịch vụ của Angular để cập nhật các mảng, tự động khởi động một $digestvò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ó $digestchu 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.