私のアプリには次のようなサービスがある:
uaInProgressApp.factory('uaProgressService',
function(uaApiInterface, $timeout, $rootScope){
var factory = {};
factory.taskResource = uaApiInterface.taskResource()
factory.taskList = [];
factory.cron = undefined;
factory.updateTaskList = function() {
factory.taskResource.query(function(data){
factory.taskList = data;
$rootScope.$digest
console.log(factory.taskList);
});
factory.cron = $timeout(factory.updateTaskList, 5000);
}
factory.startCron = function () {
factory.cron = $timeout(factory.updateTaskList, 5000);
}
factory.stopCron = function (){
$timeout.cancel(factory.cron);
}
return factory;
});
そして、それを次のようなコントローラで使います:
uaInProgressApp.controller('ua.InProgressController',
function ($scope, $rootScope, $routeParams, uaContext, uaProgressService) {
uaContext.getSession().then(function(){
uaContext.appName.set('Testing house');
uaContext.subAppName.set('In progress');
uaProgressService.startCron();
$scope.taskList = uaProgressService.taskList;
});
}
);
基本的に、私のサービスは5秒ごとに factory.taskList
を更新し、この factory.taskList
を $scope.taskList
にリンクしています。それから、$apply
や $digest
などのメソッドを試してみましたが、factory.taskList
の変更がコントローラやビュー $scope.taskList
に反映されません。
私のテンプレートでは空のままです。これらの変更を反映させる方法をご存知ですか?
watch`を使えば問題は解決するかもしれないが、最も効率的な解決策ではない。サービスにデータを保存する方法を変更した方がよいでしょう。
この問題は、スコープが古い場所を指すまま、新しい値を代入するたびに taskList
が関連付けられているメモリロケーションを置き換えていることです。この現象はこのplunkで見ることができます。
最初にplunkをロードした時にChromeでヒープスナップショットを取ってみてください。ボタンをクリックした後、スコープが指すメモリロケーションは更新されないのに、リストは別のメモリロケーションを指しているのがわかるでしょう。
この問題は、変更される可能性のある変数を含むオブジェクト(data:{task:[]、x:[]、z:[]}
のようなもの)をサービスに保持させることで簡単に解決できます。この場合、"data"は決して変更されるべきではありませんが、必要な時はいつでも変更することができます。このdata変数をスコープに渡すと、"data"を他のものに代入して上書きしない限り、dataの中のフィールドが変更されるたびにスコープはそれを知ることができ、正しく更新されます。
このplunkでは、上で提案した修正を使って同じ例を実行しています。このような場合、ウォッチャーを使う必要はありませんし、もしビューが更新されないようなことが起きても、スコープの $apply
を実行するだけでビューを更新することができます。
こうすることで、変数の変更を頻繁に比較するようなウォッチャーは不要になりますし、多くの変数をウォッチする必要がある場合の面倒なセットアップも不要になります。この方法の唯一の問題は、ビュー(html)上で、以前は変数名だけだったところに"data."がプレフィックスとして付くことです。
Angularは(Emberや他のフレームワークとは異なり)、特別な_wrapped_オブジェクトを提供しません。また、$scope.taskList = uaProgressService.taskList
と言っても、2つの値がリンクされるわけではありません。
このような link
-ing のために、angular
は $watch
on $scope
を提供しています。これにより、uaProgressService.taskList
の値を監視し、値が変更されたときに $scope
の値を更新することができます:
$scope.$watch(function () { return uaProgressService.taskList }, function (newVal, oldVal) {
if (typeof newVal !== 'undefined') {
$scope.taskList = uaProgressService.taskList;
}
});
関数 $watch
に渡される最初の式は、$digest
ループ ごとに実行され、2 番目の引数は新旧の値で呼び出される関数です。
参考になるかわかりませんが、私がやっているのは$scope.valueに関数をバインドすることです。例えば
angular
.module("testApp", [])
.service("myDataService", function(){
this.dataContainer = {
valA : "car",
valB : "bike"
}
})
.controller("testCtrl", [
"$scope",
"myDataService",
function($scope, myDataService){
$scope.data = function(){
return myDataService.dataContainer;
};
}]);
というようにDOMにバインドします。
<li ng-repeat="(key,value) in data() "></li>
こうすることで、コード内で$watchを使う必要がなくなる。
「$ watch」などはありません。 必要です。 以下を定義するだけです。
uaInProgressApp.controller('ua.InProgressController',
function ($scope, $rootScope, $routeParams, uaContext, uaProgressService) {
uaContext.getSession().then(function(){
uaContext.appName.set('Testing house');
uaContext.subAppName.set('In progress');
uaProgressService.startCron();
});
$scope.getTaskList = function() {
return uaProgressService.taskList;
};
});
関数「getTaskList」は「$ scope」に属するため、その戻り値は「uaProgressService.taskList」のすべての変更で評価(および更新)されます。
軽量化に代わるものは、コントローラーの初期化中に、サービスに設定された通知パターンをサブスクライブすることです。
のようなもの:
app.controller('YourCtrl'['yourSvc', function(yourSvc){
yourSvc.awaitUpdate('YourCtrl',function(){
$scope.someValue = yourSvc.someValue;
});
}]);
そして、サービスには次のようなものがあります。
app.service('yourSvc', ['$http',function($http){
var self = this;
self.notificationSubscribers={};
self.awaitUpdate=function(key,callback){
self.notificationSubscribers[key]=callback;
};
self.notifySubscribers=function(){
angular.forEach(self.notificationSubscribers,
function(callback,key){
callback();
});
};
$http.get('someUrl').then(
function(response){
self.importantData=response.data;
self.notifySubscribers();
}
);
}]);
これにより、コントローラーがサービスからリフレッシュするときに、より注意深く調整できます。
Gabriel Piacentiが言ったように、変化するデータをオブジェクトにラップする場合、時計は必要ありません。
ただし、スコープ内の変更されたサービスデータを正しく更新するには、サービスデータを使用するコントローラーのスコープ値が、変化するデータ(フィールド)を直接指さないことが重要です。 代わりに、スコープ値は、変化するデータをラップするオブジェクトを指す必要があります。
次のコードは、これをより明確に説明する必要があります。 私の例では、翻訳にNLSサービスを使用しています。 NLSトークンはhttp経由で更新されています。
サービス:
app.factory('nlsService', ['$http', function($http) {
var data = {
get: {
ressources : "gdc.ressources",
maintenance : "gdc.mm.maintenance",
prewarning : "gdc.mobMaint.prewarning",
}
};
// ... asynchron change the data.get = ajaxResult.data...
return data;
}]);
コントローラーとスコープの表現。
app.controller('MenuCtrl', function($scope, nlsService)
{
$scope.NLS = nlsService;
}
);
<div ng-controller="MenuCtrl">
<span class="navPanelLiItemText">{{NLS.get.maintenance}}</span>
</div>
上記のコードは機能しますが、最初にNLSトークンに直接アクセスしたい(次のスニペットを参照)ため、値は更新されませんでした。
app.controller('MenuCtrl', function($scope, nlsService)
{
$scope.NLS = nlsService.get;
}
);
<div ng-controller="MenuCtrl">
<span class="navPanelLiItemText">{{NLS.maintenance}}</span>
</div>