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

Angularjs 更改服务数据时更新范围值,angularjs,Angularjs,我的应用程序中有以下服务: uaInProgressApp.factory('uaProgressService', function(uaApiInterface, $timeout, $rootScope){ var factory = {}; factory.taskResource = uaApiInterface.taskResource() factory.taskList = []; factory.cron

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

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秒更新一次
工厂.taskList
,我将此
工厂.taskList
链接到
$scope.taskList
。然后我尝试了不同的方法,如
$apply
$digest
,但是
工厂上的更改。任务列表
不会反映在我的控制器和视图中
$scope.taskList

它在我的模板中保持为空。您知道如何传播这些更改吗?

Angular(与Ember和其他一些框架不同)不提供特殊的包装对象,它们半神奇地保持同步。您正在操作的对象是普通的javascript对象,就像说
var a=b
不链接变量
a
b
,表示
$scope.taskList=uaProgressService。taskList
不链接这两个值

对于这种
链接
-ing,
角度
提供。您可以查看
uaProgressService.taskList
的值,并在其更改时更新
$scope
上的值:

$scope.$watch(函数(){return uaProgressService.taskList},函数(newVal,oldVal){
if(typeof newVal!=“未定义”){
$scope.taskList=uaProgressService.taskList;
}
});

传递给
$watch
函数的第一个表达式在每个参数上执行,第二个参数是使用新值和旧值调用的函数。

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

问题在于,每次为任务列表指定新值时,都会替换与其关联的
内存位置,而作用域一直指向旧位置。你可以看到这种情况正在发生

当您第一次加载plunk时,使用Chrome创建一个堆快照,单击按钮后,您将看到作用域指向的内存位置从未更新,而列表指向不同的内存位置

您可以通过让您的服务持有一个包含可能更改的变量(类似于
数据:{task:[],x:[],z:[]}
)的对象来轻松解决此问题。在这种情况下,不应更改“数据”,但其任何成员都可以在需要时更改。然后将此数据变量传递给作用域,只要不通过尝试将“数据”分配给其他对象来覆盖它,只要数据中的字段发生更改,作用域就会知道并正确更新

显示了使用上面建议的修复程序运行的相同示例。在这种情况下不需要使用任何监视程序,如果发生视图上没有更新某些内容的情况,您知道您需要做的就是运行scope
$apply
来更新视图


这样,您就不需要经常比较变量变化的观察者,也不需要在需要观察许多变量的情况下进行丑陋的设置。这种方法的唯一问题是,在视图(html)上,您将有“数据”。在以前只有变量名的地方加上前缀。

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

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

    比如:

    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();
            }
        );
    }]);
    
    该服务有如下功能:

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

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

    不需要
    $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
    如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>
    
    app.controller('MenuCtrl',函数($scope,nls服务)
    {
    $scope.NLS=nlsService;
    }
    );
    {{NLS.get.maintenance}
    
    上面的代码可以工作,但首先我想直接访问我的NLS令牌(请参见下面的代码片段),而这里的值没有更新

    app.controller('MenuCtrl', function($scope, nlsService)
      {
        $scope.NLS = nlsService.get;
      }
    );
    
    <div ng-controller="MenuCtrl">
      <span class="navPanelLiItemText">{{NLS.maintenance}}</span>
    </div>
    
    app.controller('MenuCtrl',函数($scope,nls服务)
    {
    $scope.NLS=nlsService.get;
    }
    );
    {{NLS.maintenance}
    
    好的,我明白。我尝试了你的代码片段,现在我的应用程序按预期运行:)很多手表会影响应用程序的性能吗?尽管有时,它对大多数应用程序都相当有效。我失明了这么久,谢谢。我认为,因为它是一个独生子,所以属性会以一种神奇的方式传播:SI花了我好几天的时间才找到这个答案。谢谢我很惊讶这个用例没有更好的文档化,因为这是我使用服务(rpc/pubsub)的唯一原因。$watch在这种情况下是一种攻击。其他两个答案描述了正确的技巧,特别是@Gabriel Piacenti返回服务时“新”生命+iLife的原因是什么?在这种情况下,它将允许您返回一个类,删除新的将破坏代码。我倾向于选择这个结构而不是对象结构