更改服务数据时更新范围值

我的应用程序提供以下服务:

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;
});
}
);

所以基本上我的服务更新 factory.taskList每5秒,我链接这个 factory.taskList$scope.taskList。然后,我尝试了不同的方法,如 $apply$digest,但对 factory.taskList的变化没有反映在我的控制器和视图 $scope.taskList

它在我的模板中仍然是空的。您知道我如何传播这些更改吗?

92977 次浏览

Angular (unlike Ember and some other frameworks), does not provide special wrapped objects which semi-magically stay in sync. The objects you are manipulating are plain javascript objects and just like saying var a = b; does not link the variables a and b, saying $scope.taskList = uaProgressService.taskList does not link those two values.

For this kind of link-ing, angular provides ABC2 on $scope. You can watch the value of the uaProgressService.taskList and update the value on $scope when it changes:

$scope.$watch(function () { return uaProgressService.taskList }, function (newVal, oldVal) {
if (typeof newVal !== 'undefined') {
$scope.taskList = uaProgressService.taskList;
}
});

The first expression passed to the $watch function is executed on every $digest loop and the second argument is the function which is invoked with the new and the old value.

While using $watch may solve the problem, it is not the most efficient solution. You might want to change the way you are storing the data in the service.

The problem is that you are replacing the memory location that your taskList is associated to every time you assign it a new value while the scope is stuck pointing to the old location. You can see this happening in this plunk.

Take a heap snapshots with Chrome when you first load the plunk and, after you click the button, you will see that the memory location the scope points to is never updated while the list points to a different memory location.

You can easily fix this by having your service hold an object that contains the variable that may change (something like data:{task:[], x:[], z:[]}). In this case "data" should never be changed but any of its members may be changed whenever you need to. You then pass this data variable to the scope and, as long as you don't override it by trying to assign "data" to something else, whenever a field inside data changes the scope will know about it and will update correctly.

This plunk shows the same example running using the fix suggested above. No need to use any watchers in this situation and if it ever happens that something is not updated on the view you know that all you need to do is run a scope $apply to update the view.

This way you eliminate the need for watchers that frequently compare variables for changes and the ugly setup involved in cases when you need to watch many variables. The only issue with this approach is that on your view (html) you will have "data." prefixing everything where you used to just have the variable name.

I'm not sure if thats help but what I am doing is bind the function to $scope.value. For example

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;
};
}]);

Then I just bind it in DOM as

<li ng-repeat="(key,value) in data() "></li>

This way you can avoid to using $watch in your code.

Lightweight alternative is that during controller initialization you subscribe to a notifier pattern set up in the service.

Something like:

app.controller('YourCtrl'['yourSvc', function(yourSvc){
yourSvc.awaitUpdate('YourCtrl',function(){
$scope.someValue = yourSvc.someValue;
});
}]);

And the service has something like:

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();
}
);
}]);

This can let you fine tune more carefully when your controllers refresh from a service.

No $watch or etc. is required. You can simply define the following

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;
};
});

Because the function getTaskList belongs to $scope its return value will be evaluated (and updated) on every change of uaProgressService.taskList

Like Gabriel Piacenti said, no watches are needed if you wrap the changing data into an object.

BUT for updating the changed service data in the scope correctly, it is important that the scope value of the controller that uses the service data does not point directly to the changing data (field). Instead the scope value must point to the object that wraps the changing data.

The following code should explain this more clear. In my example i use an NLS Service for translating. The NLS Tokens are getting updated via http.

The Service:

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;
}]);

Controller and scope expression

app.controller('MenuCtrl', function($scope, nlsService)
{
$scope.NLS = nlsService;
}
);


<div ng-controller="MenuCtrl">
<span class="navPanelLiItemText">\{\{NLS.get.maintenance}}</span>
</div>

The above code works, but first i wanted to access my NLS Tokens directly (see the following snippet) and here the values did not become updated.

app.controller('MenuCtrl', function($scope, nlsService)
{
$scope.NLS = nlsService.get;
}
);


<div ng-controller="MenuCtrl">
<span class="navPanelLiItemText">\{\{NLS.maintenance}}</span>
</div>