Difficoltà con ng-model, ng-repeat e input


116

Sto cercando di consentire all'utente di modificare un elenco di elementi utilizzando ngRepeate ngModel. ( Vedi questo violino .) Tuttavia, entrambi gli approcci che ho provato portano a comportamenti bizzarri: uno non aggiorna il modello e l'altro sfuma la forma ad ogni tasto.

Sto facendo qualcosa di sbagliato qui? Non è un caso d'uso supportato?

Ecco il codice dal violino, copiato per comodità:

<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
Questo è molto simile a stackoverflow.com/questions/12977894/… , ma il secondo approccio è nuovo qui, quindi non è esattamente un duplicato. Ho fornito una risposta dettagliata (simile a quella di Artem) lì, spiegando come funzionano gli ambiti figlio ng-repeat.
Mark Rajcok

Dato che mi è costato una quantità ragionevole di ricerche su Google prima di trovare finalmente questo thread, posso rinominarlo in sth come: "Modello genitore non aggiornato da ngRepeat input" o "Modello non aggiornato quando si utilizza ngRepeat" o "ngRepeat input non vincolato a (genitore) modello"? Forse hai idee migliori per il titolo?
vucalur

Risposte:


120

Questo sembra essere un problema vincolante.

Il consiglio è di non legarsi ai primitivi .

Stai ngRepeatiterando su stringhe all'interno di una raccolta, quando dovrebbe iterare su oggetti. Per risolvere il tuo 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/


3
Grazie per il primo collegamento ipertestuale, in quanto spiega il problema di sfocatura / perdita di messa a fuoco / sfarfallio. Mi sono sempre chiesto perché è successo.
Mark Rajcok

2
grazie per questo, aiutato molto. Tuttavia, il legame con i primitivi dovrebbe essere lì imo ...
Daniel Gruszczyk

1
vecchio post ma grazie, mi ci è voluto un po 'di tempo per trovare il problema "non cambiare il modello all'interno di ngRepeat" ed è stato tuo consiglio di non
legarti

Inoltre: non modificare l' intero elenco durante la digitazione: una trappola in cui sono caduto. Stavo guardando la raccolta per i cambiamenti e la sostituivo con una copia identica, quindi anche se non mi legavo alle primitive, gli elementi venivano ricreati.
z0r

71

Utilizzando l'ultima versione di Angular (1.2.1) e traccia da $index. Questo problema è stato risolto

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

<div ng-repeat="(i, name) in names track by $index">
    Value: {{name}}
    <input ng-model="names[i]">                         
</div>

ha fatto il lavoro; niente più perdite di riferimento
Dan Ochiana

L'ho cercato così a lungo ... così facile ... così nascosto. Dai questo come risposta!
Andrea

oooooh, quindi l'aggiunta di "track by $ index" in ng-repeat risolve anche il problema dello "sfarfallio"
boi_echos

43

Ti trovi in ​​una situazione difficile quando è necessario capire come funzionano gli scope , ngRepeat e ngModel con NgModelController . Prova anche a usare la versione 1.0.3. Il tuo esempio funzionerà in modo leggermente diverso.

Puoi semplicemente usare la soluzione fornita da jm-

Ma se vuoi affrontare la situazione più a fondo, devi capire:

  • come funziona AngularJS ;
  • gli ambiti hanno una struttura gerarchica;
  • ngRepeat crea un nuovo ambito per ogni elemento;
  • ngRepeat crea cache di elementi con informazioni aggiuntive (hashKey); ad ogni chiamata di controllo per ogni nuovo elemento (che non è nella cache) ngRepeat costruisce un nuovo ambito, un elemento DOM, ecc . Descrizione più dettagliata .
  • da 1.0.3 ngModelController restituisce gli input con i valori effettivi del modello.

Come funziona il tuo esempio "Associazione diretta a ogni elemento" per AngularJS 1.0.3:

  • inserisci la lettera 'f'in input;
  • ngModelControllerle modifiche al modello per l'ambito oggetto (i nomi di array non viene modificato) => name == 'Samf', names == ['Sam', 'Harry', 'Sally'];
  • $digest il ciclo viene avviato;
  • ngRepeatsostituisce il valore del modello dall'ambito dell'elemento ( 'Samf') con il valore dei nomi invariati array ( 'Sam');
  • ngModelControllerrestituisce l'input con il valore del modello effettivo ( 'Sam').

Come funziona il tuo esempio "Indicizzazione nell'array":

  • inserisci la lettera 'f'in input;
  • ngModelControllercambia l'elemento nei nomi array=> `nomi == ['Samf', 'Harry', 'Sally'];
  • il ciclo $ digest viene avviato;
  • ngRepeatnon riesce a trovare 'Samf'nella cache;
  • ngRepeat crea un nuovo ambito, aggiunge un nuovo elemento div con un nuovo input (ecco perché il campo di input perde il focus: il vecchio div con il vecchio input viene sostituito dal nuovo div con un nuovo input);
  • vengono visualizzati i nuovi valori per i nuovi elementi DOM.

Inoltre, puoi provare a utilizzare AngularJS Batarang e vedere come cambia $ id dell'ambito di div con l'input in cui inserisci.


grazie per la spiegazione 1.0.3. È interessante notare che nella 1.0.3, il caso "bind directly", il campo di input sembra essere rotto, nel senso che non sembra accettare alcuna modifica / input digitato (almeno in Chrome). Sono sicuro che vedremo un bel po 'di post su questo argomento nel prossimo futuro :) Suppongo che questo nuovo modo sia migliore, poiché sarà più ovvio che qualcosa non va.
Mark Rajcok

6

Se non è necessario che il modello si aggiorni a ogni pressione di un tasto, associa namee aggiorna l'elemento dell'array all'evento di sfocatura:

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

Perché non usare ng-model="names[$index]"... So che è una soluzione alternativa, ma funziona ;-)
Draško Kokić


2

Il problema sembra essere nel modo in cui ng-modelfunziona inpute sovrascrive l' nameoggetto, facendolo perdere per ng-repeat.

Come soluzione alternativa, è possibile utilizzare il codice seguente:

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

Spero che sia d'aiuto


1

Ho provato la soluzione sopra per il mio problema in quanto ha funzionato a meraviglia. Grazie!

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

Ecco la mia versione di questo:

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

}

e il mio 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

come fare qualcosa come:

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

E nel mio elemento ispettore essere:

<select ng-model="myModel1">
...
<select ng-model="myModel2">
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.