17

How do I access the set of scopes generated by an ng-repeat?

I suppose at the heart of this is the fact that I don't understand quite how the relationship works between a) the collection of objects that I pass into the ng-repeat directive and b) the collection of scopes that it generates. I can play around with (a), which the ng-repeat scope watches and picks up, but how do I set variables on the scope itself (b)?

My use case is that I have a set of elements repeating using ng-repeat, each of which has an edit view that gets toggled using ng-show/ng-hide; the state for each element is held in a variable in the local scope. I want to be able to trigger an ng-show on a particular element, but I want the trigger to be called from outside the ng-repeat, so I need to be able to access the local scope variable.

Can anyone point me in the right direction (or tell me if I'm barking up the wrong tree)?

Thanks

Update: Link below was very helpful thankful. In the end I created a directive for each of the repeating elements, and used the directive's link function to add its scope to a collection on the root scope.

Steve
  • 237
  • 1
  • 4
  • 10
  • All scopes created during ng-repeat inherit from the parent scope, so you can easily access the parent scope element from the child scope, but you cannot change the parent scope elements from child scope (holds true for primitive types). Please go through this https://github.com/angular/angular.js/wiki/Understanding-Scopes – Chandermani Jul 24 '13 at 08:43
  • Thanks - will have a look. My issue is not that I want to access the parent scope from the child scopes but the other way round. – Steve Jul 24 '13 at 09:17
  • See http://stackoverflow.com/questions/14049480/what-are-the-nuances-of-scope-prototypal-prototypical-inheritance-in-angularjs – noj Jul 24 '13 at 09:23
  • Hi thanks for this. I did read that before. My problem isn't about inheritance I don't think, rather the other way round. How do I access the child variables from inside the parent? – Steve Jul 24 '13 at 09:25
  • We you can have a look at `scope.$broadcast` to achieve what your are looking for. – Chandermani Jul 24 '13 at 10:24
  • Steve, I have exactly that issue you've described. In one step to use so hard solution with angular.element. Have you find something more clean and good? – Semyon Vyskubov Nov 22 '13 at 16:02

3 Answers3

19

Here is a pretty simple way to do what I think you are trying to do (I figured this out when I needed to do something similar):

We know that each repeated item has it's own scope created for it. If we could pass this scope to a method defined on the parent scope, then we'd be able to do what we want with it in terms of manipulating or adding properties. It turn out this can be done by passing this as an argument:

Example

// collection on controller scope
$scope.myCollection = [
  { name: 'John', age: 25 },
  { name: 'Barry', age: 43 },
  { name: 'Kim', age: 26 },
  { name: 'Susan', age: 51 },
  { name: 'Fritz', age: 19 }
];



// template view
<ul>
  <li ng-repeat="person in myCollection">
    <span ng-class="{ bold : isBold }">{{ person.name }} is aged {{ person.age }} </span>
    <button class="btn btn-default btn-xs" ng-click="toggleBold(this)">toggle bold</button>
  </li>
</ul>

So when we press the "toggle bold" button, we are calling the $scope.toggleBold() method that we need to define on the controller's $scope. Notice that we pass this as the argument, which is actually the current ng-repeat scope object.

Therefore we can manipulate it like this

$scope.toggleBold = function(repeatScope) {
  if (repeatScope.isBold) {
    repeatScope.isBold = false;
  } else {
    repeatScope.isBold = true;
  }
};

Here is a working example: http://plnkr.co/edit/Vg9ipoEP6wxG8M1kpiW3?p=preview

Michael Bromley
  • 4,792
  • 4
  • 35
  • 57
11

Ben Nadel has given a pretty clean solution to the "how do I assign to an ngRepeat's $scope" problem, which I just implemented in my own project. Essentially, you can add an ngController directive alongside your ngRepeat, and manipulate ngRepeat's $scope inside the controller.

Below is my own contrived example, which demonstrates assigning to ngRepeat's $scope in a controller. Yes, there are better ways to do this exact thing. See Ben Nadel's post for a better example.

<div ng-controller="ListCtrl">
  <h1>ngRepeat + ngController</h1>
  <ul>
    <li ng-repeat="item in items" ng-controller="ItemCtrl" ng-show="isVisible">
      {{item.name}}
      <button ng-click="hide()">hide me!</button>
    </li>
  </ul>
</div>

<script type="text/javascript">
  var app = angular.module("myApp", []);

  app.controller("ListCtrl", function($scope) {
    $scope.items = [
      {name: "Item 1"},
      {name: "Item 2"},
      {name: "Item 3"}
    ];
  });

  app.controller("ItemCtrl", function($scope) {
    $scope.isVisible = true;
    $scope.hide = function() {
      $scope.isVisible = false;
    };
  });
</script>

EDIT: Having re-read your question, seeing that you need to manipulate a bunch of child scopes in a parent scope, I think that your directive(s) approach is the way to go. I still think this answer may be useful to some, as I came across your question while looking for this answer.

Spencer Judd
  • 409
  • 3
  • 5
1

When working within a hierarchy of scopes I find very useful to dispatch events with $emit and $broadcast. $emit dispatches an event upwards so your child scopes can notify parent scopes of a particular event. $broadcast is the other way round.

Alternatively, as child scopes have access to parent scope properties you could trigger changes by using $watch on a particular property in the parent scope.

UPDATE: As for accessing the child scopes, this may turn useful for you : Get to get all child scopes in Angularjs given the parent scope

Community
  • 1
  • 1
Joe Minichino
  • 2,793
  • 20
  • 20
  • The trouble is, the parent scope is only talking to one particular child scope in the ng-repeat collection ... so not sure how this would work with $broadcast? The simplest solution to me would be just to set a variable directly on the child scope so it does something ... kind of like $scope.ngRepeatScopesCollection[index].var = y But is there no such collection? Or am I asking silly questions / too much of a newbie to understand the angular way? ;-) – Steve Jul 24 '13 at 10:39
  • in that case you could have your ng-repeat elements set as visible depending on a particular property of the single object injected in the child scope with ng-class. I.e. you could set an element as visible if property x of the object equals to true. Take a look at this [http://docs.angularjs.org/api/ng.directive:ngClass] – Joe Minichino Jul 24 '13 at 11:00
  • I'm already using ng-class based on properties inherited from the parent scope, which I've found straightforward. My issue now is that I need to trigger ng-show on a **particular** element within the ng-repeat (in this case the first one). – Steve Jul 24 '13 at 11:12
  • The only way round I can see at the minute is to create another collection in the controller (or in a service) that the ng-repeat scopes enroll themselves into when they're created. I could then access them via this array. But this seems crazy when there's already (presumably) a collection for the ngRepeat scopes within their parent scope? – Steve Jul 24 '13 at 11:14
  • Well in that case I think you need to look at this [http://stackoverflow.com/questions/12649080/get-to-get-all-child-scopes-in-angularjs-given-the-parent-scope] – Joe Minichino Jul 24 '13 at 11:20
  • I used this information to solve the problem many thanks for the directions! Feel free if you want to post the link as the answer - I'll accept and comment on what I actually did. – Steve Jul 24 '13 at 11:55
  • Yea, arguably not as clean as being able to inherit $scope, but none-the-less a quite suitable solution. Prevents me from calling an `init` method with `this.$parent.$parent.$parent.$parent` XD – Volte Sep 29 '15 at 19:25