Confuse terms in Angular JS tutorial - angularjs

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!

Related

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

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.

Looping in AngularJS not working

I'm trying a simple AngularJS looping using 'ng-repeat' directive as below :
<div ng-app="" ng-init="numbers=[1,3,5,2]">
<ul>
<li ng-repeat="item in numbers">{{ item }}</li>
</ul>
</div>
The result of this is as below, which is perfect
1
3
5
2
However, when I change the 'numbers' array like this
<div ng-app="" ng-init="numbers=[1,3,5,2,2]">
being the rest as is, it does not work.
The only change I have made is that I've added one more item in the 'numbers' array '2'. The issue I figured out is whenever an item is repeated in the array ( '2' in this case ), the problem occurs.
The console log I noticed is like below
Error: [ngRepeat:dupes] http://errors.angularjs.org/1.3.14/ngRepeat/dupes?p0=item%20in%20numbers&p1=number%3A2&p2=2
at Error (native)
at http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js:6:417
at http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js:232:494
at Object.fn (http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js:122:53)
at l.$get.l.$digest (http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js:123:138)
at l.$get.l.$apply (http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js:126:58)
at http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js:17:479
at Object.e [as invoke] (http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js:36:315)
at d (http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js:17:400)
at tc (http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js:18:179)
Also, if the array is of string type values, the same problem persists too.
For example, <div ng-app="" ng-init="names=['Bishnu', 'Sagar', 'John', 'Bishnu']">
in this case also I'm facing the same issue.
This behavior of AngularJS is very strange.
Does anyone know the reason, and how to resolve?
Try this...
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.
ngRepeat makes the corresponding changes to the DOM
When an item is added, a new instance of the template is added to the DOM.
When an item is removed, its template instance is removed from the DOM.
When items are reordered, their respective templates are reordered in the DOM.
By default, ngRepeat does not allow duplicate items in arrays. This is because when there are duplicates, it is not possible to maintain a one-to-one mapping between collection items and DOM elements.
If you do need to repeat duplicate items, you can substitute the default tracking behavior with your own using the track by expression
<div ng-repeat="n in [42, 42, 43, 43] track by $index">
{{n}}
</div>
Refer:https://docs.angularjs.org/api/ng/directive/ngRepeat
As per the Angular Docs Duplicates are not allowed. You need to use 'track by' expression to specify unique keys.
Created this Plnkr for your reference
<div ng-app="" ng-init="numbers=[1,3,5,2,2]">
<ul>
<li ng-repeat="item in numbers track by $index">{{ item }}</li>
</ul>
</div>
You need to use track by $index to iterate through duplicate entry as well.
you can try like this
<div ng-repeat="value in [4, 4] track by $index">{{value}}</div>

Confused by AngularJS ng-repeat syntax

