Zakres izolatów dyrektywy z zakresem powtórzeń ng w AngularJS

95

Mam dyrektywę z izolowanym zakresem (aby móc ponownie wykorzystać dyrektywę w innych miejscach), a kiedy używam tej dyrektywy z rozszerzeniem ng-repeat, nie działa.

Przeczytałem całą dokumentację i odpowiedzi Stack Overflow na ten temat i rozumiem problemy. Myślę, że uniknąłem wszystkich typowych problemów.

Rozumiem więc, że mój kod nie działa z powodu zakresu utworzonego przez ng-repeatdyrektywę. Moja własna dyrektywa tworzy zakres izolowany i tworzy dwukierunkowe powiązanie danych z obiektem w zakresie nadrzędnym. Moja dyrektywa przypisze nową wartość obiektu do tej zmiennej związanej i działa to doskonale, gdy moja dyrektywa jest używana bez ng-repeat(zmienna nadrzędna jest poprawnie aktualizowana). Jednak w ng-repeatprzypadku przypisanie tworzy nową zmienną w ng-repeatzakresie, a zmienna nadrzędna nie widzi zmiany. Wszystko to jest zgodne z oczekiwaniami na podstawie tego, co przeczytałem.

Przeczytałem również, że gdy na danym elemencie jest wiele dyrektyw, tworzony jest tylko jeden zakres. I że prioritymożna ustawić w każdej dyrektywie, aby określić kolejność, w jakiej dyrektywy są stosowane; dyrektywy są sortowane według priorytetów, a następnie wywoływane są ich funkcje kompilujące (szukaj słowa „priorytet” na http://docs.angularjs.org/guide/directive ).

Miałem więc nadzieję, że mógłbym użyć priorytetu, aby upewnić się, że moja dyrektywa działa jako pierwsza i ostatecznie tworzy zakres izolowany, a po ng-repeaturuchomieniu ponownie wykorzystuje zakres izolowany zamiast tworzyć zakres, który prototypowo dziedziczy z zakresu nadrzędnego. Z ng-repeatdokumentacji wynika, że ​​ta dyrektywa ma charakter priorytetowy 1000. Nie jest jasne, czy 1jest to wyższy poziom priorytetu, czy niższy poziom priorytetu. Kiedy użyłem poziomu priorytetu 1w mojej dyrektywie, nie miało to znaczenia, więc spróbowałem 2000. Ale to pogarsza sytuację: moje dwukierunkowe wiązania stają się, undefineda moja dyrektywa nic nie wyświetla.

Stworzyłem skrzypce, aby pokazać mój problem . Skomentowałem priorityustawienie w mojej dyrektywie. Mam listę obiektów nazw i dyrektywę o nazwie, name-rowktóra pokazuje pola imienia i nazwiska w obiekcie nazwy. Po kliknięciu wyświetlanej nazwy chcę, aby ustawiała selectedzmienną w głównym zakresie. Tablica nazw, selectedzmienna jest przekazywana do name-rowdyrektywy przy użyciu dwukierunkowego wiązania danych.

Wiem, jak to działa, wywołując funkcje w głównym zakresie. Wiem również, że jeśli selectedznajduje się wewnątrz innego obiektu i wiążę się z obiektem zewnętrznym, wszystko będzie działać. Ale w tej chwili nie jestem zainteresowany tymi rozwiązaniami.

Zamiast tego mam następujące pytania:

  • Jak zapobiec ng-repeattworzeniu zakresu, który prototypowo dziedziczy po zakresie nadrzędnym, i zamiast tego używać zakresu izolowanego mojej dyrektywy?
  • Dlaczego poziom priorytetu 2000w mojej dyrektywie nie działa?
  • Czy korzystając z Batarangu można dowiedzieć się, jaki typ lunety jest używany?
Deepak Nulu
źródło
1
Zwykle nie chcesz używać izolowanego zakresu, jeśli Twoja dyrektywa będzie używana na tym samym elemencie z innymi dyrektywami. Ponieważ tworzysz własne właściwości zakresu i musisz pracować z ng-repeat, sugeruję użycie scope: truedla twojej dyrektywy. Zobacz także (jeśli jeszcze tego nie zrobiłeś) stackoverflow.com/questions/14914213/ ... Również fakt, że dyrektywa będzie używana w wielu miejscach, nie oznacza, że ​​powinniśmy automatycznie używać zakresu izolowanego.
Mark Rajcok
Przeczytałem wiele Twoich odpowiedzi (są ponadprzeciętne, dziękuję za napisanie), ale nigdy nie przyszło mi do głowy, żeby przeczytać Twoje pytania :-). Czytałem, do czego dołączyłeś. Wydaje mi się, że dyrektywy o zakresie izolowanym nie mogą być mieszane z innymi dyrektywami. Zgadzam się z opinią, że takie dyrektywy są składnikami i dlatego nie trzeba ich mieszać z innymi dyrektywami. Jedynym wyjątkiem (jak dotąd) byłby dla mnie ng-repeat. Myślę, że warto mieć możliwość mieszania samodzielnych dyrektyw z ng-repeat.
Ciąg
Ciąg dalszy z góry ... Więc jeśli powinna istnieć tylko jedna dyrektywa z zakresem dla elementu, to ng-repeatnie powinna mieć zakresu. ng-repeatposiadanie lunety ma sens w typowym przypadku użycia, więc nie sugeruję, aby go zmieniać. Zamiast tego, jak skomentowałem w odpowiedzi Alexa Osborna, myślę, że utworzę dyrektywę powtarzania opartą na ng-repeattym, że nie stworzy własnego zakresu. Może to być następnie użyte do powtarzania dyrektyw, które mają własne izolowane zakresy.
Ciąg
Kod, który powtarza dyrektywę, musi teraz wiedzieć, czy użyć, ng-repeatczy niestandardowej dyrektywy powtarzania bez zakresu. Myślę, że „dzwoniący” powinien wiedzieć o tym, ale nie jest w porządku, aby „wywołujący” (powtarzanie dyrektywy) wiedział, czy jest to powtarzane, czy nie.
Ciąg
Trochę szaleję z komentarzami tutaj ... :-) ngRepeat musi stworzyć własny zakres. Dlaczego uważasz, że potrzebujesz tutaj izolowanego zakresu?
Josh David Miller,

