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


76

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

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

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

Answers:


79

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

问题在于,taskList每次在您的作用域停留指向旧位置时为它分配一个新值时,都将替换与之关联的内存位置。您可以在这种情况下看到这种情况。

首次加载插件时,使用Chrome进行堆快照,单击按钮后,您将看到示波器指向的内存位置从未更新,而列表指向了另一个内存位置。

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

此插件显示了使用上述建议的修复程序运行的相同示例。在这种情况下,无需使用任何观察程序,并且一旦发生未在视图上进行某些更新的情况,您就知道您需要做的就是运行一个范围$apply来更新视图。

这样,您便无需观察者频繁地比较变量的更改和在您需要观察许多变量的情况下涉及的丑陋设置。这种方法的唯一问题是,您在视图(html)上将拥有“数据”。在所有以前只有变量名的地方加上前缀。


1
返回服务中“新” +“错误”的原因是什么?
lloop

1
在这种情况下,它将允许您返回一个类,删除新类将破坏代码。我倾向于这种结构而不是对象结构(例如“ {attr:value,attr2:value2 ...}”),因为我发现它更灵活。 这里是它的plunker,并且 这里是从角团队关于它的一点信息。如您所见,您可以为此使用服务而不是工厂,但是任何一种都可以正常工作。
Piacenti 2014年

4
嘿@GabrielPiacenti-您在这里有一个很好的答案,我认为如果您整理一下内容/设置格式会有所帮助...使其更具可读性...
sfletche 2015年

加布里埃尔(Gabriel),您应该删除大部分代码,而答案只针对需要的内容。您是正确的,但是此代码看起来过于复杂。请看下面的两个答案,它更干净(但是它们缺少您有关内存堆的信息)。两种方法都做得不错!
Mark Pieszak-Trilon.io

我知道这是一个很老的答案,但是我在我的应用程序中已经做了很多工作,而且它就像一个梦。在Angular 1.5中,删除了双向数据绑定(大多数情况下),那么,这种技术是否会陷入与消除双向数据绑定相同的性能陷阱?
泰勒(Tyler)

71

Angular(与Ember和其他框架不同)不提供特殊包装的对象,这些对象半魔术地保持同步。你操纵的对象是普通的JavaScript对象和就像说var a = b;没有链接的变量a,并b$scope.taskList = uaProgressService.taskList不会链接这两个值。

对于这种link-ing,angular提供$watch$scope。您可以观察的值,uaProgressService.taskList$scope在更改时更新值:

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

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


1
好吧,我明白了。我尝试了您的代码段,现在我的应用程序可以按预期运行:)大量手表会影响应用程序性能吗?
Alex Grs

1
尽管有时人们不得不采取极端措施对其进行优化,但它对于大多数应用程序都相当有效。
musicly_ut

1
我失明了这么长时间,谢谢。我认为,因为这是一个单例,所以属性会以一种神奇的方式传播:S
Dvid Silva 2014年

4
我花了几天的时间才找到这个答案。谢谢。我很惊讶这个用例没有得到更好的记录,因为这是我使用服务(rpc / pubsub)的唯一原因。
塞尼卡·冈萨雷斯

5
在这种情况下,$ watch是一个黑客。其他两个答案描述了正确的技术,特别是@Gabriel Piacenti
Bernard

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。


4
最佳答案忽略$ watch的答案,这是应该做的。上面的方法也很正确,但是非常令人困惑。
Mark Pieszak-Trilon.io

1
抱歉,给出一个旧的答案,但是该解决方案对我来说非常有效,我想知道是否有任何需要我注意的缺点?我尝试了各种方法来链接作用域和服务,但是没有一个解决方案能像这样优雅。
CT14.IT

4
抱歉迟了回应。如果您的数据功能非常复杂,则不应使用此方法。每次$ digest运行时,该方法都会被调用。
爱德华·杰科

要解决此问题,请使用工厂,它会返回值,并在数据更改时进行更新。
Tiago Sousa

我希望我不仅要感谢,还可以谢谢你!找不到任何解决方案,即使出于某些奇怪的原因,手表也无法为我工作,但这确实可以完成!谢谢!
KissKoppány16年

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


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

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


作为说明,此后我找到了一个更有效的解决方案。将嵌套路由与Angular UI路由器一起使用,并将所有长期存在的事件通知程序放在根控制器中。
Bon:

1
我希望能够在任何地方放置数据列表,并从您的答案中得到启发,从而编写了一条基于您的订阅原则来利用服务的指令。我认为at指令比嵌套路由要简单得多。但是我想这取决于...无论如何,谢谢您提供了非常漂亮和有用的代码:-)
Jette

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>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.