Difficulté avec ng-model, ng-repeat et entrées

116

J'essaie de permettre à l'utilisateur de modifier une liste d'éléments en utilisant ngRepeatet ngModel. ( Voir ce violon .) Cependant, les deux approches que j'ai essayées mènent à un comportement bizarre: l'une ne met pas à jour le modèle et l'autre brouille le formulaire à chaque keydown.

Est-ce que je fais quelque chose de mal ici? N'est-ce pas un cas d'utilisation pris en charge?

Voici le code du violon, copié pour plus de commodité:

<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
la source
1
C'est très similaire à stackoverflow.com/questions/12977894/… , mais la 2ème approche est nouvelle ici, donc ce n'est pas exactement un doublon. J'y ai fourni une réponse détaillée (similaire à celle d'Artem), expliquant comment fonctionnent les portées enfant ng-repeat.
Mark Rajcok
Comme cela m'a coûté une quantité raisonnable de recherches sur Google avant de trouver finalement ce fil, puis-je le renommer en qc comme: «Modèle parent non mis à jour à partir des entrées ngRepeat» ou «Modèle non mis à jour lors de l'utilisation de ngRepeat» ou «entrées ngRepeat non liées à (parent) modèle"? Peut-être avez-vous de meilleures idées pour le titre?
vucalur

Réponses:

120

Cela semble être une question contraignante.

Le conseil est de ne pas se lier aux primitifs .

Votre ngRepeatitère sur des chaînes à l'intérieur d'une collection, alors qu'il devrait itérer sur des objets. Pour résoudre votre problème

<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
la source
3
Merci pour le premier lien hypertexte, car il explique le problème de flou / perte de mise au point / scintillement. Je me suis toujours demandé pourquoi cela arrivait.
Mark Rajcok
2
merci pour cela, beaucoup aidé. Pourtant, la liaison aux primitives devrait être là imo ...
Daniel Gruszczyk
1
ancien message mais thnx, il m'a fallu du temps pour trouver le problème `` ne pas changer le modèle à l'intérieur de ngRepeat '' et c'était votre conseil de ne pas se lier aux primitives
Stefanos Chrs
Aussi: ne modifiez pas la liste entière en tapant - un piège dans lequel je suis tombé. Je regardais la collection pour les changements et la remplaçais par une copie identique - donc même si je ne me liais pas aux primitifs, les éléments étaient recréés.
z0r
71

Utilisation de la dernière version Angular (1.2.1) et suivi par $index. Ce problème est résolu

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

<div ng-repeat="(i, name) in names track by $index">
    Value: {{name}}
    <input ng-model="names[i]">                         
</div>
Thilaga
la source
fait le travail; plus de perte de référence
Dan Ochiana
J'ai cherché si longtemps pour ça ... si facile ... si caché. Donnez ceci comme réponse!
Andrea
oooooh, donc l'ajout de "track by $ index" dans ng-repeat corrige également le problème de "scintillement"
boi_echos
43

Vous vous retrouvez dans une situation difficile lorsqu'il est nécessaire de comprendre comment fonctionnent les scopes , ngRepeat et ngModel avec NgModelController . Essayez également d'utiliser la version 1.0.3. Votre exemple fonctionnera un peu différemment.

Vous pouvez simplement utiliser la solution fournie par jm-

Mais si vous voulez aborder la situation plus profondément, vous devez comprendre:

  • comment fonctionne AngularJS ;
  • les portées ont une structure hiérarchique;
  • ngRepeat crée une nouvelle portée pour chaque élément;
  • ngRepeat build cache des éléments avec des informations supplémentaires (hashKey); à chaque appel de surveillance pour chaque nouvel élément (qui n'est pas dans le cache) ngRepeat construit une nouvelle portée, un élément DOM, etc. Description plus détaillée .
  • à partir de la version 1.0.3, ngModelController restitue les entrées avec les valeurs réelles du modèle.

Comment votre exemple "Liaison directe à chaque élément" fonctionne pour AngularJS 1.0.3:

  • vous entrez une lettre 'f'en entrée;
  • ngModelControllermodifie le modèle pour la portée de l'élément (le tableau des noms n'est pas modifié) => name == 'Samf',names == ['Sam', 'Harry', 'Sally'] ;
  • $digest la boucle est lancée;
  • ngRepeatremplace la valeur du modèle de la portée de l'élément ( 'Samf') par la valeur du tableau des noms inchangés ('Sam' );
  • ngModelControllerrenvoie l'entrée avec la valeur réelle du modèle ( 'Sam').

Comment fonctionne votre exemple "Indexation dans le tableau":

  • vous entrez une lettre 'f'en entrée;
  • ngModelControllerchange l'élément dans les noms array=> `names == ['Samf', 'Harry', 'Sally'];
  • La boucle $ digest est lancée;
  • ngRepeatne peut pas trouver 'Samf'dans le cache;
  • ngRepeat crée une nouvelle portée, ajoute un nouvel élément div avec une nouvelle entrée (c'est pourquoi le champ d'entrée perd le focus - l'ancien div avec l'ancienne entrée est remplacé par un nouveau div avec une nouvelle entrée);
  • les nouvelles valeurs des nouveaux éléments DOM sont rendues.

En outre, vous pouvez essayer d'utiliser AngularJS Batarang et voir comment change $ id de la portée de div avec l'entrée dans laquelle vous entrez.

Artem Andreev
la source
merci pour l'explication 1.0.3. Il est intéressant de noter que dans la version 1.0.3, le cas "lier directement", le champ de saisie semble être cassé, dans le sens où il ne semble accepter aucune modification / entrée tapée (du moins dans Chrome). Je suis sûr que nous verrons pas mal de messages SO à ce sujet dans un proche avenir :) Je suppose que cette nouvelle méthode est meilleure, car il sera plus évident que quelque chose ne va pas.
Mark Rajcok
6

Si vous n'avez pas besoin que le modèle se mette à jour à chaque coup de touche, il vous suffit de lier nameet de mettre à jour l'élément de tableau lors de l'événement de flou:

<div ng-repeat="name in names">
    Value: {{name}}
    <input ng-model="name" ng-blur="names[$index] = name" />
</div>
Bill Heitstuman
la source
Pourquoi ne pas utiliser ng-model="names[$index]"... Je sais que c'est une solution de contournement, mais cela fonctionne ;-)
Draško Kokić
2

Le problème semble être la façon dont ng-modelfonctionne inputet écrase l' nameobjet, le rendant perdu ng-repeat.

Comme solution de contournement, on peut utiliser le code suivant:

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

J'espère que ça aide

Draško Kokić
la source
1

J'ai essayé la solution ci-dessus pour mon problème, cela fonctionnait comme un charme. Merci!

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

Voici ma version de cela:

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

}

et mon 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
la source
-5

comment faire quelque chose comme:

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

Et dans mon élément inspecteur soyez:

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