Odpowiedzi:

203

OK, dzięki wielu powyższym komentarzom odkryłem zamieszanie. Najpierw kilka punktów do wyjaśnienia:

  • ngRepeat nie wpływa na wybrany zakres izolatu
  • parametry przekazywane do ngRepeat do użytku na atrybuty dyrektywie należy użyć prototypowo dziedziczone zakresu
  • przyczyna, dla której twoja dyrektywa nie działa, nie ma nic wspólnego z zakresem izolowanym

Oto przykład tego samego kodu, ale z usuniętą dyrektywą:

<li ng-repeat="name in names"
    ng-class="{ active: $index == selected }"
    ng-click="selected = $index">
    {{$index}}: {{name.first}} {{name.last}}
</li>

Oto JSFiddle demonstrujący, że to nie zadziała. Otrzymasz dokładnie takie same wyniki, jak w swojej dyrektywie.

Dlaczego to nie działa? Ponieważ zakresy w AngularJS używają dziedziczenia prototypowego. Wartość selectedzakresu nadrzędnego jest prymitywna . W JavaScript oznacza to, że zostanie nadpisany, gdy dziecko ustawi tę samą wartość. W zakresach AngularJS obowiązuje złota zasada: wartości modeli powinny zawsze mieć w sobie znak .. Oznacza to, że nigdy nie powinni być prymitywni. Zobacz tę odpowiedź SO, aby uzyskać więcej informacji.


Oto obraz tego, jak początkowo wyglądały lunety.

wprowadź opis obrazu tutaj

Po kliknięciu pierwszego elementu zakresy wyglądają teraz następująco:

wprowadź opis obrazu tutaj

Zwróć uwagę, że w selectedzakresie ngRepeat została utworzona nowa właściwość. Zakres sterownika 003 nie został zmieniony.

Prawdopodobnie zgadniesz, co się stanie, gdy klikniemy na drugą pozycję:

wprowadź opis obrazu tutaj


Więc twój problem w rzeczywistości nie jest spowodowany przez ngRepeat - jest spowodowany złamaniem złotej zasady w AngularJS. Aby to naprawić, wystarczy użyć właściwości obiektu:

$scope.state = { selected: undefined };
<li ng-repeat="name in names"
    ng-class="{ active: $index == state.selected }"
    ng-click="state.selected = $index">
    {{$index}}: {{name.first}} {{name.last}}
</li>

Oto drugi JSFiddle pokazujący, że to również działa.

Oto jak początkowo wyglądają lunety:

wprowadź opis obrazu tutaj

Po kliknięciu pierwszej pozycji:

wprowadź opis obrazu tutaj

W tym przypadku, zgodnie z życzeniem, wpływa to na zakres sterownika.

Ponadto, aby udowodnić, że będzie to nadal działać z twoją dyrektywą z zakresem izolowanym (ponieważ, znowu, nie ma to nic wspólnego z twoim problemem), tutaj jest JSFiddle również do tego, widok musi odzwierciedlać obiekt. Zauważysz, że jedyną konieczną zmianą było użycie obiektu zamiast prymitywu .

Zakresy początkowo:

wprowadź opis obrazu tutaj

