Direktive Isolation Scope mit ng-Repeat Scope in AngularJS

95

Ich habe eine Direktive mit einem isolierten Gültigkeitsbereich (damit ich die Direktive an anderen Stellen wiederverwenden kann), und wenn ich diese Direktive mit einem verwende ng-repeat, funktioniert sie nicht.

Ich habe die gesamte Dokumentation und die Antworten zum Stapelüberlauf zu diesem Thema gelesen und verstehe die Probleme. Ich glaube, ich habe alle üblichen Fallstricke vermieden.

Ich verstehe also, dass mein Code aufgrund des durch die ng-repeatDirektive erstellten Bereichs fehlschlägt . Meine eigene Direktive erstellt einen Isolate-Scope und führt eine bidirektionale Datenbindung an ein Objekt im übergeordneten Scope durch. Meine Direktive weist dieser gebundenen Variablen einen neuen Objektwert zu. Dies funktioniert einwandfrei, wenn meine Direktive ohne verwendet wird ng-repeat(die übergeordnete Variable wird korrekt aktualisiert). Mit ng-repeaterstellt die Zuweisung jedoch eine neue Variable im ng-repeatBereich und die übergeordnete Variable sieht die Änderung nicht. All dies basiert wie erwartet auf dem, was ich gelesen habe.

Ich habe auch gelesen, dass bei mehreren Anweisungen für ein bestimmtes Element nur ein Bereich erstellt wird. Und dass priorityin jeder Direktive ein a gesetzt werden kann, um die Reihenfolge zu definieren, in der die Direktiven angewendet werden; Die Anweisungen werden nach Priorität sortiert und anschließend ihre Kompilierungsfunktionen aufgerufen (Suche nach dem Wort Priorität unter http://docs.angularjs.org/guide/directive ).

Ich hatte gehofft, dass ich die Priorität verwenden kann, um sicherzustellen, dass meine Direktive zuerst ausgeführt wird und am Ende einen isolierten Bereich erstellt. Wenn ng-repeatsie ausgeführt wird, wird der isolierte Bereich wiederverwendet, anstatt einen Bereich zu erstellen, der prototypisch vom übergeordneten Bereich erbt. In der ng-repeatDokumentation heißt es, dass diese Richtlinie auf Prioritätsebene ausgeführt wird 1000. Es ist nicht klar, ob 1es sich um eine höhere oder eine niedrigere Prioritätsstufe handelt. Als ich 1in meiner Direktive die Prioritätsstufe verwendet habe, hat dies keinen Unterschied gemacht, also habe ich es versucht 2000. Aber das macht die Sache noch schlimmer: Meine wechselseitigen Bindungen werden undefinedund meine Direktive zeigt nichts an.

Ich habe eine Geige erstellt, um mein Problem zu zeigen . Ich habe die priorityEinstellung in meiner Richtlinie auskommentiert. Ich habe eine Liste von Namensobjekten und eine Direktive namens name-row, die die Vor- und Nachnamenfelder im Namensobjekt anzeigt. Wenn auf einen angezeigten Namen geklickt wird, soll eine selectedVariable im Hauptbereich festgelegt werden. Das Array von Namen und die selectedVariable werden name-rowmithilfe der bidirektionalen Datenbindung an die Direktive übergeben.

Ich weiß, wie dies funktioniert, indem Funktionen im Hauptbereich aufgerufen werden. Ich weiß auch, dass selectedDinge funktionieren würden , wenn sie sich in einem anderen Objekt befinden und ich mich an das äußere Objekt binde. Aber ich bin im Moment nicht an diesen Lösungen interessiert.

Stattdessen habe ich folgende Fragen:

  • Wie kann ich verhindern, ng-repeatdass ein Bereich erstellt wird, der prototypisch vom übergeordneten Bereich erbt, und stattdessen den Isolate-Bereich meiner Direktive verwenden?
  • Warum 2000funktioniert die Prioritätsstufe in meiner Richtlinie nicht?
  • Ist es mit Batarang möglich zu wissen, welche Art von Oszilloskop verwendet wird?
Deepak Nulu
quelle
1
Normalerweise möchten Sie keinen isolierten Bereich verwenden, wenn Ihre Direktive für dasselbe Element wie andere Direktiven verwendet wird. Da Sie Ihre eigenen Bereichseigenschaften erstellen und mit ng-repeat arbeiten müssen, empfehle ich die Verwendung scope: truefür Ihre Direktive. Siehe auch (falls Sie dies noch nicht getan haben) stackoverflow.com/questions/14914213/… Nur weil eine Direktive an mehreren Stellen verwendet wird, bedeutet dies nicht, dass wir automatisch einen isolierten Bereich verwenden sollten.
Mark Rajcok
Ich habe viele Ihrer Antworten gelesen (sie sind überragend, danke, dass Sie sie geschrieben haben), aber mir ist nie in den Sinn gekommen, Ihre Fragen zu lesen :-). Ich habe gelesen, worauf du verlinkt hast. Es scheint mir, dass Direktiven mit isoliertem Geltungsbereich nicht mit anderen Direktiven gemischt werden können. Ich stimme dem Gefühl zu, dass solche Richtlinien Bestandteile sind und daher nicht mit anderen Richtlinien gemischt werden müssen. Die einzige Ausnahme (bisher) für mich wäre ng-repeat. Ich denke, es ist wertvoll, eigenständige Direktiven mit mischen zu können ng-repeat. Fortsetzung
folgt
Fortsetzung von oben ... Wenn es also nur eine Direktive mit einem Gültigkeitsbereich für ein Element geben sollte, ng-repeatsollte dies keinen Gültigkeitsbereich haben. ng-repeatEin Geltungsbereich ist für den typischen Anwendungsfall sinnvoll, daher schlage ich nicht vor, ihn zu ändern. Stattdessen denke ich, wie ich in Alex Osborns Antwort kommentiert habe, dass ich eine Wiederholungsanweisung erstellen werde, die darauf basiert und ng-repeatkeinen eigenen Geltungsbereich schafft. Dies kann dann zum Wiederholen von Anweisungen verwendet werden, die ihre eigenen Isolatbereiche haben. Fortsetzung
folgt
Der Code, der eine Direktive wiederholt, muss jetzt wissen, ob er verwendet werden soll ng-repeatoder die benutzerdefinierte Wiederholungsanweisung ohne Gültigkeitsbereich. Ich denke, es ist in Ordnung, wenn der "Anrufer" dies weiß, aber es ist nicht in Ordnung, wenn ein "Angerufener" (die Anweisung wird wiederholt) weiß, ob er wiederholt wird oder nicht. Fortsetzung
folgt
Mit den Kommentaren hier ein bisschen verrückt werden ... :-) ngRepeat muss einen eigenen Bereich erstellen. Warum brauchen Sie hier einen isolierten Bereich?
Josh David Miller

