私は同じようなものを探してこの質問に出くわしましたが、何が起こっているのか、いくつかの追加の解決策の完全な説明に値すると思います。
使用したような角度式がHTMLに存在する場合、Angularは自動的に$watch
forを設定し、変更がある$scope.foo
たびにHTMLを更新します$scope.foo
。
<div ng-controller="FooCtrl">
<div ng-repeat="item in foo">{{ item }}</div>
</div>
ここで述べられていない問題はaService.foo
、変更が検出されないように、2つのうちの1つが影響していることです。これらの2つの可能性は次のとおりです。
aService.foo
毎回新しい配列に設定され、その配列への参照が古くなっています。
aService.foo
更新時に$digest
サイクルがトリガーされないように更新されています。
問題1:古くなった参照
最初の可能性を考慮して、$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>
ご覧のように、おそらくアタッチされaService.foo
てaService.foo
いるng-repeatは変更しても更新されませんが、にアタッチされaService2.foo
ているng-repeat は更新されます。これは、私たちへの参照aService.foo
が古くなっているためですが、私たちへの参照は古くaService2.foo
はありません。を使用して初期配列への参照を作成しました$scope.foo = aService.foo;
。これは次の更新時にサービスによって破棄されました。つまり$scope.foo
、必要な配列を参照しなくなったということです。
ただし、初期参照を確実に維持する方法はいくつかありますが、オブジェクトまたは配列の変更が必要になる場合があります。または、サービスプロパティがString
またはなどのプリミティブを参照している場合はどうなりますか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
各$digest
で解決され、正しく更新された値が取得されます。
これはあなたがあなたの回避策でやろうとしていたことの一種ですが、はるかに少ないラウンドでです。それが変更されるたびに$watch
明示的にfoo
オンにするコントローラーに不要を追加しました$scope
。あなたは、余分な必要はありません$watch
あなたが接続した場合aService
の代わりをaService.foo
し$scope
、バインドを明示的にaService.foo
マークアップインチ
これで、$digest
サイクルが適用されていることを前提として、これで問題ありません。上記の例では、Angularの$interval
サービスを使用して配列を更新しました。これにより、$digest
更新ごとにループが自動的に開始されます。しかし、(何らかの理由で)サービス変数が「角度の世界」内で更新されない場合はどうでしょうか。言い換えれば、我々はいけない持っている$digest
サービスのプロパティを変更するたびにサイクルが自動的に起動されますか?
ここでの解決策の多くはこの問題を解決しますが、私はCode Whispererに同意します:
Angularのようなフレームワークを使用する理由は、独自のオブザーバーパターンを作成しないためです。
したがって、aService.foo
上記の2番目の例に示すように、HTMLマークアップで参照を引き続き使用し、コントローラー内に追加のコールバックを登録する必要がないようにします。
解決策2:セッターとゲッターを使用する $rootScope.$apply()
セッターとゲッターの使用をまだ誰も提案していないことに驚きました。この機能はECMAScript5で導入されたため、現在数年前から存在しています。もちろん、それは、何らかの理由で本当に古いブラウザーをサポートする必要がある場合、このメソッドは機能しないことを意味しますが、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
、非常に頻繁に更新される場合は、パフォーマンスに大きな影響を与える可能性があるため、注意が必要です。パフォーマンスが問題になる場合は、セッターを使用して、ここで他の回答と同様のオブザーバーパターンを設定できます。