I'm under the impression that ng-repeat creates a new scope for each element in the array/object.
Is it possible to access these new scopes that the ng-repeat creates from the controller? For example if you know the index?
Any help would be much appreciated.
Check the console of this demo: JSFiddle.
console.log the scope, there are two attributes $$childHead and $$childTail. They are the first and last child scopes created by ng-repeat.
After getting the first child scope $$childHead, you can traverse to get other ng-repeat scope objects through $$nextSibling and $$prevSibling.
Note: these attributes start with $$, which indicate that they are private Angular variables. I suppose they are not intended to be used directly.
If you use ng-repeat like <div ng-repeat="item in items"></div>, you can use ng-click="dealWithItem(item, $index)" to pass this item to a function defined in the controller:
$scope.dealWithItem = function (item, index) {
// console.log(item);
}
It works for nested ng-repeat as well: JSFiddle.
When I tried Joy's answer, I got undefined for the item variable at the console. But there is an easier way to do it.
html:
<tr ng-repeat="name in names">
<button class="btn btn-info btn-sm pull-right" ng-click="dealWithItem()">{{name.first_name}}</button>
</tr>
Controller:
$scope.dealWithItem = function () {
console.log(this.name.first_name);
}
this.name will have all of the attributes associated with $scope.names.
You can use $parent as an argument of your function to access these scopes:
<div ng-repeat="item in items">
<div ng-click="doSomethingWithItem(item, $parent)"></div>
</div>
Related
I have the following
<table id="socialMediaContainer" class="socialMediaContainer" style="width: 100%;">
<tbody>
<tr ng-repeat="row in detailCollection.ChannelsInfo"
ng-controller="WhiteLabelSitesCtrl">
<td><input id="txtSocialName" type="text" class="socialName"
placeholder="Name" ng-disabled="ViewMode" maxlength="250"
value="{{row.SocialChannelName}}" /> </td>
<td><input id="txtSocialURL" type="text" class="txtLabel socialURL"
placeholder="URL" ng-disabled="ViewMode" maxlength="250"
value="{{row.SocialChannelURL}}" />
</td>
<td class="DragnDropIcon"></td>
<td>
<a class="orange " ng-show="ViewMode">Upload</a></td>
</tr>
</tbody>
</table>
and I have another button outside the ng-repeat that updates the ViewMode variable, but this is not working inside the ng-repeat neither for the ng-show not the ng-disabled. what am i missing here?
The problem seems to be, that you need to move ngController directive to the table level (at least): it can't be on the same element with ngRepeat if the later iterated over the array defined in controller.
<table ng-controller="WhiteLabelSitesCtrl" ... >
<!-- ... -->
</table>
Demo: http://plnkr.co/edit/tu4TLmWIxdcYaiEd7whn?p=preview
ng-repeat creates a childscope for each item in the repeater.
Thus viewmode will be a primitive value on that child scope and therefore as a primitive will lose inheritance binding with the parent scope.
If you declare it as an object property in the controller scope however it will then be a reference to that parent object.
$scope.mode ={ViewMode: false}
html example
<a class="orange " ng-show="mode.ViewMode">Upload</a></td>
Try to pass object instead variable inside ng-repeat scope. Instead ViewMode,
declare in your controller:
$scope.model = {};
$scope.model.ViewMode = false;`.
And your binding must be like this: <a class="orange " ng-show="model.ViewMode">. After that all be work fine.
Read this article to understand why it happens: https://github.com/angular/angular.js/wiki/Understanding-Scopes
The button outside of the ng-repeat is not going to be nested in the same controller. The variable that it's modifying probably is not the same one that ViewMode under the WhiteLabelSitesCtrl is looking at.
When you point to this ng-controller, that controller will be activated with a new scope associated with it
<div ng-repeat ng-controller="WhiteLabelSitesCtrl">
<div ng-show="someValue"></div>
</div>
When you reference this controller again on another tag, it won't reference the existing controller as you might be expecting, it'll actually do the exact same thing... it will create the controller, and create a new scope for it- completely separate from the original one.
<div ng-controller="WhiteLabelSitesCtrl">
<button ng-click="someValue = !someValue"></button>
</div>
I have this code
http://plnkr.co/edit/aycnNVoD96UMbsC7rFmg?p=preview
<div data-ng-app="" data-ng-init="names=['One']">
<input type="text" ng-model="names[0]">
<p>Looping with ng-repeat:</p>
<ul>
<li data-ng-repeat="name in names">
<input type="text" ng-model="name"> {{ name }}
</li>
</ul>
</div>
When i change value of name[0] in the first input box it changes values of the second input box.
But when i change value of name[0] in the second input box it does not change value of the first input box. Why?
It works if you bind your second input to : names[$index]
<input type="text" ng-model="names[$index]"> {{ name }}
This is due to ng-repeat creating a child scope, so the reference to name inside the ng-repeat is different to that original one in the names array, see here:
New AngularJS developers often do not realize that ng-repeat,
ng-switch, ng-view and ng-include all create new child scopes, so the
problem often shows up when these directives are involved. (See this
example for a quick illustration of the problem.)
Regarding as to why this happens, when you bind the input to name in names inside the ng-repeat, you are creating a new property on the new child scope created by the ng-repeat called name, and thus the ng-model of the textbox created by the ng-repeat is referencing a different name to that of the actual 0th element of the names array. As others have pointed out, if you use names[$index] you are implicitly referencing the 0th element of the names array, thus NOT creating a new name property on the child scope created by the ng-repeat. An angular best practice is not to have ng-models bound to primitives, rather objects, Sandy has mentioned in his answer if you bind to an object you will overcome this, and the 2 other posters have answered this by using $index to refer to the 0th element of the names array. This is one of the nucances of scope inheritance in angular.
A couple more handy links:
Here and here.
Just wanted to give my bit on this. Somewhat related to your problem as I see.
<body>
<div data-ng-app="" data-ng-init="names=[{value:'One'}, {value:'Two'}]">
<p>Looping with ng-repeat:</p>
<ul>
<li data-ng-repeat="name in names">
<input type="text" ng-model="name.value"> {{ name }}
</li>
</ul>
</div>
</body>
Instead of binding the array item directly to the control, I would prefer to create an object of the array and then bind value of each item. This way we can avoid reference problems.
A working prototype jsfiddle
Hope it helps.
You need to provide $index in your ng-model.
<li data-ng-repeat="name in names">
<input type="text" ng-model="names[$index]"> {{ name }}
</li>
You are binding ng-model="names[0]". So it means that you are binding value on first index of names array.
So when we write ng-model="names[$index]" in ng-repeat it means that all values will be bound accordingly into array. $index is an iterator offset of the repeated element.
names[0] = 'One'
names[1] = 'Two'
and so on!
This question already has answers here:
Ng-click doesn't work inside ng-repeat
(8 answers)
Closed 8 years ago.
So I don't know if this is a problem with AngularJS, or my understanding.
In my app, I have an ng-repeat and I need to keep track of the active $index, which can be changed when you click on another item. So I thought I would do something like:
<body ng-init="active = -1">
<span ng-repeat="item in items" ng-bind="item" ng-click="active = $index"></span>
</body>
But this does not work; I know if I change the ng-click to ng-click="select($index) and apply the change in my controller, this would work. But I'd like to know why the implementation above doesn't work.
Interestingly, if you don't have an ng-repeat, this DOES work, i.e:
<body ng-init="active = -1">
<span ng-click="active = 0">Item 1</span>
<span ng-click="active = 1">Item 2</span>
...
</body>
Here is a Plunker of these two scenarios. Why?
You can fix that like below,
in HTML
<span ng-repeat="item in items track by $index" ng-click="x.badClick = $index"> // x.badClick
on controller
$scope.x = {};
thats because its create new scope for each repeat, reference ,,
reference said ,
Each template instance gets its own scope, where the given loop variable is set to the current
if u do like x.badClick , x is not in the ng-repeat scopes, so it will check the x in next top scope, if u need more, check about prototypical inheritance here is a good demonstration , link
you can refer the parent properties by using $parent also, so this will also work,
<span ng-repeat="item in items track by $index" ng-click="$parent.badClick = $index">
My ng-repeat creates different elements and I need to do some operations, like creating variables, only on the ng-repeat scope.
How can I retrieve the ng-repeat scope?
How can I do something like this?
<div ng-repeat="item in items">
<button onclick="load(item, $scope)"></button>
</div>
$scope has to be the ng-repeat scope only. Is this possible?
EDIT
that's the issue:
The code:
HTML
<div ng-repeat="item in items" class="container">
<div class="row">
Name: {{item.name}}
<select ng-model="item['id']"
ng-options="opt for opt in ids"
ng-change="loadTypes(item)"
>
<option value="">Default</option>
</select>
<select ng-model="item['type']"
ng-options="opt for opt in types"
>
<option value="">Default</option>
</select>
</div>
</div>
Controller
//Executed one time when module is loaded
$scope.init = function(){
//All ids on the DB
$scope.ids = getIdsFromServer();
}
//Executed when an Ids select changes
$scope.loadTypes = function(item){
//Possible types for an Id
types = getTypesFromServer(item.id);
/*
thisItemNgRepeatScope.types = getTypesFromServer(item.id) ?!??!! Something like this?
*/
}
The problem:
Ids it's the same in all the whole document and that's ok, it's works properly.
But I want to change the model for the second select (types) only on the ng-repeat scope. So when I change Id, I get all possible types for this Id, but only in the row where I am, where the select has been changed.
How can I do this?
the mighty 'this' in your load(item) function is the scope.
I assume that with "ng-repeat scope" are are refering to the parent scope of each individual ng-repeat loop. This can be done easily be referencing the parent scope of the current scope like this:
$scope.$parent
So, I can change a model value from a child controller, but when the child controller is in ng-switch then it doesn't work, why? I created an example to demonstrate it.
One way to avoid this is to use the . in the model name, like bunnies.kills. Is this a bug or this is a feature ?
Using Angular 1.0.6
Using your code structure, in your child controllers you would need to change:
$scope.$parent.kills++;
to
$scope.$parent.$parent.kills++;
Explanation: MainCtrl's scope is the parent scope of SimpleParentCtrl, but the grandparent of Step1Ctrl and Step2Ctrl. As some others pointed out, ng-switch creates its own scope, and then your Step1Ctrl and Step2Ctrl each created a child scope of the ng-switch.
Note: Each time the 1 or 2 button is clicked, both the ng-switch and it's currently matched child controller get a new scope.
Also: In case you happen to be looking in the Angular source and wondering how the ng-switch directive creates its own scope without a scope property, the answer is that it does so manually in its link method via scope.$new(). The directives ng-include, ng-switch, ng-repeat, and ng-view all create new scope this way, either in the link method or the compile method's returned link function.
Resources:
https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance
http://www.youtube.com/watch?v=ZhfUv0spHCY&feature=youtu.be&t=30m
ng-switch creates its own child scope, which is why #sh0ber's answer is one way to get it to work. In general, models should be referenced in controller scopes (hence reference objects), and not be not primitives. So using a . is a "best practice".
This is not a bug, but it is not a feature either. This is the way JavaScript prototypal inheritance works with primitives.
I would take a slightly different approach to this problem.
Rather than use $scope.$parent, I would recommend you move all of your bunny killing logic into a shared service/model.
Also, I would try to avoid referencing parent views/controllers. Referencing the parent can make it difficult to reuse your code and can be painful to debug as the project grows. It is okay for a parent to know about it's children but a child should know little to nothing about it's parent.
Here is an updated Plunk: http://plnkr.co/edit/PLDbfU8Fu7m59A42qdR6?p=preview
HTML
<body ng-controller="MainCtrl">
<p>
Dead bunnies: <strong>{{Elmer.deadWabbits}}</strong>
</p>
<div ng-controller="SimpleParentCtrl">
<button ng-click="Elmer.killTheWabbit()">Kill from simple parent gun</button>
</div>
<hr>
<div ng-switch="" on="step">
<div ng-switch-when="first" ng-controller="Step1Ctrl">
<button ng-click="Elmer.killTheWabbit()">Kill from 1 tab gun</button>
</div>
<div ng-switch-when="second">
<div ng-controller="Step2Ctrl">
<button ng-click="Elmer.killTheWabbit()">Kill from 2 tab gun</button>
</div>
</div>
</div>
<hr>
<p>
<button ng-click="changeStep('first')">1</button> <button ng-click="changeStep('second')">2</button>
</p>
</body>
JS
angular.module('plunker', []).
service("Elmer", [function() {
this.deadWabbits = 0;
this.killTheWabbit = function() {
this.deadWabbits++;
};
}]).
controller('MainCtrl', function($scope, Elmer) {
$scope.Elmer = Elmer;
$scope.step = 'first';
$scope.changeStep = function(name){
$scope.step = name;
};
}).
controller('SimpleParentCtrl', function() {}).
controller('Step1Ctrl', function() {}).
controller('Step2Ctrl', function() {});
One way to avoid this is to use the . in model name, like bunnies.kills. Is this a bug or this is a feature ?
This has been explained numberous times : https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance
and in mhevery's video