【问题标题】:Databinding not updating when called from $on从 $on 调用时数据绑定不更新
【发布时间】:2014-05-31 09:19:52
【问题描述】:

我有一个奇怪的错误,我无法弄清楚。我最近将我的 Angular 应用程序重构为更“有角度的”。其中一部分是将窗口调整大小事件处理程序从指令中移出,并将其放入 .run 块中,它可以将 Angular 事件广播到整个应用程序。

那部分似乎工作正常。它监视窗口和事件正确传播。但是,当我从 $on 监听器调用我的模型操作代码时,数据绑定似乎没有触发。

.controller('channelListController', function ($scope, $rootScope, $http, $filter){
  $http.get('api/v1/channels').success(function(data){
    $scope.baseChannels = data;

    filterChannelData($scope.filter);
    //call manually since initial window resize occurs before async return
    splitChannelData($rootScope.windowAttr.columns);
  });

  //*************
  //this part doesn't trigger databinding update
  //*************
  $scope.$on('columnChange', function(){
    //window resize requires changing column number
    console.log('change detected');
    splitChannelData($rootScope.windowAttr.columns);
  });

  //*************
  //but this does
  //*************
  $scope.$watch('filter', function(newVal){
  //the filters are changed
    filterChannelData(newVal);
    //re-run split for new data
    splitChannelData($rootScope.windowAttr.columns);
  }, true);

  function filterChannelData(filter){
    if(filter){
      $scope.filteredChannels = $filter('looseCreatorComparator')($scope.baseChannels, filter.creators);
      $scope.filteredChannels = $filter('looseTagComparator')($scope.filteredChannels, filter.tags);
    } else {
      $scope.filteredChannels = $scope.baseChannels;
    }
  }

  function splitChannelData(columns){
    if(columns){
      $scope.splitChannels = [];
      for(var rep=0;rep<columns;rep++){
        $scope.splitChannels.push([]);
      }
      _.forEach($scope.filteredChannels, function(channel, index){
        $scope.splitChannels[index % columns].push(channel);
      });
    }
  }
});

因此,当我更改过滤器输入字段的内容时,$watch 会捕获它,运行我的过滤器,然后触发 splitChannelData。后者将我的数据集重构为一组单独的数组以供显示。该视图数据绑定到 $scope.splitChannels。这很好用。

但是,当广播 'columnChange' 事件被 $on 捕获时,splitChannelData 函数会触发,正确重构 $scope.splitChannels 但视图不会刷新以反映更改。

此外,如果我随后对过滤器输入字段进行另一项更改并触发 $watch 处理程序,它会正确更新视图,包括 $on 所做但未更新到早点查看。

WUT。

那么,谁能告诉我为什么从 $scope.$watch 调用 splitDataChannel 可以使数据绑定正常工作,但由 $scope.$on 触发的同一个调用却不能?

编辑: 我刚刚解决了这个错误,但仍然不明白为什么这个解决方案是必要的。我仍然希望得到反馈。

我向 $on 处理程序添加了 $apply 调用:

  $scope.$on('columnChange', function(){
    //window resize requires changing column number
    console.log('change detected');
    splitChannelData($rootScope.windowAttr.columns);
    //added this
    $scope.$apply();
  });

为什么 $watch 会触发数据绑定更新,而 $on 不会,而拿铁咖啡需要手动调用 $apply?

【问题讨论】:

  • $scope.$apply(); 是必要的,因为该事件来自 Angular“世界”之外,因此您需要让应用知道该事件发生了。
  • 好的,我知道“外部”事件需要对其调用 $apply 但我很困惑为什么在我的控制器内部调用方法并操作 $scope 不会“强制”代码在 Angular 世界中执行。究竟是什么定义了那个世界的边界?

标签: javascript angularjs data-binding


【解决方案1】:

一般解释:

Angular 世界的边界”并不像听起来那么复杂。

有点简化,Angular 的脏检查通过将观察者存储在数组中来工作。观察者有一个 watchExpression 和一个 listenerFunction。当摘要循环被触发时,观察者被迭代。如果 watchExpression 的返回值自上次以来发生了变化,则执行关联的 listenerFunction。

例如,watchExpression 返回字符串hello,而上次它返回hell。执行 listenerFunction 以使用返回值 hello 更新特定 DOM 元素的文本值。

如果您更改了一个被观察但未触发摘要循环的变量,则更新将不会反映在 UI 中。

如果操作触发了摘要循环 - 你是边界内。

如果操作没有触发摘要循环 - 您超出边界。

Angular 中的大多数常用功能会在内部自动为您触发摘要循环。例如指令ngClick$http 服务。

具体说明:

$broadcast$emit$on 不会触发摘要循环。问题不在于附加到$on 的处理程序,而很可能与使用$broadcast 的位置有关。

我的猜测是它看起来像这样:

angular.element($window).on('resize', function () {
  $rootScope.$broadcast('columnChange');
});

on 函数是一个 jqLit​​e/jQuery 函数。它不会触发摘要循环,这意味着它存在于 Angular 世界的边界之外。

您应该将其封装在对 $apply 的调用中:

angular.element($window).on('resize', function() {
  $rootScope.$apply(function () { 
    $rootScope.$broadcast('columnChange');
  });
});

不在附加到$on 的处理程序中调用$apply 的原因是,如果某些东西同时调用$broadcast('columnChange') 并触发摘要循环,您将收到$digest already in progress 错误。

【讨论】:

  • 啊,这很有帮助。为了确保我正确理解它:脏检查不是由数据绑定变量的实际状态更改触发的,而是由某些事件(例如 UI 控件交互或 $watch )触发,理论上可以发出状态更改的信号。因此,由于 Angular 没有监视 $on,因此数据绑定变量的下游变化是无关紧要的,除非通过 $apply 手动强制,否则 Angular 不知道开始摘要循环。这是对正在发生的事情的恰当描述吗?
  • 函数$watch本身不会触发摘要循环,它只是将watchExpression/listenerFunction添加到循环期间迭代的数组中。 $broadcast/$on 的源代码中没有任何内容会触发摘要循环,这就是为什么如果您在不触发循环本身的上下文中使用它,则需要手动执行此操作。
  • jqLit​​e/jQuery 的 on 方法不会触发它。虽然$broadcast 将工作并运行代码,但由于摘要循环之后不会运行,因此数据绑定将不同步,并且更改不会在下一个循环之前反映在 UI 中。
  • 我猜你在输入上使用ng-model="filter"ngModel 指令将绑定到输入字段上的各种事件,并且每次它的值更改时都会触发摘要以保持数据绑定同步。随着摘要运行,所有观察者都将被执行,这就是您的过滤器观察中的代码正常工作的原因。
  • 这是一个很棒的解释,我希望我读过的书有一半那么透彻。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-07-20
  • 1970-01-01
  • 1970-01-01
  • 2014-11-17
  • 2023-03-23
  • 1970-01-01
相关资源
最近更新 更多