Use ng-repeat's $index variable within an AngularJs expression - angularjs

I have an ng-repeat div that I need to use the $index variable to build out an expression. Consider the following:
<div ng-repeat="item in myItems track by $index">
This will throw an error: {{myItemId_{{$index}}.someProperty}}
</div>
If I have a $scope variable called "myItemId_0", how do I use $index within a curly braces {{}} expression? I have tried omitting the curly braces for the $index variable, this but this doesn't work either:
{{myItemId_$index.someProperty}}

Since it's a variable on scope, create a function that retrieves your property via bracket notation.
HTML:
{{getByIndex($index, "someProperty")}}
Controller:
$scope.getByIndex = function(index, someProperty) {
var propertyName = "myItemId_" + index;
return $scope[propertyName][someProperty];
}
However, the logical solution is to not access your properties in this way at all, but rather use item which is already available from your ng-repeat.
HTML:
div ng-repeat="item in betterItems track by $index">
{{item.someProperty}}
</div>
Controller:
$scope.betterItems = [];
angular.forEach(myItems, function (i) {
$scope.betterItems.push(createMappedItem(i));
}
Even if you have to map your collection to a new collection (or reuse the same if need be), you can still access that original item. It's cleaner and much easier to maintain. In addition, retrieval by index can be very dangerous if you start mutating the collection.

Related

ng-repeat one-time binding inheritance?

If I have an ng-repeat directive iterating through a list of lists via a one-time binding, does a nested ng-repeat through each of the inner lists also need a one-time binding, or is that redundant?
<div ng-repeat="list in ::$ctrl.lists">
<span ng-repeat="item in list">{{item}}</span>
</div>
Do I need to change the inner ng-repeat to item in ::list? $ctrl.lists will never change in any manner.
It is specify nowhere that is is inherit, event if you have bind the array, object inside the array can still change, so it is better to put '::' for the span loop too.
http://jsfiddle.net/vw2fjxys/64/
var a = [1,2,3]
setTimeout(function(){
console.log(2)
a.length = 2
$scope.$apply();
},3000)
$scope.lists = [a];
You can see on the exemple after 2 second that with :: it is not recalculated for the inner loop but without it is.

Confuse terms in Angular JS tutorial

I'm following a tutorial and I have difficulties to understand a certain sentence which talks about the problem that can occur when naming my properties in Angular:
It quietly tolerates all property access errors, including nested properties on nonexistent parents and out-of-bounds array access.
aliasing variables as a way to cope with variable shadowing
My english is good enough to know what is written, but I really don't understand what is "nested properties on nonexistent parents" and "aliasing variables as a way to cope with variable shadowing". I did some research, but couldn't clearly understand.
Could someone give me a clear explanation?
Suppose you have:
obj : {
first: {
second: [1, 2, 3]
}
}
Trying to do this:
obj.nonexistent.prop // Nested property on nonexistent parent
obj.first.second[1000] // Out of bound array access
Would throw an error in javascript, but Angular doesn't throw an error
For aliasing variables as a way to cope with variable shadowing, imagine this:
<div ng-controller="ItemsController">
<div ng-repeat="item in items" ng-init="outerCount = $index">
{{outerCount + 1}}. {{item.name}}
<div ng-repeat="item in item.items">
{{outerCount + 1}}.{{$index + 1}}. {{item.name}}
</div>
</div>
</div>
From here
In an ng-repeat, the $index variable changes to point at the current index of the loop each iteration. So if you have nested loops, you'll lose a reference to the outer $index variable. This is variable shadowing. To use the outer variable, you can use ng-init and set it to the $index outside. Now, you've aliased the outer $index variable to outerCount.
Hope this is clear!

Don't understand the syntactical inconsistencies in Angular

A few examples:
<img ng-src="{{some.thing}}">
<button ng-class="{active: data.checkThing()}">
<div ng-repeat="thing in things"></div>
As someone learning Angular, this is highly confusing. I never know whether I need { } or {{ }} or neither. Can someone explain what these mean and when to use which?
{{}}:
You use {{}} when you want to evaluate an expression, such as:
$scope variables, e.g:
$scope.myVar = "test";
And in HTML:
<div>{{myVar}}</div> will result in <div>test</div>
Basic calculations, such as: {{ 1 + 1 }}, {{ myVar + " add on" }}
Result of a function on the controller so it can be displayed
Etc..
{}:
Condition, used directives such as ng-class/ng-style. It means the left-hand side will be in effect if the expression on the right-hand side evaluates to true. The following expression means that the button will have the class "active", if data.checkThing() evaluates to true:
<button ng-class="{active: data.checkThing()}">
Neither:
When you want to address objects, like you do in the ng-repeat, you have to iterate on objects, like you do in C#/Javascript/etc..
So because you're working with the objects themselves, you don't need to evaluate anything, and simply address them as they are variables on your scope:
<div ng-repeat="item in items">...</div>
If you are using directives for instance, and you have an isolated scope and want to bind a variable in that scope to an instance, you will use no brackets:
<div myDirective item="myItem"></div>
And in the directive you can have a reference to that item by doing:
angular.module('app').directive('my-directive', function () {
return {
scope: {
item: "=" //references the object that exists in
//attribute `item` on the DOM element the directive is on
}
}
});
Single braces are used for directives accepting an object parameter, which may be more than one value. It is analogous to a JSON object.
For example:
ng-class="{active: true, highlight:true}"
Double braces are used to represent an AngularJS expression.
For example
<div>{{ item.name }}<div> (evaluates to string)
<div>{{ item.name == "Sam" }}<div> (evaluates to boolean)
No braces are used for directives accepting a single value argument.
For example, data binding:
ng-model="people.jim"
{active:data.checkThing()} signifies object creation-----you are constructing and actually passing an object to ng-class directive in the angularjs library.
{{some.thing}} signifies 2-way data binding with the actual ng-model that reflects the changes in model data as and when it occurs.
Hope it helps.If this answers your queries kindly accept this as answer or comment back if not.

Why does the ng-repeat directive create a child scope?

Question: Why does the ng-repeat directive create a child scope?
I don't understand why this is the case. Creating a new scope makes sense to an extent if you explicitly create a child controller, but why would it automatically create one for every ng-repeat?
I guess the reason it confuses me is because if you create a loop in JS, that doesn't mean the code outside the loop can't access any of the variables inside of it.
Example:
for(var x=0; x<10; x++) {
var y = x
}
alert(y);
From the docs (with my emphasis):
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.
Seems pretty straightforward, doesn't it?
The DOM is not javascript. The items in the collection you are ng-repeating over need to be bound to separate scopes that are bound to the repeated DOM elements and creates a tree of scopes that mirror structure of the page (somewhat)
ex, this markup:
<body ng-app="myapp">
<div ng-repeat="item in items">
<p>{{ item.name }}</p>
</div>
</body>
will produce something like the DOM on the left and scope on the right:
body rootScope
div scope
p
div scope
p
(somewhat approximate for illustration)
Javascript was written by a single person in 10 days. In almost every other language, loops create a separate scope and your code example wouldn't compile or you'd end up with null/none for y in your last line.

How can I filter to a single item in an array bing to it via an angular expression in markup?

I have a single element that I want bound to a single item in an array and ng-repeat doesn't seem applicable.
How can I do something like the following to bind to a single item in an array
<p class="bottomline">{{vehicle.Taglines[0].Tagline | $filter:{MarketId:$scope.MarketId}}</p>
Could you try this:
{{ (vehicle.Taglines | filter: {MarketId: MarketId})[0]["Tagline"] }}
Note, filter not $filter! And you have missed a bracket after the filter object argument!
I don't think it's possible but you can always write that logic in the Controller (and avoid putting so much logic in the template)
module('yourApp', []).controller(['$scope, $filter', function Controller($scope, $filter){
$scope.$watch('MarketId', function(marketId) {
$scope.tagLineFound = $filter('filter')($scope.vehicle.Taglines, marketId)[0];
});
}]);
HTML
<p class="bottomline">{{tagLineFound.Tagline}}</p>

Resources