AngularJS - how to get an ngRepeat filtered result reference

82,223

Solution 1

UPDATE: Here's an easier way than what was there before.

 <input ng-model="query">
 <div ng-repeat="item in (filteredItems = (items | orderBy:'order_prop' | filter:query | limitTo:4))">
   {{item}}
 </div>

Then $scope.filteredItems is accessible.

Solution 2

Here's the filter version of Andy Joslin's solution.

Update: BREAKING CHANGE. As of version 1.3.0-beta.19 (this commit) filters do not have a context and this will be bound to the global scope. You can either pass the context as an argument or use the new aliasing syntax in ngRepeat, 1.3.0-beta.17+.

// pre 1.3.0-beta.19
yourModule.filter("as", function($parse) {
  return function(value, path) {
    return $parse(path).assign(this, value);
  };
});

// 1.3.0-beta.19+
yourModule.filter("as", function($parse) {
  return function(value, context, path) {
    return $parse(path).assign(context, value);
  };
});

Then in your view

<!-- pre 1.3.0-beta.19 -->
<input ng-model="query">
<div ng-repeat="item in items | orderBy:'order_prop' | filter:query | limitTo:4 | as:'filteredItems'">
 {{item}}
</div>

<!-- 1.3.0-beta.19+ -->
<input ng-model="query">
<div ng-repeat="item in items | orderBy:'order_prop' | filter:query | limitTo:4 | as:this:'filteredItems'">
 {{item}}
</div>

<!-- 1.3.0-beta.17+ ngRepeat aliasing -->
<input ng-model="query">
<div ng-repeat="item in items | orderBy:'order_prop' | filter:query | limitTo:4 as filteredItems">
 {{item}}
</div>

Which gives you access to $scope.filteredItems.

Solution 3

Try something like this, the problem with the ng-repeat is that it creates child scope because of that you can't access

filteritems

from the controller

<li ng-repeat="doc in $parent.filteritems = (docs | filter:searchitems)" ></li>

Solution 4

Update:

Even after 1.3.0. if you want to put it on the scope of the controller or the parent you cannot do that with as syntax. For example if I have the following code:

<div>{{labelResults}}</div>
<li ng-repeat="label in (labels | filter:query | as labelResults)">
</div>

the above will not work. The way to go around it is using the $parent as so:

<li ng-repeat="label in ($parent.labelResults = (labels | filter:query))">

Solution 5

I came up with a somewhat better version of Andy's solution. In his solution ng-repeat places a watch on the expression that contains the assignment. Each digest loop will evaluate that expression and assign the result to the scope variable.

The problem with this solution is that you might run into assignment issues if you are in a child scope. This is the same reason why you should have a dot in ng-model.

The other thing I don't like about this solution is that it buries the definition of the filtered array somewhere in the view markup. If it is used in multiple places in your view or your controller it can get confusing.

A simpler solution is to just place a watch in your controller on a function that makes the assignment:

$scope.$watch(function () {
    $scope.filteredItems = $scope.$eval("items | orderBy:'order_prop' | filter:query | limitTo:4");
});

This function will be evaluated during each digest cycle so performance should be comparable with Andy's solution. You can also add any number of assignments in the function to keep them all in one place rather than scattered about the view.

In the view, you would just use the filtered list directly:

<ul>
    <li  ng-repeat="item in filteredItems">{{item.name}}</li>
</ul>

Here's a fiddle where this is shown in a more complicated scenario.

Share:
82,223
Shlomi Schwartz
Author by

Shlomi Schwartz

Updated on July 08, 2022

Comments

  • Shlomi Schwartz
    Shlomi Schwartz almost 2 years

    I am using an ng-repeat directive with filter like so:

    ng-repeat="item in items | orderBy:'order_prop' | filter:query | limitTo:4"
    

    and I can see the rendered results fine; now I want to run some logic on that result in my controller. The question is how can I grab the result items reference?

    Update:


    Just to clarify: I'm trying to create an auto complete, I have this input:

    <input id="queryInput" ng-model="query" type="text" size="30" placeholder="Enter query">
    

    and then the filtered results:

    <ul>
       <li  ng-repeat="item in items | orderBy:'order_prop' | filter:query | limitTo:4">{{item.name}}</li>
    </ul>
    

    now I want to navigate the result and select one of the items.

  • Shlomi Schwartz
    Shlomi Schwartz almost 12 years
    Thanks for the reply, please see my update. How would you implement that, note that I get the entire items list on init
  • Shlomi Schwartz
    Shlomi Schwartz almost 12 years
    super cool, thanks for that. I wish it was a built in feature of NG
  • Andrew Joslin
    Andrew Joslin over 11 years
    Updated again with a much easier way
  • Ben Lesh
    Ben Lesh over 11 years
    That's a good trick. I didn't think of using expressions in that way. Makes sense though.
  • Shlomi Schwartz
    Shlomi Schwartz over 11 years
    small correction, if you want the filtered items to reference the filtered result you should add another set of brackets like so:item in (filteredItems = (items | orderBy:'order_prop' | filter:query | limitTo:4))
  • Juho Vepsäläinen
    Juho Vepsäläinen over 10 years
    The problem with this approach is that if you watch the assigned property, you'll end up spamming $digests (one per iteration)... Maybe there's some way to avoid this?
  • Andrew Joslin
    Andrew Joslin over 10 years
    The other way is to $watch the incoming items in your controller and do it there every time the items that come in change.
  • Magne
    Magne almost 10 years
    Thanks! Could you explain how the filter code works? I'm not familiar with $parse, and the angular docs didn't help me decode how your filter works exactly.
  • kevinjamesus86
    kevinjamesus86 over 9 years
    @Magne No problem! Please check out the update though as it may save you some pain and suffering down the road. To answer your question, in short, $parse is Angular's expression compiler.. For example, it'll take the string 'some.object.property' and return a function that allows you to either set or get a value at said path for a specified context. I'm using it to set filter results on the $scope here, specifically. I hope this answers your question.
  • luQ
    luQ over 9 years
    Simply $watch §scope.searchText to avoid $digest spamming!
  • ProblemsOfSumit
    ProblemsOfSumit almost 9 years
    If I watch the search object and console.log filteredItems I always get the data one filter later. It's not the result of the just changed filter, but the one from the previous change. Any ideas?
  • dalvarezmartinez1
    dalvarezmartinez1 over 8 years
    Can someone explain me, why does this work? I can understand that a variable from the $scope, is accessible in the repeat scope (scope inheritance), but the other way around? How? Some documentation is appreciated. Thanks
  • kuzyn
    kuzyn about 8 years
    Super clean and doesn't pollute the structure, nice!
  • George Kagan
    George Kagan almost 8 years
    If you're trying to access the filtered array from outside a directive and you get digest spamming, provided you only need the length of the arr, just bind it to a new var instead of passing the whole results set.
  • Lakshay Dulani
    Lakshay Dulani over 7 years
    this solution is perfect for me..i was using ionic's collection repeat instead of ng repeat..thats why the accepted answer didnt work for me
  • Zack
    Zack over 7 years
    It's good to note that if you have a limitTo filter on this. It will not be reflected in the $parent.labelResults
  • Anna
    Anna about 7 years
    If I console.log labelResults I always get the data one filter later. It's not the result of the just changed filter, but the one from the previous change. Didn't you have this problem?
  • lepe
    lepe over 6 years
    if filteredItems is empty, try $parent.filteredItems = ... or using an object: table.filteredItems = ... which is: $scope.table.filteredItems.