我在寻找类似的东西时偶然发现了这个问题,但我认为它值得对正在发生的事情进行彻底的解释,以及一些额外的解决方案。
当你使用的angular表达式出现在HTML中时,angular会自动为$scope设置一个$watch。每当$scope. foo时,它都会更新HTML。foo的变化。
<div ng-controller="FooCtrl">
<div ng-repeat="item in foo">{{ item }}</div>
</div>
这里没有提到的问题是,有两件事正在影响aService。Foo使更改不会被检测到。这两种可能性是:
项服务。Foo每次都被设置为一个新的数组,导致对它的引用过时。
项服务。Foo的更新方式是在更新时不会触发$digest循环。
问题1:过时的引用
考虑第一种可能性,假设应用了$摘要,如果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。当aService. foo不更新。但是ng-repeat附加到aService2. foo上。foo。这是因为我们引用了aService。foo已经过时,但是我们对aService2. foo的引用。Foo不是。我们用$scope创建了对初始数组的引用。foo = aService.foo;,然后被服务在下一次更新时丢弃,这意味着$scope。Foo不再引用我们想要的数组。
然而,虽然有几种方法可以确保初始引用保持适当,但有时可能需要更改对象或数组。或者,如果服务属性引用字符串或数字之类的原语,该怎么办?在这种情况下,我们不能仅仅依靠参考资料。那么我们能做什么呢?
前面给出的几个答案已经给出了这个问题的一些解决方案。然而,我个人更倾向于使用Jin和评论中的tallweeks所建议的简单方法:
引用aService即可。Foo在HTML标记中
解决方案1-b:将服务附加到作用域,并引用{service}。HTML中的{property}。
意思是,只要这样做:
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>
这样,$watch将解析aService。Foo在每个$digest上,它将得到正确更新的值。
这就是你想要的变通方法,但是不那么迂回。你在控制器中添加了一个不必要的$watch,它会在foo发生变化时显式地将它放在$scope上。当你附加aService而不是aService时,你不需要额外的$watch。foo到$scope,并显式绑定到aService。Foo的标记。
现在,假设应用了$digest周期,这一切都很好。在上面的例子中,我使用了Angular的$interval服务来更新数组,它会在每次更新后自动启动一个$digest循环。但是,如果服务变量(不管出于什么原因)在“Angular世界”中没有得到更新呢?换句话说,我们没有一个$digest周期被自动激活每当服务属性改变?
问题2:缺少$摘要
这里的许多解决方案都可以解决这个问题,但我同意Code Whisperer的观点:
我们使用Angular这样的框架的原因是不需要自己编造观察者模式
因此,我更愿意继续使用aService。在HTML标记中引用foo,如上面的第二个例子所示,并且不必在Controller中注册一个额外的回调。
解决方案2:在$rootScope中使用setter和getter。
我很惊讶还没有人建议使用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分别使用get foo()和set foo()函数在服务对象上更新和检索。
注意set函数中使用了$rootScope.$apply()。这确保了Angular能够感知service.foo的任何变化。如果你得到'inprog'错误,请参阅这个有用的参考页面,或者如果你使用Angular >= 1.3,你可以使用$rootScope.$applyAsync()。
同样,如果aService。Foo的更新非常频繁,因为这会严重影响性能。如果性能是一个问题,您可以使用setter设置一个类似于这里其他答案的观察者模式。