0

TL;DR skip to bottom-->

I found an ng-class example on Codepen, here (important code reproduced below).

In it, the author creates a simple menu bar that highlights the active item. Pretty standard. However, I thought the way he did it was funny, so I modified it to what made more sense to me and surprisingly it broke.

The author creates his view like this:

...
<div>Change the active menu item:
        <input ng-model="states.activeItem" />
</div>
<ul class="navigation">
        <li ng-repeat="item in items" class="item" ng-class="{'active': item.id == states.activeItem}" ng-click="states.activeItem=item.id">{{item.title}}</li>
</ul>
....

while his controller looks like

app.controller('NavigationController', function ($scope) {
    // Must use a wrapper object, otherwise "activeItem" won't work
    $scope.states = {};
    $scope.states.activeItem = 'item1';
    $scope.items = [{
        id: 'item1',
        title: 'Home'
    }, {
        id: 'item2',
        title: 'Public Rooms'
    }, {
        id: 'item3',
        title: 'My Rooms'
    }];
});

To me, I thought it would be easier to skip the states variable and just have a

$scope.activeItem

so I changed the controller to that, and changed the view to

<div>Change the active menu item:
        <input ng-model="states.activeItem" />
    </div>
    <ul class="navigation">
        <li ng-repeat="item in items" class="item" ng-class="{'active': item.id == activeItem}" ng-click="activeItem=item.id">{{item.title}}</li>
    </ul>

However it now doesn't work.

So it seems for some reason Angular needs to store these variables in a new object. But why?


TL;DR: Here are both Codepens:

The Original (works great)

Mine (doesn't work)


Why doesn't mine work?

CodyBugstein
  • 21,984
  • 61
  • 207
  • 363
  • It has to do with Angular's scope inheritance. I recommend you read this interesting post http://stackoverflow.com/questions/14049480/what-are-the-nuances-of-scope-prototypal-prototypical-inheritance-in-angularjs – kubuntu Nov 23 '14 at 11:40

1 Answers1

2

This is because the ng-repeat directive creates a new scope in each iteration. As stated in the ng-repeat documentation:

The ngRepeat directive instantiates a template once per item from a collection. Each template instance gets its own scope, where the given loop variable is set to the current collection item, and $index is set to the item index or key.

Using objects to provide access for child scopes towards its parent scope using scope inheritance. Using natives or primitives, will opt the current child scope to only access the parent scope once and then it will create that native/primitive scope variable once it has been assigned with a certain value. You can take your ng-click="activeItem=item.id" expression as a testament for the previous statements. To rectify this: you have two options, create an object that will hold your states to active scope inheritance lookup, or use the $parent property to access the parent scope, ng-click="$parent.activeItem=item.id", this also applies to the ng-class directive, ng-class="{'active': item.id == $parent.activeItem}"

DEMO

HTML

<h3>Using the `$parent` scope property</h3>
<ul>
  <li ng-repeat="item in items track by $index" 
    ng-class="{'active': $parent.activeItem == item.id}" 
    ng-click="$parent.activeItem = item.id" 
    ng-bind="item.name"></li>
</ul>

<h3>Using an object state</h3>
<ul>
  <li ng-repeat="item in items track by $index" 
    ng-class="{'active': state.activeItem == item.id}" 
    ng-click="state.activeItem = item.id" 
    ng-bind="item.name"></li>
</ul>
ryeballar
  • 29,658
  • 10
  • 65
  • 74
  • You can read more about scopes in the [**AngularJS github wiki**](https://github.com/angular/angular.js/wiki), checkout the [**Understanding Scopes**](https://github.com/angular/angular.js/wiki/Understanding-Scopes) section. And don't forget to read the [**Developer's Guide**](https://docs.angularjs.org/guide) as well cheers :) – ryeballar Nov 23 '14 at 12:47