334

So I have an ng-repeat nested within another ng-repeat in order to build a nav menu. On each <li> on the inner ng-repeat loop I set an ng-click which calls the relevant controller for that menu item by passing in the $index to let the app know which one we need. However I need to also pass in the $index from the outer ng-repeat so the app knows which section we are in as well as which tutorial.

<ul ng-repeat="section in sections">
    <li  class="section_title {{section.active}}" >
        {{section.name}}
    </li>
    <ul>
        <li class="tutorial_title {{tutorial.active}}" ng-click="loadFromMenu($index)" ng-repeat="tutorial in section.tutorials">
            {{tutorial.name}}
        </li>
    </ul>
</ul>

here's a Plunker http://plnkr.co/edit/bJUhI9oGEQIql9tahIJN?p=preview

Episodex
  • 4,479
  • 3
  • 41
  • 58
Tules
  • 4,905
  • 2
  • 27
  • 29
  • Why do you want to pass $index? just pass the object reference like this `ng-click="loadFromMenu(section)"`. Passing $index means you will do a loop to find the object which is unnecessary. – Moshi Nov 25 '17 at 18:38

6 Answers6

486

Each ng-repeat creates a child scope with the passed data, and also adds an additional $index variable in that scope.

So what you need to do is reach up to the parent scope, and use that $index.

See http://plnkr.co/edit/FvVhirpoOF8TYnIVygE6?p=preview

<li class="tutorial_title {{tutorial.active}}" ng-click="loadFromMenu($parent.$index)" ng-repeat="tutorial in section.tutorials">
    {{tutorial.name}}
</li>
Alex Osborn
  • 9,831
  • 3
  • 33
  • 44
  • awesome, so that's how I access the outer $index, but I needed to pass in BOTH $index values. I tried putting them in an array and it worked :) – Tules Mar 06 '13 at 21:26
  • 18
    You don't have to use an array: `ng-click="loadFromMenu($parent.$index, $index)"` – Mark Rajcok Mar 06 '13 at 21:37
  • 3
    you don't even have to pass the indexes in - in the loadFromMenu, it should have access to a this object, which will give you access to: this.$index and this.$parent.$index – Oddman Oct 22 '13 at 18:43
  • 3
    @Oddman even though this is possible I don't think its a good idea as you then hard wire your loadFromMenu to run at a context of an object that has a scope with an $index and a parent scope with an $index. suppose you then for example create groups within the menu that generate another scope in between the two significant ones - you will have to change loadFromMenu instead of changing the call to it. And if in some menues you need to call `loadFromMenu($parent.$parent.$index,$index)` and in some you need just `loadFromMenu($parent.$index,$index)` then using `this....` would just not work. – epeleg Mar 05 '14 at 06:19
  • Agreed. Usually when I do this kind of thing I build directives specific to the resource, so it's not an issue. – Oddman Mar 05 '14 at 23:45
  • This works well for simple tables if the table model is represented in the controller with a 2D array. You can use nested ng-repeats for the rows. – AlexMA Oct 14 '14 at 15:31
  • 7
    You should not use $parent.$index as it's not really safe. If you add an ng-if inside the loop, you get the $index messed, check: http://plnkr.co/52oIhLfeXXI9ZAynTuAJ – Gonz Dec 10 '14 at 05:06
  • @gonzalon So what do you suggest instead? – Casey Jul 10 '15 at 14:12
  • @Casey look at my answer http://stackoverflow.com/questions/15256600/passing-2-index-values-within-nested-ng-repeat/31373160#31373160 – Gonz Jul 12 '15 at 22:23
  • Using $parent is what worked for me when using angular-ui-tree, where nodes can be dragged/dropped in different locations. It might be brittle, but at least it does not result in data corruption. The other answers here resulted in corrupted data when nodes were moved, since the indexes were not updated. – Mike Taverne Dec 11 '15 at 00:43
  • you don't need to pass the $index and $parent.$index variables to your function. In your function you can get the index with: var index = this.$index; var parent = this.$parent.$index; – goldlife Feb 15 '16 at 12:25
207

Way more elegant solution than $parent.$index is using ng-init:

<ul ng-repeat="section in sections" ng-init="sectionIndex = $index">
    <li  class="section_title {{section.active}}" >
        {{section.name}}
    </li>
    <ul>
        <li class="tutorial_title {{tutorial.active}}" ng-click="loadFromMenu(sectionIndex)" ng-repeat="tutorial in section.tutorials">
            {{tutorial.name}}
        </li>
    </ul>
</ul>

Plunker: http://plnkr.co/edit/knwGEnOsAWLhLieKVItS?p=info

