Trudność z modelem ng, powtórzeniem ng i danymi wejściowymi

116

Próbuję zezwolić użytkownikowi na edycję listy elementów za pomocą ngRepeati ngModel. ( Zobacz to skrzypce .) Jednak oba podejścia, które wypróbowałem, prowadziły do ​​dziwacznego zachowania: jedno nie aktualizuje modelu, a drugie zaciera formularz przy każdym keydown.

Czy ja tu robię coś złego? Czy to nie jest obsługiwany przypadek użycia?

Oto kod ze skrzypiec, skopiowany dla wygody:

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

Wcześniejsze

Nick Heiner
źródło
1
Jest to bardzo podobne do stackoverflow.com/questions/12977894/… , ale drugie podejście jest tutaj nowe, więc nie jest dokładnie duplikatem. Udzieliłem tam szczegółowej odpowiedzi (podobnej do Artema), wyjaśniając, jak działają zakresy dzieci z powtórzeniami ng.
Mark Rajcok,
Ponieważ kosztowało mnie to rozsądną ilość wyszukiwania w Google przed ostatecznym znalezieniem tego wątku, czy mogę zmienić jego nazwę na coś takiego: „Model nadrzędny nie został zaktualizowany z danych wejściowych ngRepeat” lub „Model nie został zaktualizowany podczas korzystania z ngRepeat” lub „Dane wejściowe ngRepeat nie są powiązane z (rodzic) Model"? Może masz lepsze pomysły na tytuł?
vucalur

Odpowiedzi:

120

Wydaje się, że jest to wiążąca kwestia.

Rada brzmi : nie wiąż się z prymitywami .

Twój ngRepeatjest iteracja na smyczki wewnątrz kolekcji, gdy powinna być Iterowanie nad obiektami. Aby rozwiązać twój problem

<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
źródło
3
Dzięki za pierwsze hiperłącze, ponieważ wyjaśnia problem rozmycia / utraty ostrości / migotania. Zawsze się zastanawiałem, dlaczego tak się stało.
Mark Rajcok,
2
dzięki za to, pomogłem dużo. Jednak wiązanie z prymitywami powinno tam być imo ...
Daniel Gruszczyk
1
stary post, ale dzięki, zajęło mi trochę czasu znalezienie problemu „nie zmienianie modelu w ngRepeat” i radziłeś nie wiązać się z prymitywami
Stefanos Chrs
Ponadto: Nie modyfikuj całej listy podczas pisania - pułapka, w którą wpadłem. Obserwowałem kolekcję pod kątem zmian i zastępowałem ją identyczną kopią - więc mimo że nie wiązałem się z prymitywami, elementy były odtwarzane.
z0r
71

Korzystanie z najnowszej wersji Angular (1.2.1) i śledzenie według $index. Ten problem został rozwiązany

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

<div ng-repeat="(i, name) in names track by $index">
    Value: {{name}}
    <input ng-model="names[i]">                         
</div>
Thilaga
źródło
wykonał pracę;
koniec z
Szukałem tego tak długo ... tak łatwo ... po prostu tak ukryte. Podaj to jako odpowiedź!
Andrea
oooooh, więc dodanie „track by $ index” w ng-repeat również rozwiązuje problem „migotania”
boi_echos
43

Wpadasz w trudną sytuację, gdy konieczne jest zrozumienie, jak działają zakresy , ngRepeat i ngModel z NgModelController . Spróbuj także użyć wersji 1.0.3. Twój przykład zadziała trochę inaczej.

Możesz po prostu skorzystać z rozwiązania dostarczonego przez jm-

Ale jeśli chcesz dokładniej zająć się sytuacją, musisz zrozumieć:

  • jak działa AngularJS ;
  • zakresy mają strukturę hierarchiczną;
  • ngRepeat tworzy nowy zakres dla każdego elementu;
  • ngRepeat buduje pamięć podręczną elementów z dodatkowymi informacjami (hashKey); przy każdym wywołaniu obserwacyjnym dla każdego nowego elementu (którego nie ma w pamięci podręcznej) ngRepeat konstruuje nowy zakres, element DOM itp. Bardziej szczegółowy opis .
  • od 1.0.3 ngModelController rerenderuje dane wejściowe z rzeczywistymi wartościami modelu.

Jak Twój przykład „Bezpośrednie powiązanie z każdym elementem” działa w AngularJS 1.0.3:

  • wpisujesz literę 'f'do wejścia;
  • ngModelControllerzmienia model dla zakresu przedmiotu (nazwy tablica nie zostanie zmieniona) => name == 'Samf', names == ['Sam', 'Harry', 'Sally'];
  • $digest pętla jest uruchomiona;
  • ngRepeatzastępuje wartość modelu z zakresu pozycji ( 'Samf') wartością z niezmienionej tablicy nazw ( 'Sam');
  • ngModelControllerrerenderuje dane wejściowe z rzeczywistą wartością modelu ( 'Sam').

Jak działa Twój przykład „Indeksowanie do tablicy”:

  • wpisujesz literę 'f'do wejścia;
  • ngModelControllerzmienia element w names array=> `names == ['Samf', 'Harry', 'Sally'];
  • pętla $ Digest jest uruchomiona;
  • ngRepeatnie można znaleźć 'Samf'w pamięci podręcznej;
  • ngRepeat tworzy nowy zakres, dodaje nowy element div z nowym wejściem (dlatego pole wejściowe traci fokus - stary div ze starym wejściem jest zastępowany nowym div z nowym wejściem);
  • renderowane są nowe wartości dla nowych elementów DOM.

Możesz także spróbować użyć AngularJS Batarang i zobaczyć, jak zmienia się $ id zakresu div na dane wejściowe, w które wpisujesz.

Artem Andreev
źródło
dzięki za wyjaśnienie 1.0.3. Interesujące jest to, że w wersji 1.0.3, w przypadku „bezpośredniego powiązania”, pole wejściowe wydaje się być uszkodzone, w tym sensie, że nie wydaje się akceptować żadnych zmian / wpisywanych danych (przynajmniej w Chrome). Jestem pewien, że w najbliższej przyszłości zobaczymy sporo postów SO na ten temat :) Myślę, że ten nowy sposób jest lepszy, ponieważ będzie bardziej oczywiste, że coś jest nie tak.
Mark Rajcok,
6

Jeśli nie potrzebujesz aktualizować modelu po każdym naciśnięciu klawisza, po prostu połącz się z, namea następnie zaktualizuj element tablicy przy zdarzeniu rozmycia:

<div ng-repeat="name in names">
    Value: {{name}}
    <input ng-model="name" ng-blur="names[$index] = name" />
</div>
Bill Heitstuman
źródło
Dlaczego nie użyć ng-model="names[$index]"... Wiem, że to obejście, ale działa ;-)
Draško Kokić
2

Wydaje się, że problem tkwi w sposobie, w jaki ng-modeldziała z obiektem inputi nadpisuje go name, przez co zostaje utracony ng-repeat.

Aby obejść ten problem, można użyć następującego kodu:

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

Mam nadzieję, że to pomoże

Draško Kokić
źródło
1

Wypróbowałem powyższe rozwiązanie dla mojego problemu, które działało jak urok. Dzięki!

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

Oto moja wersja:

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

}

i mój 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
źródło
-5

jak coś takiego:

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

A w moim elemencie inspektora:

<select ng-model="myModel1">
...
<select ng-model="myModel2">
Nicolás Oviedo
źródło