What is the meaning of the singular/plural syntax in, say, ng-repeat="product in store.products"?
Singular/plural is used just for common sense and code readability - it doesn't have to be singular/plural. You can do
ng-repeat="whatever in store.products"`
and then have the whatever object available inside (like: <img ng-src="{{whatever.images[0]}}" />).
In your case, store.products can't be changed since it refers to an actual object, while product is a completely custom name to be used in the repeat loop.
Fairly common in programming. Like the other answer said, it's similar to the for..in syntax.
This is essentially the same syntax as a Javascript for...in loop. It means for someTempVar in someArrayOrObject.
The directive ng-repeat="product in products" creates a new variable product that you can reference inside your template. There is no singular/plural interpolation going on.

Angular: Getting list with ng-repeat with dividers / separators

Still pretty new with Angular, just finding my way around.
I'm using ng-repeat to output an alphabetised list of names. I'd like to add dividers within this list that act as labels.
Example:
--------
A
--------
Author 1
Author 2
--------
B
--------
Author 3
Author 4
etc
My thinking is to use nested ng-repeats to loop through the alphabet, getting an object with the authors for that specific letter with a second ng-repeat. Here's what I have so far:
<div data-ng-repeat="letter in alphabet">
<div class="item item-divider">
{{letter}}
</div>
<ul>
<li data-ng-repeat="speaker in GetSpeakers(letter)" type="item-text-wrap" href="#/speaker/{{speaker.ID}}">
{{speaker.title}}
</li>
</ul>
</div>
Controller code:
.controller('SpeakersCtrl', function($scope, $routeParams, StorageHandler) {
$scope.GetSpeakers = function(letter) {
// Get list of authors for that letter
console.log('test '+letter);
};
$scope.alphabet = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'];
})
Fiddle: http://jsfiddle.net/t6Xq8/
I have a couple of questions.
In general, is using a nested ng-repeat a good approach for this
problem, or does Angular have built-in specifically for this purpose? Some sources also say using a function in ng-repeat is a bad idea. But it does work so I'm confused as to why I shouldn't use this.
When looking at the console, GetSpeakers gets called twice in this example and I can't figure out why?
How should I return an object to the scope within the GetSpeakers function, while preventing overloading the $scope?
I think you can do this in a much simpler way without:
having to do a nested ng-repeat
writing out the whole alphabet
needing to handle letters which have no author
manipulating your original array of objects or copying it
writing much fewer lines of code
by using $index.
so that would look something like this:
<div ng-repeat="speaker in speakers | orderBy : 'name'">
<div class="item item-divider" ng-if="firstLetter(speaker.name) != firstLetter(speakers[$index-1].name)">
{{firstLetter(speaker.name)}}
</div>
{{speaker.name}}
</div>
This would need a simple function to get the first letter such as:
function firstLetter(name) {
return name && name.charAt(0);
}
So what this does is it compares the first letter of whatever you passed to the previous object's first letter and if they are different it adds the divider with that letter. Pretty clean and simple :)
Check out this working JsFiddle
You can obviously improve on that code to handle upper/lowercase (i.e. always capitalize before comparing) as well as extract the comparison into a function for cleaner code.
Likely better to map the data into a single object where the object keys are the letter. I'll assume you have objects like:
{id:123, firstName:'Frank',lastName :'Enstein'}
and want the letter to represent last names
var tmp={};
for(i=0;i<authourArray.length;i++){
var letter=authourArray[i].lastName.charAt(0);
if( tmp[ letter] ==undefined){
tmp[ letter]=[]
}
tmp[ letter].push( authourArray[i] );
}
/* likely want to loop over all the arrays now and sort unless already sorted from server*/
$scope.repeaterObject=tmp;
Now in markup will ng-repeat all the letters in the object keys, and within that loop, do an ng-repeat of the authors array for that letter
<div data-ng-repeat="(letter, authors) in repeaterObject">
<div class="item item-divider">
{{letter}}
</div>
<ul>
<li ng-repeat="author in authors">{{author.firstName}} {{author.lastName}}</li>
</ul>
</div>
Resulting object will look like:
{
A:[.....],
......
E:[ {id:123, firstName:'Frank',lastName :'Enstein'}, /* other E author objects*/ ],
......
}
I believe that using nested ng repeats is perfectly fine. Im not familiar with a better way that angularjs provides to iterate over multi dimensional arrays/data structures.
The reason you should avoid using functions in ng repeats is that angularjs will call that function each time it will create the element in the repeat directive. As charlie suggested above it would be better to order the authors once and use the resulting array each time, rather than ordering the authors each time you display them. This has the added benefit of being able to reuse the array

AngularJS not able to include markup in IDs?

I have an array of 3 items and want them to be "ng-repeated"
<li ng-repeat="item in obj.items id="testobj{{testobj.number}}">
</li>
When I look at the page, it appears that the id of the "li" is just "testobj" for all 3 items and not testobj1 testobj2 testobj3 like I was expecting. What is the issue?
Your ng-repeat attribute is missing a final ".
The {{ }} binding is probably coming back with no data and so being treated as if it was an empty string. I see no reference to testobj (the scope variable) anywhere outside of your binding. Is this defined, or should your id read id="testobj{{item.number}}" or something similar?

Resources