Сложность с ng-model, ng-repeat и входами

116

Я пытаюсь разрешить пользователю редактировать список элементов с помощью ngRepeatи ngModel. ( См. Эту скрипку .) Однако оба подхода, которые я пробовал, приводят к странному поведению: один не обновляет модель, а другой размывает форму при каждом нажатии клавиши.

Я что-то здесь делаю не так? Разве это не поддерживаемый вариант использования?

Вот код скрипки, скопированный для удобства:

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

Ник Хайнер
источник
1
Это очень похоже на stackoverflow.com/questions/12977894/… , но второй подход здесь новый, так что это не совсем дубликат. Я дал подробный ответ (аналогичный ответу Артема), объяснив, как работают дочерние области действия ng-repeat.
Марк Райкок
Поскольку это стоило мне разумного количества поисков в Google, прежде чем я наконец нашел этот поток, могу ли я переименовать его в sth, например: «Родительская модель не обновляется из входов ngRepeat» или «Модель не обновляется при использовании ngRepeat» или «Входы ngRepeat не привязаны к (родительскому) модель"? Может быть, у вас есть идеи получше для названия?
vucalur

Ответы:

120

Это кажется обязательной проблемой.

Совет: не привязывайтесь к примитивам .

Вы ngRepeatвыполняете итерацию по строкам внутри коллекции, тогда как она должна выполнять итерацию по объектам. Чтобы исправить вашу проблему

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

Хайме
источник
3
Спасибо за первую гиперссылку, поскольку она объясняет проблему размытия / потери фокуса / мерцания. Я всегда задавался вопросом, почему это произошло.
Марк Райкок
2
спасибо за это, очень помогли. Тем не менее, привязка к примитивам должна быть там, я ...
Даниэль Грушчик
1
старый пост, но спасибо, мне потребовалось некоторое время, чтобы найти проблему «не менять модель внутри ngRepeat», и это был ваш совет не привязывать к примитивам
Stefanos Chrs
Также: не изменяйте весь список во время набора текста - я попал в ловушку. Я смотрел коллекцию на предмет изменений и заменял ее идентичной копией - поэтому, хотя я не привязывался к примитивам, элементы создавались заново.
z0r
71

Используя последнюю версию Angular (1.2.1) и следите за $index. Эта проблема исправлена

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

<div ng-repeat="(i, name) in names track by $index">
    Value: {{name}}
    <input ng-model="names[i]">                         
</div>
Thilaga
источник
сделал работу; больше нет ссылки
Дэн Очиана
Я так долго искал это ... так просто ... так спрятано. Дайте это как ответ!
Андреа
ооооо, поэтому добавление «track by $ index» в ng-repeat также устраняет проблему «мерцания»
boi_echos
43

Вы попадаете в сложную ситуацию, когда необходимо понять, как работают scopes , ngRepeat и ngModel с NgModelController . Также попробуйте использовать версию 1.0.3. Ваш пример будет работать немного иначе.

Вы можете просто использовать решение, предоставленное jm-

Но если вы хотите разобраться в ситуации более глубоко, вы должны понимать:

  • как работает AngularJS ;
  • области имеют иерархическую структуру;
  • ngRepeat создает новую область видимости для каждого элемента;
  • ngRepeat строит кеш элементов с дополнительной информацией (hashKey); при каждом вызове часов для каждого нового элемента (которого нет в кеше) ngRepeat создает новую область видимости, элемент DOM и т. д. Более подробное описание .
  • из 1.0.3 ngModelController повторно отображает входные данные с фактическими значениями модели.

Как ваш пример «Привязка к каждому элементу напрямую» работает для AngularJS 1.0.3:

  • вводите букву 'f'во ввод;
  • ngModelControllerизменяет модель для охвата элемента (массив имен не изменяется) => name == 'Samf', names == ['Sam', 'Harry', 'Sally'];
  • $digest цикл запущен;
  • ngRepeatзаменяет значение модели из области действия элемента ( 'Samf') на значение из массива неизмененных имен ( 'Sam');
  • ngModelControllerповторно отображает ввод с фактическим значением модели ( 'Sam').

Как работает ваш пример «Индексирование в массив»:

  • вводите букву 'f'во ввод;
  • ngModelControllerизменяет элемент в names array=> `names == ['Samf', 'Harry', 'Sally'];
  • цикл $ дайджест запускается;
  • ngRepeatне могу найти 'Samf'в кеше;
  • ngRepeat создает новую область видимости, добавляет новый элемент div с новым вводом (поэтому поле ввода теряет фокус - старый div со старым вводом заменяется новым div с новым вводом);
  • отображаются новые значения для новых элементов DOM.

Кроме того, вы можете попробовать использовать AngularJS Batarang и посмотреть, как изменяет $ id области действия div с вводом, в который вы вводите.

Артем Андреев
источник
спасибо за объяснение 1.0.3. Интересно, что в 1.0.3, в случае «связывания напрямую», поле ввода кажется неработающим в том смысле, что оно не принимает никаких изменений / вводимых данных (по крайней мере, в Chrome). Я уверен, что мы увидим довольно много сообщений об этом в ближайшем будущем :) Я полагаю, что этот новый способ лучше, поскольку будет более очевидным, что что-то не так.
Марк Райкок
6

Если вам не нужно, чтобы модель обновлялась при каждом нажатии клавиши, просто выполните привязку, nameа затем обновите элемент массива при событии размытия:

<div ng-repeat="name in names">
    Value: {{name}}
    <input ng-model="name" ng-blur="names[$index] = name" />
</div>
Билл Хейтстуман
источник
Почему бы не использовать ng-model="names[$index]"... Я знаю, что это обходной путь, но он работает ;-)
Драшко Кокич
2

Проблема, похоже, в том, как ng-modelработает inputи перезаписывает nameобъект, что делает его потерянным для ng-repeat.

В качестве обходного пути можно использовать следующий код:

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

Надеюсь, поможет

Драшко Кокич
источник
1

Я попробовал решение выше для моей проблемы, и это сработало как шарм. Спасибо!

http://jsfiddle.net/leighboone/wn9Ym/7/

Вот моя версия этого:

var myApp = angular.module('myApp', []);
function MyCtrl($scope) {
    $scope.models = [{
        name: 'Device1',
        checked: true
    }, {
        name: 'Device1',
        checked: true
    }, {
        name: 'Device1',
        checked: true
    }];

}

и мой HTML

<div ng-app="myApp">
    <div ng-controller="MyCtrl">
         <h1>Fun with Fields and ngModel</h1>
        <p>names: {{models}}</p>
        <table class="table table-striped">
            <thead>
                <tr>
                    <th></th>
                    <th>Feature 1</td>
                    <th>Feature 2</th>
                    <th>Feature 3</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td>Device</td>
                   <td ng-repeat="modelCheck in models" class=""> <span>
                                    {{modelCheck.checked}}
                                </span>

                    </td>
                </tr>
                <tr>
                    <td>
                        <label class="control-label">Which devices?</label>
                    </td>
                    <td ng-repeat="model in models">{{model.name}}
                        <input type="checkbox" class="checkbox inline" ng-model="model.checked" />
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
</div>
Ли Лоухон
источник
-5

как сделать что-то вроде:

<select ng-model="myModel($index+1)">

И в моем элементе инспектора быть:

<select ng-model="myModel1">
...
<select ng-model="myModel2">
Николас Овьедо
источник