vucalur
  • 6,007
  • 3
  • 29
  • 46
  • Works perfectly, not just for functions but also as part of a compound ng-class condition dependant on parent data – Jake Rowsell Jan 06 '14 at 15:07
  • 2
    This is the recommended method of achieving this effect. In fact the angular team have expressed a few times that this is one of the few recommended uses of the ng-init directive (see: [link](https://docs.angularjs.org/api/ng/directive/ngInit)). I often find use of $parent (as per the currently accepted answer) is a bit of a code smell as there's usually a better way of achieving $scope to $scope communication (Services or Broadcast/Emit Events) and by assigning it to a named variable it becomes clearer what the value represents. – David Beech May 16 '14 at 12:39
  • 2
    IMO this answer is way better than the accepted. Using $parent is really, really unadvised. – olivarra1 Sep 23 '14 at 15:25
  • 1
    This is a much better and more elegant solution than the accepted answer (I tried the previous one and ended up with $parent.$parent.$index for my code - looks really brittle). Thanks! – spinningarrow Oct 09 '14 at 12:22
  • 46
    This stops working when you remove items from the list because the ng-init value doesn't re-initialize. It is only useful if you don't intend to remove items from the list. – Jesse Greathouse Dec 05 '14 at 18:06
  • 1
    @JesseGreathouse very important point that can prevent really difficult bugs – Moshe Shaham Jul 02 '15 at 13:52
  • 6
    Looks like angular syntax evolved. http://stackoverflow.com/a/31477492/2516560 you can now use "ng-repeat = (key,value) in data" – Okazari Jul 17 '15 at 13:57
  • simple but better than using $parent selectors as the parent can change. – Shannon Hochkins Aug 14 '16 at 22:33
  • 2
    Back in the days then I wrote this answer, this was the way to do it for Angular 1.X. This use of `ng-init` was demonstrated in `ng-init` docs with no single mention in `ng-repeat` docs, so I wrote here + amended the docs on Github. Current `ng-repeat` syntax has a better way to achieve this, which is demonstrated by @Okazari. It is free of some imperfections mentioned by you in the comments. – vucalur Oct 09 '16 at 08:55
124

What about using this syntax (take a look in this plunker). I just discovered this and it's pretty awesome.

ng-repeat="(key,value) in data"

Example:

<div ng-repeat="(indexX,object) in data">
    <div ng-repeat="(indexY,value) in object">
       {{indexX}} - {{indexY}} - {{value}}
    </div>
</div>

With this syntax you can give your own name to $index and differentiate the two indexes.

Smi
  • 13,850
  • 9
  • 56
  • 64
Okazari
  • 4,597
  • 1
  • 15
  • 27
  • 8
    I think this is much cleaner than using parent when you have multiple nested loops – Yasitha Waduge Oct 12 '15 at 05:02
  • Actually this caused me some real problems when using the angular-ui-tree to drag/drop nodes. The index doesn't seem to get reset and weird things happen as a result. – Mike Taverne Dec 11 '15 at 00:34
  • @MikeTaverne Could you try to reproduce in a plunker ? I'd love to see the behavior. – Okazari Dec 11 '15 at 08:51
  • 4
    This is the correct way to accomplish this and avoid any potential issues. ng-init works for the initial render, but if the object is updated on the page, it breaks. – Eric Martin Feb 18 '16 at 17:50
  • good! but please fix with adding "track by" like the plunker example. – Danilo Jul 25 '19 at 12:06
  • @Danilo Hey Danilo, i won't add my "track by" example from the plunker since it's a bad practice to use indexes as indentifier for track by. You should always use an indentifier specific to the object (like the id). Since i don't have id's in my example, my "track by" in the plunker is just a workaround. – Okazari Jul 31 '19 at 10:01
34

Just to help someone who get here... You should not use $parent.$index as it's not really safe. If you add an ng-if inside the loop, you get the $index messed!

Right way

  <table>
    <tr ng-repeat="row in rows track by $index" ng-init="rowIndex = $index">
        <td ng-repeat="column in columns track by $index" ng-init="columnIndex = $index">

          <b ng-if="rowIndex == columnIndex">[{{rowIndex}} - {{columnIndex}}]</b>
          <small ng-if="rowIndex != columnIndex">[{{rowIndex}} - {{columnIndex}}]</small>

        </td>
    </tr>
  </table>

Check: plnkr.co/52oIhLfeXXI9ZAynTuAJ

Eric Martin
  • 2,841
  • 2
  • 23
  • 23
Gonz
  • 1,198
  • 12
  • 26
  • 1
    Note that the "track by" isn't needed here - that's a separate thing used for ng-repeat to understand unique items and watch for changes in the collection (see "Tracking and Duplicates" section of the docs: https://docs.angularjs.org/api/ng/directive/ngRepeat). The important point here is the "ng-init" - that is the correct way that the angular docs agree with. – Rebecca Feb 11 '16 at 19:17
  • @gonzalon How to pass these index as parameter in ng click – Nagaraj S Mar 23 '16 at 14:15
  • I am doing removeList($index) or removeList(rowIndex) It is not recording the index correctly in function I receive index value as undefined . I am using angular 1.5.6 – user269867 Mar 03 '17 at 19:42
  • This is the right way, you should never access $parent – Jesú Castillo Aug 22 '17 at 18:11
4

Just use ng-repeat="(sectionIndex, section) in sections"

and that will be useable on the next level ng-repeat down.

<ul ng-repeat="(sectionIndex, section) in sections">
    <li  class="section_title {{section.active}}" >
        {{section.name}}
    </li>
    <ul>
        <li ng-repeat="tutorial in section.tutorials">
            {{tutorial.name}}, Your section index is {{sectionIndex}}
        </li>
    </ul>
</ul>
RobKohr
  • 6,611
  • 7
  • 48
  • 69
3

When you are dealing with objects, you want to ignore simple id's as much as convenient.

If you change the click line to this, I think you will be well on your way:

<li class="tutorial_title {{tutorial.active}}" ng-click="loadFromMenu(tutorial)" ng-repeat="tutorial in section.tutorials">

Also, I think you may need to change

class="tutorial_title {{tutorial.active}}"

to something like

ng-class="tutorial_title {{tutorial.active}}"

See http://docs.angularjs.org/misc/faq and look for ng-class.

kwerle
  • 2,225
  • 22
  • 25