Dificuldade com ng-model, ng-repeat e entradas

116

Estou tentando permitir que o usuário edite uma lista de itens usando ngRepeate ngModel. ( Veja este violino .) No entanto, as duas abordagens que tentei levam a um comportamento bizarro: uma não atualiza o modelo e a outra desfoca a forma em cada tecla.

Estou fazendo algo errado aqui? Este não é um caso de uso compatível?

Aqui está o código do violino, copiado por conveniência:

<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
fonte
1
Isso é muito semelhante a stackoverflow.com/questions/12977894/… , mas a segunda abordagem é nova aqui, então não é exatamente uma duplicata. Forneci uma resposta detalhada (semelhante à de Artem) lá, explicando como os escopos ng-repeat funcionam.
Mark Rajcok
Uma vez que me custou uma quantidade razoável de pesquisa no Google antes de finalmente encontrar este tópico, posso renomeá-lo para sth como: "Modelo pai não atualizado das entradas ngRepeat" ou "Modelo não atualizado ao usar ngRepeat" ou "entradas ngRepeat não vinculadas a (pai) modelo"? Talvez você tenha ideias melhores para o título?
vucalur

Respostas:

120

Este parece ser um problema vinculativo.

O conselho é não se vincular a primitivos .

Você ngRepeatestá iterando em strings dentro de uma coleção, quando deveria estar iterando em objetos. Para consertar seu 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
fonte
3
Obrigado pelo primeiro hyperlink, pois explica o problema de desfoque / perda de foco / tremulação. Sempre me perguntei por que isso aconteceu.
Mark Rajcok
2
obrigado por isso, ajudou muito. Ainda assim, a ligação a primitivos deve estar lá imo ...
Daniel Gruszczyk
1
post antigo, mas thnx, levei algum tempo para encontrar o problema 'não alterando o modelo dentro do ngRepeat' e foi seu conselho para não vincular a primitivos
Stefanos Chrs
Além disso: não modifique a lista inteira enquanto digita - uma armadilha na qual caí. Eu estava observando a coleção em busca de alterações e substituindo-a por uma cópia idêntica - então, embora eu não estivesse vinculando aos primitivos, os elementos estavam sendo recriados.
z0r
71

Usando a versão mais recente do Angular (1.2.1) e rastreado por $index. Este problema foi corrigido

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

<div ng-repeat="(i, name) in names track by $index">
    Value: {{name}}
    <input ng-model="names[i]">                         
</div>
Thilaga
fonte
fez o trabalho; sem perda de referência
Dan Ochiana
Eu procurei por tanto tempo por isso ... tão fácil ... tão escondido. Dê esta como a resposta!
Andrea
oooooh, então adicionar "faixa por $ index" no ng-repeat também corrige o problema de "oscilação"
boi_echos
43

Você entra em uma situação difícil quando é necessário entender como escopos , ngRepeat e ngModel com NgModelController funcionam. Também tente usar a versão 1.0.3. Seu exemplo funcionará de maneira um pouco diferente.

Você pode simplesmente usar a solução fornecida por jm-

Mas se você quiser lidar com a situação mais profundamente, você deve entender:

  • como funciona o AngularJS ;
  • escopos têm uma estrutura hierárquica;
  • ngRepeat cria um novo escopo para cada elemento;
  • ngRepeat criar cache de itens com informações adicionais (hashKey); em cada chamada de observação para cada novo item (que não está no cache) ngRepeat constrói novo escopo, elemento DOM, etc. Descrição mais detalhada .
  • de 1.0.3 ngModelController renderiza novamente as entradas com os valores reais do modelo.

Como seu exemplo "Vinculando a cada elemento diretamente" funciona para AngularJS 1.0.3:

  • você insere uma letra 'f'na entrada;
  • ngModelControllermuda modelo de escopo item (variedade nomes não é alterado) => name == 'Samf', names == ['Sam', 'Harry', 'Sally'];
  • $digest loop é iniciado;
  • ngRepeatsubstitui o valor do modelo do escopo do item ( 'Samf') pelo valor do array de nomes inalterados ( 'Sam');
  • ngModelControllerrenderiza novamente a entrada com o valor real do modelo ( 'Sam').

Como funciona o seu exemplo "Indexação na matriz":

  • você insere uma letra 'f'na entrada;
  • ngModelControllermuda o item em nomes array=> `nomes == ['Samf', 'Harry', 'Sally'];
  • o loop $ digest é iniciado;
  • ngRepeatnão consigo encontrar 'Samf'no cache;
  • ngRepeat cria novo escopo, adiciona novo elemento div com nova entrada (é por isso que o campo de entrada perde o foco - div antigo com entrada antiga é substituído por novo div com nova entrada);
  • novos valores para novos elementos DOM são renderizados.

Além disso, você pode tentar usar AngularJS Batarang e ver como muda $ id do escopo de div com a entrada na qual você insere.

Artem Andreev
fonte
obrigado pela explicação 1.0.3. É interessante que em 1.0.3, o caso "vincular diretamente", o campo de entrada parece estar quebrado, no sentido de que não parece aceitar nenhuma alteração / entrada digitada (pelo menos no Chrome). Tenho certeza que veremos alguns posts do SO sobre isso em um futuro próximo :) Suponho que essa nova forma seja melhor, pois será mais óbvio que algo está errado.
Mark Rajcok
6

Se você não precisa que o modelo seja atualizado a cada pressionamento de tecla, basta vincular namee atualizar o item da matriz no evento de desfoque:

<div ng-repeat="name in names">
    Value: {{name}}
    <input ng-model="name" ng-blur="names[$index] = name" />
</div>
Bill Heitstuman
fonte
Por que não usar ng-model="names[$index]"... Eu sei que é uma solução alternativa, mas funciona ;-)
Draško Kokić
2

O problema parece estar na maneira como ng-modelfunciona inpute sobrescreve o nameobjeto, tornando-o perdido para ng-repeat.

Como alternativa, pode-se usar o seguinte código:

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

Espero que ajude

Draško Kokić
fonte
1

Eu tentei a solução acima para o meu problema e funcionou como um encanto. Obrigado!

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

Aqui está minha versão disso:

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

}

e meu 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
fonte
-5

como fazer algo como:

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

E no meu elemento inspetor seja:

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