如何过滤 angular 模型(数组)而不破坏它

How to filter angular model (array) without destroying it

我有一个适合我的观点的模型。
该模型是对象数组:

var arr = { "12345qwery": { prop1: "value", prop2: "value" } } // contains 500 items

今天我用以下方式过滤它:

arr = $filter('filter')(arr, filterTerm); // contains 4 items

在这一行之后,我得到了很好的过滤数据,但是如果我再次 运行 这个过滤器,我没有 500 个项目,而是 4 个。
因此,为了避免这种情况,我将原始数组存储在临时对象中,当用户更改过滤器时,我首先使用备份数据更新 arr(它是原始的 500 项)并进行过滤。
现在我遇到了麻烦,因为我有多个过滤器,我必须在每个过滤器之前恢复原始数据......无论如何都是一团糟:)
在 javascript 过滤中有更好的 (angular) 方法吗?

更新

为了更好地解释我创建了 plunker 的问题:

https://plnkr.co/edit/99b02UtUfPeM3wl4IiX6?p=preview

如您所见,我使用对象加载标记并希望通过文本字段对其进行过滤。
但我不能,因为我总是会遇到一些错误。
我做错了什么吗?
为了避免这种情况并以某种方式实现过滤器,这就是为什么我决定在代码中执行此操作并在每个过滤器之后保留原始数组,但这是非常复杂的解决方案,我不想以更自然的方式实现它 angular .

赏金更新

我在 js 代码中过滤对象,因为我找不到以标准 angular 方式过滤此指令上的标记的方法。
这就是为什么我在代码中过滤并且在过滤之前总是复制它的原因。
我需要帮助以标准 angular 方式过滤此指令上的标记对象。
Plunker 实现了这个指令,但我不知道如何过滤它。

当您在 控制器 中应用 angular 过滤器时,这是一个一次性过程。看来您的用例实际上更适合在 视图 中应用过滤器,如下所示:

{{ arr | filter : filterTerm}}

这将使您的模型保持不变,但无论如何只会在视图中显示过滤后的项目。 fiddle 显示了 filterTerm 的输入字段的用法。

您可以像我在 a plunker forked from yours 中那样做。创建一个工厂,在那里你保存你的标记对象,return 它们被调用到控制器,然后根据 filterTerm 过滤它们(这在你原来的 plunker 中不在你的控制器范围内,顺便说一句)。

app.factory('myMarkers', function() {

    var markers = {
        m1: {
            lat: 51.505,
            lng: -0.09,
            data: 'a'
        },
        m2: {
            lat: 51,
            lng: 0,
            data: 'ab'
        },
        m3: {
            lat: 51,
            lng: 0.1,
            data: 'abc'
        },
        m4: {
            lat: 51,
            lng: 0.14,
            data: 'abcd'
        }
    };

    function filterMarkersBy(term) {
        return _.filter(markers, function(marker) {
          return marker.data.indexOf(term) > -1;
        });
    }

    return {
        markers: markers,
        filterMarkersBy: filterMarkersBy
    }
});

然后在您的控制器中,您可以通过将所有标记放在 $scope 上(使用 angular.extend($scope, { markers: myMarkers.markers });)来初始化地图,然后观察您的 $scope.filterTerm 的值进行过滤$scope.markers 相应的对象。

...
angular.extend($scope, { markers: myMarkers.markers });

$scope.filterTerm;

$scope.$watch(function() { return $scope.filterTerm; }, function(newVal) {
    if (newVal) {
      console.log(newVal);
        $scope.markers = myMarkers.filterMarkersBy(newVal);
    }
});

现在它会即时过滤并在您减少过滤条件时将标记添加回去。请注意,我正在使用 lodash 的 _.filter() 方法在工厂中进行过滤,但您可能已经将 lodash 作为依赖项了。

一种在 angularjs 视图中过滤的方法

<input type="text" ng-model="filterBy"><!-- this is filter option -->
<div ng-repeat="row in rows| filter: {filterName : filterBy}">

或者你也可以在控制器中尝试这样的东西

$scope.filtered = $filter('filter')($scope.results, filterTerm)[0];

实现此目的的一种方法是创建两个数据副本。保持原始状态,并将过滤后的数据副本分配给地图作为标记。当用户更改过滤条件时,对原始数据应用过滤器并将结果分配给过滤后的数据变量。例如,

$scope.orignalMarkers = {1,2,3,4}
$scope.filteredMarkers = $scope.orignalMarkers // initial values for both are same

watch (filterTerm){
   $scope.filteredMarkers = $filter on $scope.orignalMarkers
}
$scope.filteredMarkers// assign this variable to map.

请原谅我的编码,它只是 sudo。

问题是您正在尝试过滤对象而不是数组。

尝试构建您自己的自定义文件管理器:

app.filter('myObjectFilter', function() {
  return function(input, search) {
    var result = {};
    for (var key in input) {
      if (input.hasOwnProperty(key)) {
        if (input[key].data.toLowerCase().indexOf(search.toLowerCase()) > -1) {
          result[key] = input[key];
        }
      }
    }
    return result;
  }
});