Antworten:

203

Okay, durch viele der obigen Kommentare habe ich die Verwirrung entdeckt. Zunächst einige Klarstellungen:

  • ngRepeat hat keinen Einfluss auf den von Ihnen gewählten Isolatbereich
  • die Parameter in ngRepeat auf Attribute zur Verwendung übergeben Ihre Richtlinie der Sie verwenden , um eine prototypisch vererbte Umfang
  • Der Grund, warum Ihre Direktive nicht funktioniert, hat nichts mit dem isolierten Bereich zu tun

Hier ist ein Beispiel für denselben Code, wobei die Direktive entfernt wurde:

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

Hier ist eine JSFiddle, die zeigt, dass es nicht funktioniert. Sie erhalten genau die gleichen Ergebnisse wie in Ihrer Richtlinie.

Warum funktioniert es nicht? Weil Bereiche in AngularJS prototypische Vererbung verwenden. Der Wert selectedin Ihrem übergeordneten Bereich ist ein Grundelement . In JavaScript bedeutet dies, dass es überschrieben wird, wenn ein untergeordnetes Element denselben Wert festlegt. In AngularJS-Bereichen gibt es eine goldene Regel: Modellwerte sollten immer verwendet werden mit einem versehen .sein. Das heißt, sie sollten niemals primitiv sein. Weitere Informationen finden Sie in dieser SO-Antwort .


Hier ist ein Bild davon, wie die Bereiche zunächst aussehen.

Geben Sie hier die Bildbeschreibung ein

Nach dem Klicken auf das erste Element sehen die Bereiche nun folgendermaßen aus:

Geben Sie hier die Bildbeschreibung ein

Beachten Sie, dass selectedim Bereich ngRepeat eine neue Eigenschaft erstellt wurde. Der Controller-Bereich 003 wurde nicht geändert.

Sie können wahrscheinlich erraten, was passiert, wenn wir auf den zweiten Punkt klicken:

Geben Sie hier die Bildbeschreibung ein


Ihr Problem wird also überhaupt nicht durch ngRepeat verursacht, sondern durch das Brechen einer goldenen Regel in AngularJS. Die Möglichkeit, dies zu beheben, besteht darin, einfach eine Objekteigenschaft zu verwenden:

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

Hier ist eine zweite JSFiddle zeigt, dass dies auch funktioniert.

So sehen die Bereiche zunächst aus:

Geben Sie hier die Bildbeschreibung ein

Nach dem Klicken auf den ersten Eintrag:

Geben Sie hier die Bildbeschreibung ein

Hier wird der Controller-Bereich wie gewünscht beeinflusst.

Um zu beweisen, dass dies weiterhin mit Ihrer Direktive mit einem isolierten Bereich funktioniert (da dies wiederum nichts mit Ihrem Problem zu tun hat), ist hier auch eine JSFiddle , die Ansicht muss das Objekt widerspiegeln. Sie werden feststellen, dass die einzige notwendige Änderung darin bestand, ein Objekt anstelle eines Grundelements zu verwenden .