Zakresy po kliknięciu pierwszej pozycji:

wprowadź opis obrazu tutaj

Podsumowując: po raz kolejny problem nie dotyczy zakresu izolowanego ani sposobu działania ngRepeat. Twoim problemem jest to, że łamiesz zasadę, o której wiadomo, że prowadzi do tego właśnie problemu. Modele w AngularJS powinny zawsze mieć rozszerzenie ..

Josh David Miller
źródło
Moje eksperymenty z dyrektywami z izolowanymi zakresami i dwukierunkowym wiązaniem danych doprowadziły mnie do przekonania, że .złota reguła jest wymagana tylko dla zakresów, które mają dziedziczenie prototypowe. W przypadku izolowanych zakresów zmienne, które Cię interesują, są zdefiniowane w scope: {}definicji zawartej w dyrektywie, a zatem od początku te zmienne będą istniały w izolowanym zakresie. Ponadto nie maskują zmiennych nadrzędnych, ponieważ zakres izolowany nie dziedziczy prototypowo z zakresu nadrzędnego. tj. nie ma zasięgu „rodzica” do zamaskowania.
Deepak Nulu,
1
Powinienem był również powiedzieć: Twoja dyrektywa nie tworzy zakresu podrzędnego, ale może być łatwo użyta w kontekście, który wymaga zakresu podrzędnego. ngRepeat to jeden przypadek. Tak samo jest z transkluzją. Zaufaj mi - użyj ..
Josh David Miller
1
Dyskusja na grupie AngularJS Google, gdzie @JoshDavidMiller wreszcie był w stanie oczyścić moje zamieszanie!
Deepak Nulu
2
Skąd macie te wszystkie fajne diagramy? Widziałem, jak inny użytkownik też je publikował.
Asad Saeeduddin
3
@Asad, niedawno umieściłem to narzędzie na GitHub, nazywa się Peri $ scope .
Mark Rajcok
6

Nie próbując bezpośrednio unikać odpowiedzi na pytania, zamiast tego spójrz na następujące skrzypce:

http://jsfiddle.net/dVPLM/

Kluczową kwestią jest to, że zamiast próbować walczyć i zmieniać konwencjonalne zachowanie Angulara, możesz zaprojektować swoją dyrektywę tak, aby działała, zamiast próbować ng-repeatją zastąpić.

W Twoim szablonie:

    <name-row 
        in-names-list="names"
        io-selected="selected">
    </name-row>

W twojej dyrektywie:

    template:
'        <ul>' +      
'            <li ng-repeat="name in inNamesList" ng-class="activeClass($index)" >' +
'                <a ng-click="setSelected($index)">' +
'                    {{$index}} - {{name.first}} {{name.last}}' +
'                </a>' +
'            </li>' +
'        </ul>'

W odpowiedzi na Twoje pytania:

  • ng-repeat utworzy zakres, naprawdę nie powinieneś próbować tego zmieniać.
  • Priorytet w dyrektywach to nie tylko kolejność wykonywania - zobacz: AngularJS: W jaki sposób kompilator HTML ustala kolejność kompilacji?
  • W Batarangu, jeśli zaznaczysz zakładkę wydajności, możesz zobaczyć wyrażenia powiązane dla każdego zakresu i sprawdzić, czy odpowiada to Twoim oczekiwaniom.
Alex Osborn
źródło
Dziękuję za tak szybką odpowiedź. Jeśli to, co próbuję, idzie wbrew oczekiwaniom, jestem trochę zasmucony (mimo wszystko AngularJS to niesamowity framework). Dyrektywa ma być składnikiem wielokrotnego użytku. Kiedy jest napisany, autor nie powinien martwić się, czy zostanie użyty z, ng-repeatczy nie. Może kiedy jest napisany po raz pierwszy, nigdy nie jest używany z ng-repeat. I kiedyś w przyszłości może się z nim przyzwyczaić ng-repeatiw tym momencie powinien po prostu działać bez przepisywania. Miejmy nadzieję, że przyszłe wydanie AngularJS to umożliwi.
Deepak Nulu,
Chciałbym wyjaśnić mój komentarz powyżej. Myślę, że można napisać dyrektywę, która nie martwi się, czy jest używana, ng-repeatczy nie. Wygląda jednak na to, że musiałbym przekazać funkcję do dyrektywy, aby mogła modyfikować zmienne w zakresie nadrzędnym, zamiast móc mutować dwukierunkowe wiązanie w zakresie samej dyrektywy. Dyrektywy wiążące dwukierunkowe i dyrektywy wielokrotnego użytku to moje dwie ulubione rzeczy w AngularJS i ng-repeatokazuje się być muchą w maści. Może mógłbym napisać ng-repeatodpowiednik, który nie tworzy własnego zakresu.
Deepak Nulu,