Каков угловой способ привязки данных к множеству входов?

Я изучаю angularjs, и я хочу, чтобы пользователь мог вводить множество входных данных. Когда эти входы вводятся, элементы массива list должны соответственно меняться. Я хотел попробовать использовать директиву ngRepeat, но я прочитал, что, поскольку он создает новую область видимости, я не могу привязывать к ней:

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

Мне было интересно, следует ли мне использовать настраиваемую директиву для этого или подойти по-другому.

Ответ 1

Вам повезет, если ваш list представляет собой массив объектов (в отличие от массива примитивов). Это отлично работает, даже если новая область создана с помощью ng-repeat:

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

с контроллером:

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

В качестве примера см. эту скрипту.

С другой стороны, если вы пытаетесь привязать к массиву строк, новая область вызовет проблему, так как значения, которые вы изменяете, не будут привязаны к примитивам исходной строки массива (как в этот пример скрипта).

Ответ 2

Причина, по которой привязка данных к примитивному "элементу" не работает, связана с тем, как ng-repeat создает дочерние области для каждого элемента. Для каждого элемента ng-repeat имеет новую дочернюю область, прототипно наследуемую от родительской области (см. Пунктирные строки на рисунке ниже), , а затем присваивает значение элемента новому свойству в области дочернего объекта ( красные предметы на рисунке ниже). Имя нового свойства - это имя переменной цикла. Из ng-repeat исходный код:

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

Если элемент является примитивным, новое свойство дочерней области по существу назначается копией примитивного значения. Это свойство дочерней области недоступно родительской области, и изменения, внесенные вами в поле ввода, сохраняются в этом объекте дочерней области. Например, предположим, что в родительском пространстве

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

И в HTML:

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

Тогда первая область-дочерняя область будет иметь следующее свойство item с примитивным значением (value 1):

item: "value 1"

ng-repeat with primitives

Из-за привязки данных ng-модели изменения, внесенные вами в поле ввода формы, сохраняются в этом объекте дочерней области.

Вы можете проверить это, зарегистрировав дочернюю область на консоли. Добавьте в свой HTML, внутри ng-repeat:

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

Добавьте к контроллеру:

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


С подходом @Gloopy каждый охват дочернего объекта по-прежнему получает новое свойство "item", но поскольку список теперь представляет собой массив объектов, childScope[valueIdent] = value; приводит к тому, что значение свойства элемента устанавливается в ссылку на один из объектов массива (не копия).

ng-repeat with objects

Используя метод showScope(), вы увидите, что значение свойства scope item дочернего объекта ссылается на один из объектов массива - оно больше не является примитивным значением.

См. также не привязывать к примитивам в дочерних областях ng-repeat и
Каковы нюансы объема прототипа/прототипного наследования в AngularJS? (который содержит изображения того, как выглядят области при использовании ng-repeat).

Ответ 3

Я нашел интересный способ сделать это, и это позволило мне работать с массивом примитивов.

Я использую AngularJS 1.2.1, которая является единственной версией, в которой я могу сделать эту работу.

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;
};

Ссылка: http://jsfiddle.net/bxD2P/10/ (спасибо Gloopy за стартовый скрипт)

Я уверен, что есть легкие способы вызывать дыры в том, как это работает, и я хотел бы их услышать. Это позволило бы мне сохранить мой код.

Ответ 4

Вот способ сделать это. Я использовал текстовые поля и другую структуру для своих повторителей, но основная концепция:

  • отображает простое значение, основанное на индексе. (не связан)
  • на blur обновить модель
  • при повторной рендеринге обновления модели

Это, по сути, подделка привязки.

Рабочая скрипка - http://jsfiddle.net/VvnWY/4/

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 a few strings: <br />
    <div ng-repeat="str in strings">{{strings[$index]}}</div>

    Here 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>

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;
            };
        }
    };
  })
;