参见:https://plnkr.co/edit/Gi4gWHne57owB44MTsAB?p=preview

好的..所以你有一些事情要做。

问题

  • 范围:将您的范围稍微移开一点。由于您需要使用 filterTerm 它需要在您的控制器范围内,因此将范围移出一个级别。我将其移至 <body> 标签 - 请参阅 plnkr.

  • 结构:尝试始终将您的 JS 文件包含在 <body> 标记的末尾,并确保您的顺序正确。即在 angular-simple-logger.js

  • 之前包含 angular.js
  • $scope的使用: 你可以直接使用scope,不需要扩展它,只会增加阅读难度。

  • 模型结构:你的Markers元素层次太深,我把Markers变量设为标记对象数组.

解决方案

使用Angular的filter,还是不错的,但需要使用得当。 一般是这样的: {{ array_to_filter | filter : filter_term}}

所以在这种情况下你可以这样使用它:

<leaflet defaults="defaults" markers="markers | filter: filterTerm " height="480px" width="640px"></leaflet>

现在应该可以使用了,只需尝试搜索 LondonPark

如果您在 JS 代码中使用过滤器,则更容易将其设为变量在其作用域末尾终止的函数。否则你将永远覆盖你的变量。

TL;RD

这是一个包含工作版本的plnkr

简短的回答是

Angular 在两种情况下进行过滤时都不会破坏数组:

在HTML

{{ arr | filter : filterTerm}}

或在 JS 中:

newArray = $filter('filter')(arr, filterTerm);

这将是新数组。

您需要使用 angular.copy

复制标记数组
angular.extend($scope,{
  filteredMarkers:angular.copy($scope.markers)
 });  

编写您的客户过滤器以过滤对象而不是数组

app.filter('markerFilter',function(){
   return function(input,filterBy){
     var markers = [];
     if(input && filterBy && input[filterBy]){
       markers.push(input[filterBy]);
       return markers;
     }
     return input;
   }
 })

查看plunker,在文本框中输入m1、m2、m3,然后tab out。 https://plnkr.co/edit/GI4gn5

您正在用新的筛选数组覆盖您的数组。

arr = $filter('filter')(arr, filterTerm);

相同
var x = 5;
x = x+4;

我不知道有一种简单的内置 'angular' 方法可以解决这个问题。以下是我将如何处理通过多个过滤器过滤列表。我会保留一组过滤器,然后只要这些过滤器中的任何一个发生变化,就会根据所有过滤器重新生成结果列表。

看看这个片段,看看它是否符合您的要求。

angular.module('app', [])
  .controller('MyController', function($filter) {
    var vm = this;
    vm.markers = [{
      lat: 51.505,
      lng: -0.09,
      data: 'a'
    }, {
      lat: 51,
      lng: 0,
      data: 'ab'
    }, {
      lat: 51,
      lng: 0.1,
      data: 'abc'
    }, {
      lat: 51,
      lng: 0.14,
      data: 'abcd'
    }];

    //list of all terms to filter the data by
    vm.filterTerms = [];

    //start out with an unfiltered list of markers
    vm.filteredMarkers = vm.markers;

    vm.addFilterTerm = function(term) {
      vm.filterTerms.push(term);
      vm.filterMarkers();
    };

    /**
     * Takes the source array, and applies every filter to it and saves it to the filtered array
     */
    vm.filterMarkers = function() {
      //start with the original data
      var result = vm.markers;
      for (var i in vm.filterTerms) {
        //get the current term
        var filterTerm = vm.filterTerms[i];
        //filter the results by the current filter term
        result = $filter('filter')(result, filterTerm);
      }
      vm.filteredMarkers = result;
    }
  });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js"></script>
<div ng-app="app">
  <div ng-controller="MyController as vm">
    <input type="text" ng-model="vm.currentFilterTerm" />
    <button ng-click="vm.addFilterTerm(vm.currentFilterTerm);">Add to filter</button>
    <h1>Filters</h1>
    <ul>
      <li ng-repeat="term in vm.filterTerms">{{term}}</li>
    </ul>
    <h1>Markers</h1>
    <ul>
      <li ng-repeat="marker in vm.filteredMarkers">{{marker}}</li>
    </ul>
  </div>
</div>

在应用 $filter.

之前,使用 $watch('searchTerm') 过滤更改并将 markers 对象转换为数组
  $scope.filteredMarkers=$scope.markers;          
  $scope.$watch("filterTerm",function(filterTerm){
        $scope.arr=Object.keys($scope.markers).map(function(key) {
          return $scope.markers[key];
        });
        $scope.filteredMarkers=filterTerm ? $filter('filter')($scope.arr, {'data':filterTerm}) : $scope.markers;
    });

最后在指令上使用过滤标记:

<leaflet defaults="defaults" markers="filteredMarkers" height="480px" width="640px"></leaflet>

查看更新的插件:https://plnkr.co/edit/P5bNzHmZ2CRjImbMztyr?p=preview