Dificultad con ng-model, ng-repeat y entradas

116

Estoy tratando de permitir que el usuario edite una lista de elementos usando ngRepeaty ngModel. ( Vea este violín ). Sin embargo, ambos enfoques que he probado conducen a un comportamiento extraño: uno no actualiza el modelo y el otro difumina el formulario en cada tecla.

¿Estoy haciendo algo mal aquí? ¿No es este un caso de uso compatible?

Aquí está el código del violín, copiado por conveniencia:

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

Nick Heiner
fuente
1
Esto es muy similar a stackoverflow.com/questions/12977894/… , pero el segundo enfoque es nuevo aquí, por lo que no es exactamente un duplicado. Proporcioné una respuesta detallada (similar a la de Artem) allí, explicando cómo funcionan los ámbitos de niños ng-repeat.
Mark Rajcok
Dado que me costó una cantidad razonable de buscar en Google antes de encontrar finalmente este hilo, ¿puedo cambiarle el nombre a algo como: "Modelo principal no actualizado desde las entradas ngRepeat" o "Modelo no actualizado al usar ngRepeat" o "entradas ngRepeat no vinculadas a (padre)? modelo"? ¿Quizás tienes mejores ideas para el título?
vucalur

Respuestas:

120

Este parece ser un tema vinculante.

El consejo es no ligarse a los primitivos .

Su ngRepeates la iteración del cuerdas dentro de una colección, cuando debería ser la iteración sobre los objetos. Para solucionar tu problema

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

jaime
fuente
3
Gracias por el primer hipervínculo, ya que explica el problema de desenfoque / pérdida de enfoque / parpadeo. Siempre me pregunté por qué sucedió eso.
Mark Rajcok
2
gracias por eso, ayudó mucho. Aún así, la unión a los primitivos debería estar allí en mi opinión ...
Daniel Gruszczyk
1
publicación anterior, pero gracias, me tomó algo de tiempo encontrar el problema de 'no cambiar el modelo dentro de ngRepeat' y fue su consejo no vincularse a los primitivos
Stefanos Chrs
Además: no modifique la lista completa mientras escribe, una trampa en la que caí. Estaba observando la colección en busca de cambios y reemplazándola con una copia idéntica, por lo que, aunque no estaba vinculando a los primitivos, los elementos se estaban recreando.
z0r
71

Usando la última versión de Angular (1.2.1) y rastrea por $index. Este problema está solucionado

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

<div ng-repeat="(i, name) in names track by $index">
    Value: {{name}}
    <input ng-model="names[i]">                         
</div>
Thilaga
fuente
hizo el trabajo; no más pérdida de referencia
Dan Ochiana
Busqué tanto tiempo por eso ... tan fácil ... tan escondido. ¡Da esto como respuesta!
Andrea
oooooh, así que agregar "track by $ index" en ng-repeat también soluciona el problema de "parpadeo"
boi_echos
43

Te encuentras en una situación difícil cuando es necesario comprender cómo funcionan los ámbitos , ngRepeat y ngModel con NgModelController . También intente utilizar la versión 1.0.3. Tu ejemplo funcionará de manera un poco diferente.

Simplemente puede usar la solución proporcionada por jm-

Pero si quieres lidiar con la situación más profundamente, debes entender:

  • cómo funciona AngularJS ;
  • los ámbitos tienen una estructura jerárquica;
  • ngRepeat crea un nuevo alcance para cada elemento;
  • ngRepeat crear caché de elementos con información adicional (hashKey); en cada llamada de vigilancia para cada elemento nuevo (que no está en la caché) ngRepeat construye un nuevo alcance, elemento DOM, etc. Descripción más detallada .
  • de 1.0.3 ngModelController reproduce las entradas con los valores reales del modelo.

Cómo funciona su ejemplo "Enlace a cada elemento directamente" para AngularJS 1.0.3:

  • ingresa una letra 'f'en la entrada;
  • ngModelControllercambios en el modelo para el ámbito artículo (nombres de matriz no se cambia) => name == 'Samf', names == ['Sam', 'Harry', 'Sally'];
  • $digest se inicia el bucle;
  • ngRepeatreemplaza el valor del modelo del alcance del elemento ( 'Samf') por el valor de la matriz de nombres sin cambios ( 'Sam');
  • ngModelControllervuelve a reproducir la entrada con el valor real del modelo ( 'Sam').

Cómo funciona su ejemplo "Indexación en la matriz":

  • ingresa una letra 'f'en la entrada;
  • ngModelControllercambia el elemento en los nombres array=> `nombres == ['Samf', 'Harry', 'Sally'];
  • Se inicia el bucle $ digest;
  • ngRepeatno se puede encontrar 'Samf'en el caché;
  • ngRepeat crea un nuevo alcance, agrega un nuevo elemento div con una nueva entrada (es por eso que el campo de entrada pierde el foco: el div antiguo con la entrada antigua se reemplaza por un nuevo div con la nueva entrada);
  • Se representan nuevos valores para nuevos elementos DOM.

Además, puede intentar usar AngularJS Batarang y ver cómo cambia $ id del alcance de div con la entrada en la que ingresa.

Artem Andreev
fuente
gracias por la explicación 1.0.3. Es interesante que en 1.0.3, el caso de "vincular directamente", el campo de entrada parece estar roto, en el sentido de que no parece aceptar ningún cambio / entrada escrita (al menos en Chrome). Estoy seguro de que veremos bastantes publicaciones de SO sobre esto en un futuro cercano :) Supongo que esta nueva forma es mejor, ya que será más obvio que algo anda mal.
Mark Rajcok
6

Si no necesita que el modelo se actualice con cada pulsación de tecla, simplemente enlace namey luego actualice el elemento de la matriz en el evento de desenfoque:

<div ng-repeat="name in names">
    Value: {{name}}
    <input ng-model="name" ng-blur="names[$index] = name" />
</div>
Bill Heitstuman
fuente
Por qué no usar ng-model="names[$index]"... Sé que es una solución alternativa, pero funciona ;-)
Draško Kokić
2

El problema parece estar en la forma en que ng-modelfunciona inputy sobrescribe el nameobjeto, haciéndolo perdidong-repeat .

Como solución alternativa, se puede utilizar el siguiente código:

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

Espero eso ayude

Draško Kokić
fuente
1

Probé la solución anterior para mi problema y funcionó como un encanto. ¡Gracias!

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

Aquí está mi versión de eso:

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

}

y mi 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>
Leigh Lawhon
fuente
-5

cómo hacer algo como:

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

Y en mi elemento de inspector sea:

<select ng-model="myModel1">
...
<select ng-model="myModel2">
Nicolás Oviedo
fuente