我偶然发现了这个问题,寻找了类似的东西,但是我认为它应该对正在发生的事情以及一些其他解决方案进行透彻的解释。
当HTML中出现一个角度表达式(例如您使用的表达式)时,Angular会自动设置$watch
for $scope.foo
,并将在$scope.foo
更改时更新HTML 。
<div ng-controller="FooCtrl">
<div ng-repeat="item in foo">{{ item }}</div>
</div>
这里未说的问题是,有两件事之一正在影响aService.foo
,使得未检测到更改。这两种可能性是:
aService.foo
每次都将其设置为一个新数组,从而导致对该数组的引用已过时。
aService.foo
正在以$digest
不触发更新周期的方式进行更新。
问题1:过时的引用
考虑到第一种可能性,假设应用了a $digest
,如果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-重复理应连接到aService.foo
时不更新aService.foo
变化,但附加了NG-重复aService2.foo
做。这是因为我们的引用aService.foo
已过时,但我们的引用aService2.foo
却未过。我们使用创建了对初始数组的引用,该引用$scope.foo = aService.foo;
随后在下一次更新时被服务丢弃,这意味着$scope.foo
不再引用我们想要的数组。
但是,尽管有多种方法可以确保初始引用保持完整,但有时可能需要更改对象或数组。或者,如果服务属性引用了诸如a String
或a之类的原语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>
这样,$watch
将aService.foo
在each上解析$digest
,这将获取正确的更新值。
这是您尝试使用的解决方法,但是回合方式要少得多。您$watch
在控制器中添加了不必要的内容,该内容foo
在$scope
更改时会显式地添加。$watch
当您附加aService
而不是附加aService.foo
到$scope
,并aService.foo
在标记中显式绑定时,则不需要额外的附加内容。
现在,假设$digest
正在应用一个循环,这一切都很好。在上面的示例中,我使用了Angular的$interval
服务来更新数组,该数组$digest
在每次更新后自动启动循环。但是,如果服务变量(无论出于何种原因)没有在“ Angular world”中更新,该怎么办。换句话说,我们不拥有$digest
每当服务属性的变化能够自动启动周期?
这里的许多解决方案都可以解决此问题,但是我同意Code Whisperer的观点:
我们之所以使用Angular之类的框架,是为了不构成我们自己的观察者模式
因此,我希望继续aService.foo
在HTML标记中使用引用,如上面的第二个示例所示,而不必在Controller中注册其他回调。
解决方案2:将setter和getter与 $rootScope.$apply()
令我惊讶的是,还没有人建议使用二传手和吸气剂。此功能是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()
函数在service
对象上更新和获取此获取。
注意$rootScope.$apply()
在set函数中的使用。这样可以确保Angular知道对的任何更改service.foo
。如果出现“ inprog”错误,请参见此有用的参考页,或者如果使用Angular> = 1.3,则可以使用$rootScope.$applyAsync()
。
如果aService.foo
要经常更新,也要小心,因为这可能会严重影响性能。如果性能是一个问题,则可以使用设置器来设置与其他答案类似的观察者模式。