AngularJS repeat duplicate error - javascript

Included is an object array being used in an ng-repeat call
And here is the error
For the love of me I cannot seem to get why angular is treating these as duplicates.
Any help is much appreciated.

Angular is telling you it doesn't know how to differentiate the items in your list, so you must tell it which field in your objects make it unique. Click here for more documentation on track by
To do this you need to add track by to your ng-repeat statement. You can specify any field on the object such as yid.
<div ng-repeat="item in items track by item.yid">
...
</div>
However, if you didn't have any fields that tracked uniqueness, you can also track by the index of the item in the list using $index.
<div ng-repeat="item in items track by $index">
...
</div>

Related

AngularJS ng-repeat

I have one form which reads data from web api, bellow dates I have two radio buttons All and Excluded, when Excluded is checked I want to show only records that have Check = 1 in it. I hope you understand me, is there any easy way to solve this in the view ? some ng-if condition or something like that.
You can use the ng-show/ng-hide property (https://docs.angularjs.org/api/ng/directive/ngShow) in your record. It would look something like:
<div ng-hide="exluded && check!=1">Your record</div>
The radio button should have as ng-model the excluded variable.
With this what you would achieve is tho show all the records always, except when the variable excluded is set to true and the check variable is not 1
You can use a filter pipe in your ng-repeat:
<div ng-repeat="record in records | filter: matches">
{{record.name}}
</div>
Here is a jsfiddle that uses that approach.

What is "track by" in AngularJS and how does it work?

I don't really understand how track by works and what it does.
My main goal is to use it with ng-repeat to add some precision.
Using track by to track strings & duplicate values
Normally ng-repeat tracks each item by the item itself. For the given array objs = [ 'one', 'one', 2, 'five', 'string', 'foo'], ng-repeat attempts to track changes by each obj in the ng-repeat="obj in objs". The problem is that we have duplicate values and angular will throw an error. One way to solve that is to have angular track the objects by other means. For strings, track by $index is a good solution as you really haven't other means to track a string.
track by & triggering a digest & input focuses
You allude to the fact you're somewhat new to angular. A digest cycle occurs when angular performs an exhaustive check of each watched property in order to reflect any change to the correspodant view; often during a digest cycle it happens that your code modify other watched properties so the procedure needs to be performed again until angular detects no more changes.
For example: You click a button to update a model via ng-click, then you do somethings (i mean, the things you wrote in the callback to perform when an user makes a click), then angular trigger digest cycle in order to refresh the view. I'm not too articulate in explaining that so you should investigate further if that didn't clarify things.
So back to track by. Let's use an example:
call a service to return an array of objects
update an object within the array and save object
after save service, depending on what the API returns, you may:
replace the whole object OR
update a value on the existing object
reflect change in ng-repeat UI
How you track this object will determine how the UI reflects the change.
One of the most annoying UXs I've experienced is this. Say you have a table of objects, each cell has an input where you want to in-line edit those objects' properties. I want to change the value, then on-blur, save that object while moving to the next cell to edit while you might be waiting on the response. So this is an autosave type thing. Depending on how you setup your track by statement, you may lose current focus (e.g. the field you're currently editing) when the response gets written back into your array of objects.
When you add track by you basically tell angular to generate a single DOM element per data object in the given collection.
You can track by $index if your data source has duplicate identifiers.
If you do need to repeat duplicate items, you can substitute the default tracking behavior with your own using the track by expression.
Example:
[{id:1,name:'one'}, {id:1,name:'one too'}, {id:2,name:'two'}]
Try to use the duplicate values in ng-repeat, you will get an error such as:
Error: ngRepeat:dupes Duplicate Key in Repeater
To avoid this kind of problems you should use track by $index. For example:
<ul>
<li ng-repeat="item in [1, 2, 3, 3] track by $index">
{{ item }}
</li>
</ul>
Here is how you would get $index in nested ng-repeat:
<div ng-repeat="row in matrix">
<div ng-repeat="column in row">
<span>outer: {{$parent.$index}} inner: {{$index}}</span>
</div>
</div>
Here are some resources that may help you:
track by $index documentation
ngRepeat documentation
2014 codelord.net article about ng-repeat performance and track by
You should use track by only if you need to go against the default behaviour of ng-repeat which is to remove duplicate items.
You can track the items using the scope property $index or specifying a custom function.
For instance:
<div ng-repeat="x in [42, 42, 43, 43] track by $index">
{{x}}
</div>
Display all values of the array (42 is displayed twice).
For reference: https://docs.angularjs.org/api/ng/directive/ngRepeat
Let's say, we have the following list:
<ul>
<li ng-repeat="item in items">
{{ item }}
</li>
</ul>
where, an item has the following structure:
{ 'id'=>id, 'name'=>name, 'description'=>description }
There is no problem whatsoever in this list until we wish to update it. For our own convenience we replace the list of items, with another updated list of items, as such:
items = newItems;
However, in this new list, few items change. Most items remain the same. Unfortunately Angular does not know how to identify our items and map them to the respective <li> elements, so it just deletes all elements and creates them again. This is extremely performance-costly in some cases and here is where track by comes in use.
By adding the track by clause to the element
<li ng-repeat="item in items track by item.id">
we are informing Angular, that the unique identifier of our items is item.id. Now Angular knows not to recreate all items, but only items with new ids, and only updates the rest. The performance improvement is significant in most cases. Also, personally, I like that I can monitor my items easier on my browser's developer tools, because they don't disappear every time I update them.

Dragula Angular2 update values using observable

I am writing a tree control using Angular2 and ng2-dragula (based on dragula). I am currently using something very similar to the example nested repeat example here. I have no problem loading in my list and getting drag and drop to work as expected. What I need to do is click a button, go back to the http service get fresh data, then update data on page. I know that I can use javascript to find the id and update that way but it doesn't seem like the correcy way to handle it.
Here is my component code - All this does is just over write the view data and completey undo any drags changed etc (im sure that is by design). I have tried a bunch of different different ways to use dragulaModel, but no matter what the whole component is re-rendered no matter what happens. I need to be able just to update the child text
I'm new to angular and want to make sure i follow the correct patterns
item.component.ts
loadItems(){
this._itemsService.getIems();
}
reloadIems(){
this._itemsService.getItems();
}
item.component.html
<div *ngIf="items">
<div class="holder">
<div *ngFor="let item of items | async">
<div (click)="checkCollapsed(item.text)" >
<div *ngIf='item.children' [dragula]='"first-bag"'>
<div *ngFor='let child of item.children' class="item">
<span class="handle">+</span><span id='{{child.id}}' [innerHtml]="child.text"> </span>
</div>
</div>
</div>
</div>
</div>
</div>
Edit
If I add a .subscribe to loadItems() then update this.items manually like this.items[0].children[0].text = "1000" in reloadItems() it works as i would like. Should I be manually updating the whole object this way? Seems like a hack
**Edit 2 **
I managed to get it to work by subscribing in the loadItems() then comparing the 2 objects in reloadItems, then making any necessary changes there. I think due to the subscribe it is autoupdating. Can anyone confirm if im doing it wrong/right (working plnkr here, leaving out the angular dependencies)

Check options in select in Angular

In my Angular app, I have 2 select inputs as follows.
<select ng-model="selectedItem.name" ng-options="item.name for item in items" />
<select ng-model="selectedSubItem.name" ng-options="subItem.name for subItem in selectedItem" />
The options in the second select is dependent upon the option selected in the first select. Once the form is submitted, the id's from selectedItem and selctedSubItem are extracted and submitted to the API. This works perfectly for creating a new record.
While retrieving an existing record, the API returns the id's of selectedItem and selectedSubItem. I use resolve in my app.js to populate the available options in both the selects.
I'm a bit confused on how to reuse the same view to check the options in both selects based on the id's retrieved from the API in the same view.
If I understand correctly, after your route resolves on the update view you have:
selectedItem.id
selectedSubItem.id
items collection
...and each item has a nested collection of subItems.
First, you need to loop through each item in your items collection until you find the one that matches selectedItem.id. Then, you need to loop through each of that item's subItems until you find the one that matches selectedSubItem.id.
If you're using Underscore.js, the findWhere method works great for this. I'm sure lodash has something similar.
Does that answer your question?

AngularJS - Getting a ng-repeated DOM element's data model

This seems super simple. but I can't figure out how its supposed to work.
I have a unordered list of items, which are built from an ng-repeat, which itself is based on a collection of data objects.
So in the DOM (in Jade):
div#projectListing
ul.sidebar-listing
li.sidebar-header Theatrical Projects
li.sidebar-item(ng-click="loadProject()", ng-repeat="project in theatricalProjects | orderBy:'title'") {{ project.title }}
Now I have a nice listing of items in my sidebar, but I want to be able to click one of these elements, and get the full model its tied to - project. What is the 'angular' method to handle this? Do I need to create a custom directive instead of using ng-click()? Do I need to assign an ng-model to each listing with a unique name?
Thanks.
I am assuming you need the respective project inside loadProject when clicked. You could just pass in the project as an argument to loadProject and accept it in your function definition:
li.sidebar-item(ng-click="loadProject(project)", ng-repeat="project in theatricalProjects | orderBy:'title'");
Basically ng-repeat will create a child scope for all the repeated element (li.sidebar) so all of those child scope will have the propery project associated to them and also from inside the method loadProject() you should be also able to access it using this.project

Categories

Resources