筛选传入指令的数据会引发异常

Filter on data passed into directive throws exception

本文关键字:异常 数据 指令 筛选      更新时间:2023-09-26

我正在使用一个指令来模板化对象列表。根据指令的使用位置,应筛选模板中呈现的对象列表。在一种方法中,代码如下所示:

person_list.html

<ul>
  <li ng-repeat="person in (people | selected:true)">
  <a class="selected-{{ person.selected }}" ng-click="toggleSelect( person )">{{ person.name }}</a>
  </li>
</ul>

person_list.js

app.directive('personList', function(){
  return {
    restrict: 'E',
    scope: {people: '=list'},
    controller: "ListCtrl",
    templateUrl: 'person_list.html'
  }
});

selected_filter.js

app.filter('selected', function(){
  return function(list, criteria){
    return list.filter(function(element){
      return !!element.selected === criteria;
    });
  }
});

该指令的使用方式如下:

<person-list list="people"></person-list>

我想使用的另一种方法是从指令外部过滤列表:

person_list.html

<ul>
  <li ng-repeat="person in people">
  <a class="selected-{{ person.selected }}" ng-click="toggleSelect( person )">{{ person.name }}</a>
  </li>
</ul>

该指令将按如下方式使用:

<person-list list="people | selected:true"></person-list>

但是,Angular 不喜欢这样。异常Cannot call method 'filter' of undefined在筛选器内引发。目标是使指令尽可能简单,部分原因是将此筛选器作为可选组件。

我想知道的是:

  1. 为什么第一种方法有效,而第二种方法无效?
  2. 哪些替代方法可以满足这些要求?

请参阅完整的 Plunker 示例。

目前的代码中有很多竞争条件(初始化顺序)和其他微妙之处。

首先,解决您描述的错误。

解决前promise的初始值是多少

首次评估people | selected:true时,people是一个未解决的承诺。因此,angular调用值为 undefined 的过滤器。这是正确的 (TM) 行为,因为会有过滤器想要捕获该undefined,然后在后台解析该值时显示一些默认值。这种情况需要在过滤器中处理:

app.filter('selected', function(){
  return function(list, criteria){
    if (typeof list !== 'undefined') {
      return list.filter(function(element){
        return !!element.selected === criteria;
      });
    } else { 
      return []; 
    }
  }
});

使用 angular 的filter可以更轻松地将此过滤器编写为people | filter:{'selected' : true},该优雅地处理承诺(以及其他所有内容)。我假设您有使用自定义过滤器的理由。

其他问题

指令scope是先初始化还是控制器的scope

属性peopleListCtrldirective的独立作用域上定义:

app.directive('personList', function(){
  return {
    restrict: 'E',
    scope: {people: '=list'},  // <-- 'people' on scope
    controller: "ListCtrl",    // <-- Also defines 'people' on scope
    templateUrl: 'person_list.html'
  }
});

目前尚不清楚哪些people实际上会延续到模板中。我怀疑您需要控制器的toggleSelection函数,但不需要people字段的初始化。因此,您需要此类内容:

app.directive('personList', function(){
  return {
    restrict: 'E',
    scope: {people: '=list'},
    controller: ['$scope', function ($scope) { 
        $scope.toggleSelect = function (p) { 
            p.selected = !p.selected; 
        };
    }],
    templateUrl: 'person_list.html'
  }
});

根据您希望如何与应用程序的其余部分进行通信,您可以在link函数或controller中定义toggleSelect函数(如此处所示)。

关于混合过滤器和承诺

通过这些更改,由于指令上的=list绑定和返回新列表对象的people | selected:true,您也会遇到Error: 10 $digest() iterations reached. Aborting!。我怀疑问题就在这里,因为只使用了对象相等,而没有使用angular.equals。但是,我不是那里的专家。

我不喜欢在原语以外的任何东西上使用filters,因为很难确保对象相等并防止此类错误。此外,由于这些问题,我不太喜欢直接在 UI 中使用promises

通常,我发现最好让controller执行过滤任务以及位于UI modelview之间的任何其他逻辑。因此,我会在控制器中的$scope上定义一个属性,该属性是在解决promise后设置的,然后在模板中使用它。这确实使控制器更厚一些。

工作示例:http://plnkr.co/edit/8eMvGUWsxQwe4fhypBf1?p=preview

您的设置中存在许多问题。建议您在指令中使用完全不同的控制器引用,因为您将数据从ListCtrl传递到指令,然后在指令中调用相同的控制器并再次检索相同的数据

一个问题是摘要循环将在数据从承诺中可用之前运行指令。承诺似乎不会传递到指令中。这意味着首次运行过滤器时未定义指令中的people范围

要修复过滤器,请执行以下操作:

app.filter('selected', function(){
  return function(list, criteria){
     if(!angular.isUndefined(list)){
        return list.filter(function(element){
          return !!element.selected === criteria;
        });
     }
  }
});

不要尝试筛选指令的属性。 ng-repeat允许这样做,因为它的指令需要过滤器。将过滤器放在模板中的 ng-repeat 上,或过滤指令本身中的数据。

这是您的 plunker 的一个版本,不会抛出错误