Каков угловой способ привязки данных к множеству входов?
Я изучаю 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-модели изменения, внесенные вами в поле ввода формы, сохраняются в этом объекте дочерней области.
Вы можете проверить это, зарегистрировав дочернюю область на консоли. Добавьте в свой 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;
приводит к тому, что значение свойства элемента устанавливается в ссылку на один из объектов массива (не копия).
Используя метод 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;
};
}
};
})
;
Ответ 5
Рассмотрим с помощью директивы ngList
https://docs.angularjs.org/api/ng/directive/ngList