Angular 2 - trackBy function, what does it really do? - javascript

I was under the impression that trackBy function is used only when trying to optimize the performance of *ngFor, so that if something changes, the DOM doesn't have to be rebuild.
However, recently, I came across a situation when trackBy actually fixed a wrong behavior.
Take this for example: https://plnkr.co/edit/nRgdwoiKAMpsbmWaoMoj?p=preview
Focus on Hobbies section, especially HTML:
<div>
<h2>Hobbies</h2>
<div *ngFor="let h of user.hobbies; trackBy:customTrackBy; let i = index">
#{{i}} - {{h | json}}<br />
<input [(ngModel)]="h.name" name="hobby_name_{{i}}" /> <br /><br />
<select [(ngModel)]="h.type_id" name="type_{{i}}">
<option *ngFor="let t of types" [value]="t.id">{{t.type}}</option>
</select>
<br />
<br />
<button class="btn btn-warn" (click)="remove(i)">Remove</button>
<br /><br />
</div>
</div>
I had to explicitly define this part: trackBy:customTrackBy in the first *ngFor. If trackBy is removed, and the following steps are performed:
remove the first item
add a new item
In this case, the inputs of the first item get replaced with the content of the second item (both fields have the same content), however, the values in the model are correct.
trackBy solves this issue, but why?
I would really appreciate any kind of explanation. If this is not the right place to ask this kind of questions please redirect me to the correct one. Thanks.
update
Here's an example of the wrong behavior: https://plnkr.co/edit/u8YajKfHcPiVqY0WcJt7?p=preview remove the first item (cycling) and add a new item (add button) and see how both values get the same default value (BF will get replaced by "default value" even though the model stays correct).

*ngFor by default tracks items by object identity.
If you have primitive values like an array of strings, and use them in
<div *ngFor="let item of items; let i=index">
<input [(ngModel)]="item" name="item{{i}}">
</div>
and you edit one item, then *ngFor gets in trouble, because the identity of the edited item has changed.
With ngForTrackBy you can tell *ngFor to track the item by index, then above code will work fine when you edit fields.
Another use case is when you want *ngFor to track items by some custom object id property instead of the object identity.

Related

Angularjs object-property

I am using this Angular Module but I can not get the work with nested data.
Here is my PLUNKR my output shows country when using object-property="country but when I try to show only states, it doesn't work.
<div class="mrg-top50">
<label>1. Autocomplete field - options list as array of objects</label>
<multiple-autocomplete ng-model="skills" object-property="country" suggestions-arr="skillsList"></multiple-autocomplete>
<label>Selected Model Value : <br />
</label>
{{skills}}
</div>
I could do it in your fiddle like this:
<multiple-autocomplete ng-model="skills" object-property="labels" suggestions-arr="skillsList[0].states"></multiple-autocomplete>
Though this is really dependent on the [0] index , which means only useful when you have just one element in the given array like in the given example.

Cannot retrieve parent id on child v-for in Edge

