过滤传递给指令的数据抛出异常

时间:2013-10-26 15:32:32

标签: javascript angularjs angularjs-directive angularjs-filter

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

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. 有哪些替代方法可以满足这些要求?
  3. 请参阅full Plunker example

2 个答案:

答案 0 :(得分:1)

目前的代码中存在相当多的竞争条件(初始化顺序)和其他细微之处。

首先,要解决你所描述的错误。

解决之前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 []; 
    }
  }
});

使用角度people | filter:{'selected' : true} which handles promises (and everything else) gracefully可以更轻松地将此过滤器编写为filter。我假设您有使用自定义过滤器的原因。

其他问题

首先初始化指令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'
  }
});

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

关于混合过滤器和承诺

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

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

作为一项规则,我发现最好让controller执行过滤任务,并将任何其他逻辑放在用户界面modelview之间。因此,我将在控制器中的$scope上定义一个属性,该属性在promise解析后设置,然后在模板中使用。这确实使控制器更厚一些。

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

答案 1 :(得分:1)

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

一个问题是摘要循环将在promise中提供数据之前运行该指令。承诺似乎没有传递给指令。这意味着在首次运行过滤器时未定义people in指令的范围

修复过滤器:

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

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

Here's a version of your plunker that doesn't throw errors