Geltungsbereich zunächst:

Geben Sie hier die Bildbeschreibung ein

Geltungsbereich nach dem Klicken auf das erste Element:

Geben Sie hier die Bildbeschreibung ein

Fazit: Auch hier liegt Ihr Problem nicht im isolierten Bereich und nicht in der Funktionsweise von ngRepeat. Ihr Problem ist, dass Sie gegen eine Regel verstoßen, von der bekannt ist, dass sie genau zu diesem Problem führt. Modelle in AngularJS sollten immer eine haben ..

Josh David Miller
quelle
Meine Experimente mit Direktiven mit Isolat-Scopes und 2-Wege-Datenbindung lassen mich glauben, dass die .goldene Regel nur für Scopes mit prototypischer Vererbung erforderlich ist. Bei Isolate-Scopes werden die Variablen, an denen Sie interessiert sind, in der scope: {}Definition in der Direktive definiert, und daher sind diese Variablen von Anfang an im Isolate-Scope vorhanden. Außerdem maskieren sie die übergeordneten Variablen nicht, da der Isolatbereich nicht prototypisch vom übergeordneten Bereich erbt. dh es gibt keinen "übergeordneten" Bereich zum Maskieren.
Deepak Nulu
1
Sollte haben auch gesagt: Ihre Richtlinie ist ein Kind Umfang nicht zu schaffen, aber es könnte leicht werden verwendet in einem Kontext, erfordert ein Kind Umfang. ngRepeat ist ein Fall. Transklusion auch. Vertrau mir - benutze die ..
Josh David Miller
1
Diskussion über die AngularJS Google Group, in der @JoshDavidMiller endlich meine Verwirrung beseitigen konnte!
Deepak Nulu
2
Woher bekommt ihr all diese coolen Diagramme? Ich habe auch einen anderen Benutzer gesehen, der diese gepostet hat.
Asad Saeeduddin
3
@Asad, ich habe das Tool erst kürzlich auf GitHub veröffentlicht. Es heißt Peri $ scope .
Mark Rajcok
6

Schauen Sie sich stattdessen die folgende Geige an, ohne direkt zu versuchen, die Beantwortung Ihrer Fragen zu vermeiden:

http://jsfiddle.net/dVPLM/

Der entscheidende Punkt ist, dass Sie, anstatt zu versuchen, das konventionelle Verhalten von Angular zu bekämpfen und zu ändern, Ihre Direktive so strukturieren können, dass Sie damit arbeiten, anstatt ng-repeatzu versuchen, sie zu überschreiben.

In Ihrer Vorlage:

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

In Ihrer Richtlinie:

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

Als Antwort auf Ihre Fragen:

  • ng-repeat Wenn Sie einen Bereich erstellen, sollten Sie wirklich nicht versuchen, dies zu ändern.
  • Priorität in Direktiven ist nicht nur die Ausführungsreihenfolge - siehe: AngularJS: Wie ordnet der HTML-Compiler die Reihenfolge für das Kompilieren an?
  • Wenn Sie in Batarang die Registerkarte Leistung überprüfen, können Sie die für jeden Bereich gebundenen Ausdrücke anzeigen und prüfen, ob dies Ihren Erwartungen entspricht.
Alex Osborn
quelle
Vielen Dank für eine so schnelle Antwort. Wenn das, was ich versuche, gegen den Strich geht, bin ich ein bisschen traurig (AngularJS ist dennoch ein großartiger Rahmen). Eine Richtlinie soll eine wiederverwendbare Komponente sein. Wenn es geschrieben wird, sollte sich der Autor keine Gedanken darüber machen müssen, ob es mit einem verwendet wird ng-repeatoder nicht. Vielleicht wird es beim ersten Schreiben nie verwendet ng-repeat. Und irgendwann in der Zukunft könnte es sich daran gewöhnen ng-repeat, und zu diesem Zeitpunkt sollte es einfach ohne Umschreiben funktionieren. Hoffentlich wird eine zukünftige Version von AngularJS dies ermöglichen.
Deepak Nulu
Ich möchte meinen obigen Kommentar klarstellen. Ich denke, es ist möglich, eine Direktive zu schreiben, die sich keine Gedanken darüber macht, ob sie verwendet wird ng-repeatoder nicht. Es scheint jedoch, dass ich eine Funktion an die Direktive übergeben müsste, damit sie die Variablen im übergeordneten Bereich ändern kann, anstatt eine bidirektionale Bindung im eigenen Bereich der Direktive mutieren zu können. Zwei-Wege-Bindungs- und wiederverwendbare Anweisungen sind meine beiden Lieblingssachen bei AngularJS und ng-repeaterweisen sich als eine Fliege in der Salbe. Vielleicht kann ich ein ng-repeatÄquivalent schreiben , das keinen eigenen Bereich schafft.
Deepak Nulu