I have a form which contains a parent element and a child element, in every browser I can retrieve this value but in MS Edge I am unable to get the key from the parent element. Is anyone able to shed light on this issue?
<div v-for="(levelKey, level) in levels">
#{{ level.name }}
<div v-for="(currencyKey, currency) in currencies">
<input type="text" name="levels[#{{ level.id }}][currency.id]" v-model="...">
</div>
</div>
You can write your type attribute as a bound attribute using the :type syntax. See: https://vuejs.org/guide/syntax.html
Then you don't need the moustache braces (#{{ ... }}) when you're already inside an expression. You can just write:
<input :name="levels[level.id][currency.id]" v-model="...">
or even simpler (since you're already looping through levels):
<input :name="level[currency.id]" v-model="...">
EDIT
I created a JSFiddle with some mock data (probably not exactly the same as yours, though) and tried it in MS Edge. It worked fine:
Maybe you forgot to copy a part of the code?

Ember.js input checkbox in table

I have lots of models and show them in tables. When user needs to do something with several models, we need to give him ability to choose rows.
How can I implement it with checkboxes? Of course I don't want to create special field on my models for every table.
This is simple example.
https://ember-twiddle.com/0b8f429f6ad3e0572438
My tries were:
{{input type='checkbox' checked=model.someNotExistedField}}
But in this case input just doesnt work.
And:
<input type="checkbox" {{action marked model}} checked={{in-arr record selectedItems}} />
In second example I've tried to keep selected ids in an array on context. But id doesnt work too.
There are a few steps to solving this problem, which you have not shown in your code examples.
you dont need to worry about binding a checked value on the checkbox.. it can manage its own internal state, and you can take advantage of it when selecting an item... a native <input> should be fine
<input type="checkbox">
You will need an action (preferably a closure action) that handles what to do when a record is selected
onchange={{action (action "valueHasChanged" item) value="target.checked"}}
You will need an array to store the "selected items"
this.selectedItems = [];
I put together a twiddle as one example of how these pieces fit together.
(This answer should be valid with ember version 1.13.0 and up)
I'am guessing your model is an array of rows.
So try adding a chked (boolean) property to the model structure so you now have one for each row and bind it to the respective checkbox.
I finished with such technic:
components/action-based-checkbox.hbs
{{#if checked}}
<input type="checkbox" {{action changed}} checked="checked" />
{{else}}
<input type="checkbox" {{action changed}} />
{{/if}}
In context we have array with selected items, for instance "selectedItems"
In context we have an action, that manages array
//2 and 3 steps example
actions:{
marked(id){
this.selectedItems.push(id);
var uniq = this.selectedItems.uniq();
this.set('selectedItems', uniq);
},
selectedItems:[],
4.next I put the component to my cell
{{inputs/action-based-checkbox changed=(action marked record.id) checked=(in-arr record.id selectedItems)}}
in-arr my "in_array?" helper, ember 2.3

Cascading Dropdowns using AngularJS

A merchant has multiple branches.
When selecting a merchant I'm trying to make another dropdown list the data from merchant.branches.
Doing the below does not seem to be resolving the problem:
<label>Merchant:</label>
<select ng-if="merchants" ng-model="merchant" ng-options="merchant.name for merchant in merchants track by merchant.id"></select>
<span ng-if="!merchants">Listing merchants, please wait...</span>
<br />
<label>Branch:</label>
<select ng-if="merchant.branches" ng-model="device.branch" ng-options="branch.name for branch in merchant.branches track by branch.id"></select>
<span ng-if="!merchant.branches">Listing branches, please wait...</span>
<pre>
{{ merchant.branches }}
</pre>
Please note that the data is correct and Merchants return properly with their respective branches.
SOLVED
Using a different method (By calling ng-change on the first dropdown and then populating the second dropdown). However, is it possible to achieve this without writing any controller code?
I have to guess at how your controller works, but you might have luck with this:
<label>Branch:</label>
<select ng-if="merchant.branches"
ng-model="device.branch"
ng-options="branch.name for branch in merchants[merchant.id].branches track by branch.id"></select>

How to manipulate the type of DOM while keeping the content in AngularJS?

I want to display a list of things and let users edit them.
The list is generated using ng-repeat. At first when it was displayed, it should be in the form of pure texts. But when the user pushed the corresponding edit button, it should be changed into an input textfield, with the contents unchanged. When the user submits the form, the data is saved and the input should be changed back to pure texts.
Is this compatible with the Angular way of thinking? If so, how do I realize it? If not, what is the correct way to realize the idea in AngularJS?
Something like this would probably work:
<ul>
<li ng-repeat='item in items'>
<span ng-hide='item.editing'>item.value</span>
<input type='text' ng-show='item.editing' ng-model='item.value' />
<button ng-click='item.editing = !item.editing'>Edit</button>
</li>
</ul>
Then in your submit action set item.editing = false for every item in items
other way or way that i prefer with angular js is to keep a track of current item on scope, this works better if you the fields being edited are in large number
$scope.currentitem;
setting current item equal to the item tha's being edited
<button ng-click='currentitem = item'>Edit</button>
Now you can have an form filled in like
<input type='text' ng-model='currentitem.value' />

Categories

Resources