Angular2 Dynamic input field lose focus when input changes

Elbbard picture Elbbard · Feb 19, 2017 · Viewed 23.9k times · Source

I'm making a dynamic form. A Field has a list of values. Each value is represented by a string.

export class Field{
    name: string;
    values: string[] = [];
    fieldType: string;
    constructor(fieldType: string) {this.fieldType = fieldType;}
}

I have a function in my component which adds a new value to the field.

addValue(field){
    field.values.push("");
}

The values and the button are displayed like this in my HTML.

<div id="dropdown-values" *ngFor="let value of field.values; let j=index">
    <input type="text" class="form-control" [(ngModel)]="field.values[j]" [name]="'value' + j + '.' + i"/><br/>
</div>
<div class="text-center">
    <a href="javascript:void(0);" (click)="addValue(field)"><i class="fa fa-plus-circle" aria-hidden="true"></i></a>
</div>

As soon as I write some text in an input of a value, the input loses focus. If I add many values to a field and I write a character in one the values input, the input lose focus and the character is written in every input.

Answer

AJT82 picture AJT82 · Feb 19, 2017

This happens when the array is a primitive type, in your case a String array. This can be solved by using TrackBy. So change your template to match the following:

<div *ngFor="let value of field.values; let i=index; trackBy:trackByFn">
    <input type="text" [(ngModel)]="field.values[i]"  /><br/>
</div>
<div>
    <button (click)="addValue(field)">Click</button>
</div>

and in the ts file add the function trackByFn, which returns the (unique) index of the value:

trackByFn(index: any, item: any) {
   return index;
}

This is a link about the same issue, except the issue is for AngularJS, but the problem corresponds yours. Most important excerpt from that page:

You are repeating over an array and you are changing the items of the array (note that your items are strings, which are primitives in JS and thus compared "by value"). Since new items are detected, old elements are removed from the DOM and new ones are created (which obviously don't get focus).

With TrackBy Angular can track which items have been added (or removed) according to the unique identifier and create or destroy only the things that changed which means you don't lose focus on your input field :)

As seen in the link you can also modify your array to contain objects which are unique and use [(ngModel)]="value.id" for example, but that's maybe not what you need.