我偶然发现了这个问题,正在寻找类似的东西,但我认为它值得对正在发生的事情进行彻底的解释,以及一些额外的解决方案。
当一个 Angular 表达式(例如您使用的那个)出现在 HTML 中时,Angular 会自动设置一个$watch
for $scope.foo
,并在发生更改时更新 HTML $scope.foo
。
<div ng-controller="FooCtrl">
<div ng-repeat="item in foo">{{ item }}</div>
</div>
这里未说明的问题是,有两件事之一正在影响aService.foo
,因此未检测到更改。这两种可能性是:
aService.foo
每次都设置为一个新数组,导致对它的引用已过时。
aService.foo
正在以更新时$digest
不会触发循环的方式进行更新。
问题 1:过时的引用
考虑第一种可能性,假设$digest
正在应用 a,如果aService.foo
始终是同一个数组,则自动设置$watch
将检测到更改,如下面的代码片段所示。
解决方案 1-a:确保数组或对象在每次更新时都是同一个对象
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>
如您所见,据说附加到的 ng-repeat在更改aService.foo
时不会更新aService.foo
,但附加到的 ng-repeataService2.foo
会更新。这是因为我们的引用aService.foo
已经过时,但我们的引用aService2.foo
没有。我们创建了对初始数组的引用,$scope.foo = aService.foo;
然后在下一次更新时被服务丢弃,这意味着$scope.foo
不再引用我们想要的数组。
然而,虽然有多种方法可以确保初始引用保持完整,但有时可能需要更改对象或数组。或者,如果服务属性引用像 aString
或之类的原语Number
怎么办?在这些情况下,我们不能简单地依赖参考。那么我们能做些什么呢?
之前给出的几个答案已经为该问题提供了一些解决方案。不过,我个人更赞成在评论中使用Jin和thetallweeks建议的简单方法:
只需在 html 标记中引用 aService.foo
解决方案 1-b:将服务附加到范围,并{service}.{property}
在 HTML 中引用。
意思是,只需这样做:
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>
这样,将在 each 上$watch
解析,这将获得正确更新的值。aService.foo
$digest
这就是你试图用你的解决方法做的事情,但方式要少得多。$watch
您在控制器中添加了一个不必要的,它foo
在$scope
更改时显式放置。$watch
当您附加aService
而不是附加aService.foo
到, 并在标记中$scope
显式绑定到时,您不需要额外的东西。aService.foo
现在假设$digest
正在应用一个循环,这一切都很好。在上面的示例中,我使用 Angular 的$interval
服务来更新数组,每次更新后它都会自动启动一个$digest
循环。但是,如果服务变量(无论出于何种原因)没有在“Angular 世界”中得到更新怎么办。换句话说,当服务属性发生变化时,我们不会自动激活一个循环吗?$digest
这里的许多解决方案都可以解决这个问题,但我同意Code Whisperer:
我们使用像 Angular 这样的框架的原因是为了不编写我们自己的观察者模式
因此,我更愿意继续使用aService.foo
HTML 标记中的引用,如上面第二个示例所示,而不必在 Controller 中注册额外的回调。
解决方案 2:使用 setter 和 getter$rootScope.$apply()
我很惊讶没有人建议使用setter和getter。此功能是在 ECMAScript5 中引入的,因此已经存在多年。当然,这意味着无论出于何种原因,如果您需要支持非常旧的浏览器,那么这种方法将不起作用,但我觉得 getter 和 setter 在 JavaScript 中的使用严重不足。在这种特殊情况下,它们可能非常有用:
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>
在这里,我在服务函数中添加了一个“私有”变量:realFoo
. get foo()
分别使用对象上的和set foo()
函数更新和检索此 get service
。
注意$rootScope.$apply()
set 函数中的使用。这确保了 Angular 将知道对service.foo
. 如果您收到“inprog”错误,请参阅这个有用的参考页面,或者如果您使用 Angular >= 1.3,您可以只使用$rootScope.$applyAsync()
.
aService.foo
如果更新非常频繁,也要注意这一点,因为这可能会显着影响性能。如果性能是一个问题,您可以使用 setter 设置类似于此处其他答案的观察者模式。