What is the angularjs way to databind many inputs?

58,506

Solution 1

You'll have better luck if your list is an array of objects (as opposed to an array of primitives). This works fine even though a new scope is created with ng-repeat:

<div ng-repeat="item in list">
    <label>Input {{$index+1}}:</label>
    <input ng-model="item.value" type="text"/>
</div>

with a controller of:

function TestController($scope) {
    $scope.list = [ { value: 'value 1' }, { value: 'value 2' }, { value: 'value 3' } ];
}​

See this fiddle as an example.

On the other hand if you are trying to bind to an array of strings the new scope will cause a problem as the values you are modifying will not be tied to the original array string primitives (as in this fiddle example).

Solution 2

The reason that databinding to a primitive "item" doesn't work is because of the way ng-repeat creates the child scopes for each item. For each item, ng-repeat has the new child scope prototypically inherit from the parent scope (see dashed lines in picture below), and then it assigns the item's value to a new property on the child scope (red items in picture below). The name of the new property is the loop variable's name. From the ng-repeat source code:

childScope = scope.$new();
...
childScope[valueIdent] = value;

If item is a primitive, the new child scope property is essentially assigned a copy of the primitive's value. This child scope property is not visible to the parent scope, and changes you make to the input field are stored in this child scope property. E.g., suppose we have in the parent scope

$scope.list = [ 'value 1', 'value 2', 'value 3' ];

And in the HTML:

<div ng-repeat="item in list">

Then, the first child scope would have the following item property, with a primitive value (value 1):

item: "value 1"

ng-repeat with primitives

Because of the ng-model databinding, changes you make to the form's input field are stored in that child scope property.

You can verify this by logging the child scope to the console. Add to your HTML, inside the ng-repeat:

<a ng-click="showScope($event)">show scope</a>

Add to your controller:

$scope.showScope = function(e) {
    console.log(angular.element(e.srcElement).scope());
}


With @Gloopy's approach, each child scope still gets a new "item" property, but because list is now an array of objects, childScope[valueIdent] = value; results in the item property's value being set to a reference to one of the array objects (not a copy).

ng-repeat with objects

Using the showScope() technique, you'll see that the child scope item property's value references one of the array objects -- it is no longer a primitive value.

See also don't bind to primitives in ng-repeat child scopes and
What are the nuances of scope prototypal / prototypical inheritance in AngularJS? (which contains pictures of what the scopes look like when using ng-repeat).

Solution 3

I found an interesting way of doing this, and it's allowing me to work on an array of primitives.

I'm using AngularJS 1.2.1, which is the only version I can make this work in.

HTML:

<div ng-repeat="item in list">
    <label>Input {{$index+1}}:</label>
    <input ng-model="item" type="text" ng-blur="editItem($index, item)"/>
</div>

JavaScript:

$scope.editItem = function(idx, eItem) {
    $scope.list[idx] = eItem;
};

Link: http://jsfiddle.net/bxD2P/10/ (thank you to Gloopy for the starter Fiddle)

I'm sure there are easy ways of poking holes in how this works, and I would love to hear them. It would allow me to shore up my code.

Solution 4

Consider using the ngList directive https://docs.angularjs.org/api/ng/directive/ngList

Solution 5

Here's a way to do it. I used textareas and a different structure to my repeaters, but the main concept is:

  • display a simple value based on index. (not bound)
  • on blur update the model
  • on model update re-render

It is essentially, faking the binding.

Working fiddle - http://jsfiddle.net/VvnWY/4/

The html:

  <script type="text/ng-template" id="textareas.html">
    <textarea ng-if="strings" ng-repeat="str in strings" ng-blur="blur( $event, $index )">{{strings[$index]}}</textarea>
  </script>

<div ng-controller="MyCtrl">
    Here's a few strings: <br />
    <div ng-repeat="str in strings">{{strings[$index]}}</div>

    Here's the strings as editable (twice so that you can see the updates from a model change): <br />
    <form-textareas strings="strings"></form-textareas>
    <form-textareas strings="strings"></form-textareas>

</div>

The JS:

var myApp = angular.module('myApp',[]);
angular.module('myApp', [])
  .controller('MyCtrl', ['$scope', function($scope) {
    $scope.strings = [ "foo", "bar", "cow" ];
   }])
  .directive('formTextareas', function() {
    return {
        restrict: "E",
        scope: {
            strings: '='
        },
        templateUrl: "textareas.html",
        link: function( $scope ){
            $scope.blur = function( $event, $index ){
                $scope.strings[ $index ] = $event.currentTarget.value;
            };
        }
    };
  })
;
Share:
58,506

Related videos on Youtube

rcell
Author by

rcell

Updated on July 09, 2022

Comments

  • rcell
    rcell almost 2 years

    I'm learning angularjs and I want to be able let the user enter many inputs. When these inputs are entered the list array elements should change accordingly. I wanted to try using ngRepeat directive but I read that since it creates a new scope I cannot databind:

    <div ng-repeat="item in list">
        <label>Input {{$index+1}}:</label>
        <input ng-model="item" type="text"/>
    </div>
    

    I was wondering if I should be using a custom directive to do this or approach it differently.

    • Mark Rajcok
      Mark Rajcok over 11 years
      If one input box would work, with each item separated by some delimiter, see ngList. docs.angularjs.org/api/ng.directive:ngList
    • rcell
      rcell over 11 years
      Although it is not intuitive for what I am currently developing, It's good to know angular has this feature as a directive that I may use in the future. Thanks!
  • abbood
    abbood almost 10 years
    hey man what software did you use to render the diagrams above?
  • Mark Rajcok
    Mark Rajcok almost 10 years
    @abbood, graphviz.