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

时间:2013-11-02 17:01:27

标签: angularjs

我的应用程序中有以下服务:

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

我的模板中仍然为空。你知道我如何传播这些变化吗?

6 个答案:

答案 0 :(得分:78)

虽然使用$watch可以解决问题,但它不是最有效的解决方案。您可能希望更改在服务中存储数据的方式。

问题是,当范围被卡住指向旧位置时,每次为其分配新值时,您将替换与taskList关联的内存位置。您可以在this plunk中看到这种情况。

首次加载插件时,使用Chrome创建堆快照,然后在单击按钮后,您会看到当列表指向不同的内存位置时,范围指向的内存位置永远不会更新。

您可以通过让服务保存包含可能更改的变量的对象(类似data:{task:[], x:[], z:[]})来轻松解决此问题。在这种情况下"数据"永远不应该改变,但任何成员都可以随时更改。然后,您将此数据变量传递到范围,并且只要您不通过尝试分配"数据"来覆盖它。对于其他内容,只要数据中的字段发生变化,范围就会知道并且会正确更新。

This plunk显示使用上面建议的修补程序运行的相同示例。在这种情况下无需使用任何观察者,如果在视图中没有更新某些内容,您知道所有需要做的就是运行范围$apply来更新视图。

通过这种方式,您无需经常比较变量变量的观察者以及需要观察许多变量时所涉及的丑陋设置。这种方法的唯一问题是,在您的视图(html)上,您将拥有"数据。"为您曾经拥有变量名称的所有内容添加前缀。

答案 1 :(得分:71)

Angular(与Ember和其他一些框架不同),不提供半魔法保持同步的特殊包装对象。您正在操作的对象是普通的javascript对象,就像说var a = b;没有链接变量ab一样,说$scope.taskList = uaProgressService.taskList没有将这两个值联系起来。

对于这种linkangular提供$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 loop上执行,第二个参数是用新值和旧值调用的函数。

答案 2 :(得分:44)

我不确定这是否有帮助,但我正在做的是将函数绑定到$ 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。

答案 3 :(得分:5)

不需要$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的每次更改都会评估(并更新)其返回值

答案 4 :(得分:3)

轻量级替代方案是在控制器初始化期间,您订阅在服务中设置的通知程序模式。

类似的东西:

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

当您的控制器从服务中刷新时,这可以让您更仔细地进行微调。

答案 5 :(得分:2)

像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>