7

I'm creating a form in HTML using ng-repeat to generate the form elements from an object in the scope. I also use that object to generate other elements outside of the ng-repeat.

A simplified example looks like this in HTML:

<div ng-app="App">
  <div ng-controller="Ctrl">
      <div class="block1">
          <form ng-repeat="(key, value) in test">
              <label>{{key}}</label>
              <input ng-model="value" />
              <p>{{value}}</p>
          </form>
      </div>
      <div class="block2">
        <p>
          {{test.a}}
        </p>
        <p>
            {{test.b}}
        </p>
      </div>
  </div>
</div>

and this in JS:

angular.module('App', []);

function Ctrl($scope) {
    $scope.test = {
        a:"abc",
        b:"def"
    }
}

In this example, the text in block2 is set to the initial values of test.a and test.b. The input values and <p> values inside of the loop are also set to the initial value.

When I modify the values within the inputs, the <p> values inside of the ng-repeat block update correctly, but the <p> tags in block2 fail to update.

Why is this the behavior? Does ng-repeat create its own isolated scope? If so how can I get the controller level scope to update? Also, could somebody explain the thinking behind this behavior and any advantages it provides?

JSFiddle Showing the problem

Ben McCormick
  • 25,260
  • 12
  • 52
  • 71

2 Answers2

23

ng-repeat creates a child scope for each repeated item. As a result you are trying to pass a primitive to child scope which won't create a reference to parent. When you pass objects however, you pass the original object reference.

From the mouth of one of the fathers of Angular:

Always have a dot in ng-model

This is a great video regarding Angular Best Practices given by Angular creator (2012/12/11). Go to minute 31 for well explained detail of this exact situation

Modify data to array of objects:

$scope.test = [{ val:"abc",key:'a'}, {val:"def",key:'b'} ]

Then in repeater:

<form ng-repeat="item in test">
  <label>{{item.key}}</label>
  <input ng-model="item.val" />
  <p>{{item.val}}</p>
</form>

DEMO

Mars Robertson
  • 12,673
  • 11
  • 68
  • 89
charlietfl
  • 170,828
  • 13
  • 121
  • 150
  • Thanks for the clear explanation of the behavior. My use case requires that I reference the properties of "test" by name in the outer scope, so an array doesn't work for me, but sub objects seem to work fine. – Ben McCormick Dec 14 '13 at 23:57
  • My Object keys are UNIX times, so this is very difficult for me – neaumusic Jul 14 '14 at 04:17
  • @neaumusic comment not clear, post a question if you need help – charlietfl Jul 14 '14 at 13:44
2

try this:

    angular.module('App', []);

function Ctrl($scope) {
    $scope.test = [
        {label:"a", value:"abc"},
        {label:"b", value:"def"}
    ]
}

and

<div ng-app="App">
  <div ng-controller="Ctrl">
      <div class="block1">
          <form ng-repeat="o in test">
              <label>{{o.label}}</label>
              <input ng-model="o.value" />
              <p>{{o.value}}</p>
          </form>
      </div>
      <div class="block2">
        <p>
          {{test[0].value}}
        </p>
        <p>
            {{test[1].value}}
        </p>
      </div>
  </div>
</div>

Angularjs uses the fact that objects are passed by reference. So, if you pass a object to a function and change the object inside the function, the object outside also changes. Look at this updated JSFiddle

csbenjamin
  • 356
  • 2
  • 8