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中。

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

4

6 回答 6

78

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

taskList问题是每次您为其分配一个新值时,您都在替换与您关联的内存位置,而示波器卡在指向旧位置。你可以在这个 plunk中看到这种情况。

首次加载 plunk 时使用 Chrome 拍摄堆快照,单击按钮后,您将看到范围指向的内存位置永远不会更新,而列表指向不同的内存位置。

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

这个 plunk显示了使用上面建议的修复程序运行的相同示例。在这种情况下不需要使用任何观察者,如果发生视图上没有更新的事情,你知道你需要做的就是运行一个范围$apply来更新视图。

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

于 2014-10-02T19:41:19.107 回答
71

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

对于这种link-ing,angular提供$watchon$scope。您可以查看 的值uaProgressService.taskList并在其更改时更新该值$scope

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

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

于 2013-11-02T17:17:27.030 回答
45

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

于 2014-12-16T09:47:15.467 回答
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

于 2016-01-05T14:51:34.237 回答
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();
        }
    );
}]);

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

于 2015-10-09T16:29:51.157 回答
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>
于 2017-01-23T13:35:47.010 回答