How to filter angular model (array) without destroying it
Q

11

7

I have a model for my view.
That model is array of objects:

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

And today I am filtering it in the following way:

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

And after this line I get nice filtered data but if I run this filter again I don't have 500 items in it but 4.
So to avoid this I store original array in temporary object and when user change filter I first update arr with backup data (it's original 500 items) and do the filtering.
Now I get in trouble as I have multiple filters and I must restore original data before each filter... anyway it is a mess :)
Is there any better (angular) way to make this in javascript filtering?

UPDATE

To explan better what is issue I created plunker:

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

As you can see I load markers with objects and want to filter it via text field.
But I can't as I always get some errors.
Am I doing anything wrong here?
And to avoid this and implement filter somehow that is why I decided to do it in code and keep original array after each filter, but this is very complex solution and I wan't to make it in a more natural angular way.

BOUNTY UPDATE

I am filtering object in js code because I can't find a way to filter markers on this directive in a standard angular way.
That is why I filter in code and before filter always make a copy of it.
I need help to filter marker objects on this directive in a standard angular way.
Plunker implement this directive but I don't know how to filter it.

Quarterage answered 27/2, 2016 at 19:0 Comment(8)
question is not clear, could you please add jsfiddler for example to demostrate the problem?Farland
Is there any reason why you're filtering the array from JS and not from HTML template?Kootenay
I am using this directive. And it is not filterable in a standard angular way I don't know why: github.com/tombatossals/angular-leaflet-directive/issues/395Quarterage
How does your plunk attempt to make use of filtering? I can't find any uses of either $filter or the {{ | filter }} syntax.Fuld
You cannot use the filter pipe on objects, it must be used on arrays. In your Plunker, arr is an object. Using any variation of filter will result in an error like this.Jewry
Seems like you have code that successfully filters your object. Wrap that code in a service, then when the user changes the filter, call the service, and have it return a filtered version of the object.Fuld
I use cached object - only object in array for performance I can't call service all the time. @IgorRaush This directive have some issues when using standard [] that is why I use {}. There must be some (angular) way to filter objects?Quarterage
@Quarterage I think you've got it backwards... I don't know of a built-in Angular function to filter object values (you can see some alternative suggestions in the error page link I posted above). If you have a plain array, you should be able to get by with the built-in filter.Jewry
D
5

Ok.. So you have a few things going on.

Issues

  • Scoping: Move your scope a bit out. Since you need to use filterTerm it needs to be within your controller scope, so move the scope a level out. I moved it out to the <body> tag - see plnkr.

  • Structure: Try to always include your JS files at the end of the <body> tag, and make sure you have the right order. I.e include angular.js before angular-simple-logger.js

  • Use of $scope: You can use scope directly, you don't need to extend it, that just makes it harder to read.

  • Model structure: Your Markers elements are a level too deep, I make the Markers variable an array of marker objects.

Solution

Use Angular's filter, it's pretty good, but need to be used properly. Generally it's like this: {{ array_to_filter | filter : filter_term}}

So in this case you can use it like so:

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

Should be working now, just try to search for London or Park.

If you use a filter in your JS code it's easier to just make it a function where the variable dies at the end of it's scope. Otherwise you will always be overwriting your variable.

TL;RD

Here's a plnkr containing the working version.

Dolomite answered 4/3, 2016 at 5:30 Comment(0)
C
4

the short answer is

Angular is not destroying array when you do filtering in both cases:

either in HTML

{{ arr | filter : filterTerm}}

or in JS:

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

it will be new array.

Checkmate answered 4/3, 2016 at 8:57 Comment(0)
L
3

You need to copy the markers array using angular.copy

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

Write your customer filter to filter object instead of array

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

Check out the plunker, write m1, m2, m3 in text-box and tab out. https://plnkr.co/edit/GI4gn5

Langdon answered 4/3, 2016 at 14:52 Comment(0)
C
2

When you apply an angular filter in your controller, it is a one-shot process. It seems that your use-case actually fits better to applying the filter within the view, like this:

{{ arr | filter : filterTerm}}

This will leave your model unchanged, but show only the filtered items in the view anyway. This fiddle shows the usage with an input field for the filterTerm.

Convolve answered 27/2, 2016 at 19:19 Comment(4)
But how I can run filtering from code then? My filter should work when I type in text input so I need event for this.Quarterage
@Quarterage use ng-model and same object in filterTermInternist
added fiddle with example usage.Convolve
I updated question with complete plunker example. If I try to use filterTerm I get mass errors in console.Quarterage
A
2

The problem is that you are trying to filter an object instead an array.

Try to build your own custom filer:

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;
  }
});

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

Anglesey answered 3/3, 2016 at 22:34 Comment(0)
A
2

You're overwriting your array with the new filtered array.

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

Is the same as

var x = 5;
x = x+4;
Admonition answered 4/3, 2016 at 21:11 Comment(0)
P
1

You could do something like I've done in a plunker forked from yours. Create a factory where you keep your marker objects, return them to the controller as called, and then filter them according to the filterTerm(which wasn't within the scope of your controller in your original plunker, btw).

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
    }
});

And then in your controller, you can initialize the map by putting all the markers on $scope (with angular.extend($scope, { markers: myMarkers.markers });), and then watch the value of your $scope.filterTerm to filter the $scope.markers object accordingly.

...
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);
    }
});

Now it filters on the fly and adds the markers back as you reduce the filter term. Note that I'm using lodash's _.filter() method to filter in the factory, but you've probably already got lodash as a dependency anyway.

Pemberton answered 29/2, 2016 at 22:33 Comment(0)
T
1

One way to filter in angularjs view

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

Or you can also try in controller something like this

$scope.filtered = $filter('filter')($scope.results, filterTerm)[0];
Tocci answered 1/3, 2016 at 5:29 Comment(0)
S
1

One way to accomplish this is to create two copies of data. keep original same as ever and assign filtered copy of data to map as marker. when ever user changes filter term, apply filter on Original data and assign result to filtered data variable. for instance,

$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.

forgive my coding, its just sudo.

Salvation answered 3/3, 2016 at 0:28 Comment(0)
J
0

I don't know that there is an easy built-in 'angular' way to approach this. Here is how I would handle filtering a list by multiple filters. I would keep an array of filters, and then anytime any of those filters changed, regenerate the list of results based on ALL of the filters.

Take a look at this snippet and see if it does what you are looking for.

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>
Jilt answered 7/3, 2016 at 19:16 Comment(0)
P
0

Use $watch('searchTerm') to filter on change and transform the markers Object to array before apply $filter.

  $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;
    });

Finally use the filteredMarkers on directive:

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

See updated plunker: https://plnkr.co/edit/P5bNzHmZ2CRjImbMztyr?p=preview

Professorship answered 7/3, 2016 at 19:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.