Difficulty with ng-model, ng-repeat, and inputs

161,825

Solution 1

This seems to be a binding issue.

The advice is don't bind to primitives.

Your ngRepeat is iterating over strings inside a collection, when it should be iterating over objects. To fix your problem

<body ng-init="models = [{name:'Sam'},{name:'Harry'},{name:'Sally'}]">
    <h1>Fun with Fields and ngModel</h1>
    <p>names: {{models}}</p>
    <h3>Binding to each element directly:</h3>
    <div ng-repeat="model in models">
        Value: {{model.name}}
        <input ng-model="model.name">                         
    </div>

jsfiddle: http://jsfiddle.net/jaimem/rnw3u/5/

Solution 2

Using Angular latest version (1.2.1) and track by $index. This issue is fixed

http://jsfiddle.net/rnw3u/53/

<div ng-repeat="(i, name) in names track by $index">
    Value: {{name}}
    <input ng-model="names[i]">                         
</div>

Solution 3

You get into a difficult situation when it is necessary to understand how scopes, ngRepeat and ngModel with NgModelController work. Also try to use 1.0.3 version. Your example will work a little differently.

You can simply use solution provided by jm-

But if you want to deal with the situation more deeply, you have to understand:

  • how AngularJS works;
  • scopes have a hierarchical structure;
  • ngRepeat creates new scope for every element;
  • ngRepeat build cache of items with additional information (hashKey); on each watch call for every new item (that is not in the cache) ngRepeat constructs new scope, DOM element, etc. More detailed description.
  • from 1.0.3 ngModelController rerenders inputs with actual model values.

How your example "Binding to each element directly" works for AngularJS 1.0.3:

  • you enter letter 'f' into input;
  • ngModelController changes model for item scope (names array is not changed) => name == 'Samf', names == ['Sam', 'Harry', 'Sally'];
  • $digest loop is started;
  • ngRepeat replaces model value from item scope ('Samf') by value from unchanged names array ('Sam');
  • ngModelController rerenders input with actual model value ('Sam').

How your example "Indexing into the array" works:

  • you enter letter 'f' into input;
  • ngModelController changes item in names array => `names == ['Samf', 'Harry', 'Sally'];
  • $digest loop is started;
  • ngRepeat can't find 'Samf' in cache;
  • ngRepeat creates new scope, adds new div element with new input (that is why the input field loses focus - old div with old input is replaced by new div with new input);
  • new values for new DOM elements are rendered.

Also, you can try to use AngularJS Batarang and see how changes $id of the scope of div with input in which you enter.

Solution 4

If you don't need the model to update with every key-stroke, just bind to name and then update the array item on blur event:

<div ng-repeat="name in names">
    Value: {{name}}
    <input ng-model="name" ng-blur="names[$index] = name" />
</div>

Solution 5

I just updated AngularJs to 1.1.2 and have no problem with it. I guess this bug was fixed.

http://ci.angularjs.org/job/angular.js-pete/57/artifact/build/angular.js

Share:
161,825

Related videos on Youtube

Nick Heiner
Author by

Nick Heiner

JS enthusiast by day, horse mask enthusiast by night. Talks I've Done

Updated on July 08, 2022

Comments

  • Nick Heiner
    Nick Heiner almost 2 years

    I am trying to allow the user to edit a list of items by using ngRepeat and ngModel. (See this fiddle.) However, both approaches I've tried lead to bizarre behavior: one doesn't update the model, and the other blurs the form on each keydown.

    Am I doing something wrong here? Is this not a supported use case?

    Here is the code from the fiddle, copied for convenience:

    <html ng-app>
        <head>
            <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.2.1/css/bootstrap-combined.min.css" rel="stylesheet">
        </head>
        <body ng-init="names = ['Sam', 'Harry', 'Sally']">
            <h1>Fun with Fields and ngModel</h1>
            <p>names: {{names}}</p>
            <h3>Binding to each element directly:</h3>
            <div ng-repeat="name in names">
                Value: {{name}}
                <input ng-model="name">                         
            </div>
            <p class="muted">The binding does not appear to be working: the value in the model is not changed.</p>
            <h3>Indexing into the array:</h3>
            <div ng-repeat="name in names">
                Value: {{names[$index]}}
                <input ng-model="names[$index]">                         
            </div>
            <p class="muted">Type one character, and the input field loses focus. However, the binding appears to be working correctly.</p>
        </body>
    </html>
    

    • Mark Rajcok
      Mark Rajcok over 11 years
      This is very similar to stackoverflow.com/questions/12977894/…, but the 2nd approach is new here, so it is not exactly a duplicate. I provided a detailed answer (similar to Artem's) there, explaining how ng-repeat child scopes work.
    • vucalur
      vucalur over 10 years
      Since it cost me reasonable amount of googling before finally finding this thread, may I rename it to sth like: "Parent Model not updated from ngRepeat inputs" or "Model not updated when using ngRepeat" or "ngRepeat inputs not bound to (parent) model"? Maybe you have better ideas for the title ?
  • Mark Rajcok
    Mark Rajcok over 11 years
    Thanks for the first hyperlink, as it explains the blur/loss of focus/flicker issue. I always wondered why that happened.
  • Mark Rajcok
    Mark Rajcok over 11 years
    thanks for the 1.0.3 explanation. It is interesting that in 1.0.3, the "bind directly" case, the input field appears to be broken, in the sense that it doesn't appear to accept any changes/typed input (at least in Chrome). I'm sure we'll see quite a few SO posts about this in the near future :) I suppose this new way is better, as it will be more obvious that something is wrong.
  • Daniel Gruszczyk
    Daniel Gruszczyk almost 10 years
    thanks for that, helped alot. Still, binding to primitives should be there imo...
  • Stefanos Chrs
    Stefanos Chrs over 9 years
    old post but thnx, took me some time to find the 'not changing the model inside ngRepeat' problem and it was your advice not to bind to primitives
  • Dan Ochiana
    Dan Ochiana almost 9 years
    did the job; no more reference loss
  • Andrea
    Andrea almost 8 years
    I did search so long for that.... so easy... just so hidden. Give this as the answer!
  • z0r
    z0r almost 8 years
    Also: Don't modify the entire list while typing - a trap I fell into. I was watching the collection for changes, and replacing it with an identical copy - so even though I wasn't binding to primitives, the elements were being recreated.
  • Alex Coroza
    Alex Coroza over 7 years
    oooooh, so adding "track by $index" in ng-repeat also fixes the "flickering" issue
  • Draško Kokić
    Draško Kokić almost 7 years
    Why not using ng-model="names[$index]" ... I know it's a workaround, but it works ;-)