Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

<input> loses focus when ng-model references ng-repeat array directly #13327

Closed
djmarcus1 opened this issue Nov 18, 2015 · 10 comments
Closed

<input> loses focus when ng-model references ng-repeat array directly #13327

djmarcus1 opened this issue Nov 18, 2015 · 10 comments

Comments

@djmarcus1
Copy link

I have a simple <input> inside a <div ng-repeat...">

As soon as I type a character the <input> focus is lost. To continue typing I first have to click inside the input area for each character I want to enter.

The essence of the ng-repeat code that shows the problem is:

<div ng-repeat="item in items">
    <input ng-model="items[$index]">
</div>

However, an <input> inside the ng-repeat scope but which does not directly reference the ng-repeat array, works fine - I can enter data all day long. For example:

<div ng-repeat="item in items">
    <input ng-model="george">
</div>

Note: the problem is clearly related to the the ng-model referring directly to the $index-th element in the items array. My guess is that a $watch on the collection is triggered and that causes the loss of focus.

Here's a codepen that shows the problem:
http://codepen.io/djmarcus/pen/bVZGvj

Here's a distillation:

    <div ng-controller="myCtrl">
        <div ng-repeat="item in items">
          <div>This fails</div>
          <div layout="row" layout-align="start center">
            <input ng-model="items[$index]"  label=""> 
          </div>
        </div>

        <div ng-repeat="item in items">
          <div>This works</div>
          <div layout="row" layout-align="start center">
            <input ng-model="george"  label=""> 
          </div>
        </div>

        <div>This works (its outside of ng-repeat) </div>
        <input ng-model="harvey" label="">
    </div>
@Narretz
Copy link
Contributor

Narretz commented Nov 18, 2015

I think there's a related issue floating around somewhere, but that was because the repeated elements were moved, which caused them to lose the focus. Since in this example, there should happen no DOM reordering, this needs an investigation.

@gkalpak
Copy link
Member

gkalpak commented Nov 18, 2015

I think this is expected behaviour.

As @Narretz mentioned, your 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).

There are several ways to achieve the desired behaviour:

  1. Use the local item variable created in ngRepeat's child scope: Actually, not working for 2-way binding in this case. Use options 2 or 3.

    <div ng-repeat="item in items">
      <input type="text" ng-model="item" />
    </div>
  2. Use track by $index (if it's appropriate for your usecase, i.e. you know the items won't be reordered):

    <div ng-repeat="item in items track by $index">
      <input type="text" ng-model="items[$index]" />
    </div>
  3. Wrap your strings into objects. E.g. items = [{value: 'string1'}, {value: 'string2'}, ...]:

    <div ng-repeat="item in items">
      <input type="text" ng-model="items[$index].value" />
    </div>

You can see all 3 solutions in action in this demo pen.

@Narretz
Copy link
Contributor

Narretz commented Nov 18, 2015

Thanks @gkalpak. You are right, the behavior is expected.

@Narretz Narretz closed this as completed Nov 18, 2015
@djmarcus1
Copy link
Author

I ended up using option #3. Thanks for clarifying the issue (in hindsight its obvious).

It would help if there was some documentation on Angular 'gotcha' issues.. this one is a prime candidate for that list.

@gkalpak
Copy link
Member

gkalpak commented Nov 19, 2015

TBH, I would expect you'd follow option #1. It's quite typical to use it like this - I wonder why can't you use it (if there's a specific reason).

@djmarcus1
Copy link
Author

Option #1 (as coded in the codepen) is what I was using originally. It does not work (at least when I try it in the codepen) for the reasons you gave above... perhaps you meant something different in the codepen?

Nevertheless, option #3 is most closely related to my code. The array that I ng-repeat on actually is an array of objects (not simple strings) where my interest is in one of the properties.. so option #3 is a good match.

@gkalpak
Copy link
Member

gkalpak commented Nov 20, 2015

[Option #1] does not work (at least when I try it in the codepen)

Ooops, you are right. Option #1 is not suitable for 2-way binding via ngModel.
(I updated my previous comment to avoid confusion for future readers.)

@bkottapally
Copy link

approach #2 by @gkalpak fixed the problem. I needed this in Angular 2 though. Thanks for the solution.

@abhi9bakshi
Copy link

abhi9bakshi commented Aug 11, 2017

If you stumbled upon this thread and are looking for a solution in Angular 2, the solution is quite simple.
Instead of two way binding with the array, only bind the property and handle the change event using 'change' event instead of 'input'

Example:

// Instead of writing
<div *ngFor="let param of params.value; let id = index;">
    <input type="text" placeholder="0" [(ngModel)]="params.value[id]">
</div

// Write
<div *ngFor="let param of params.value; let id = index;">
    <input type="text" placeholder="0" [ngModel]="params.value[id]" (change)="onValueUpdate($event, id)">
</div>

and update the value in onValueUpdate function.

Since the change event triggers only when you blur from input, your will not face the error anymore.

@Sathish-Krish
Copy link

thanks @gkalpak
it worked for me. I ended up with option3. it worked for angular 4 also

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

6 participants