Direttiva isolare l'ambito con ambito ng-repeat in AngularJS


95

Ho una direttiva con un ambito isolato (in modo da poter riutilizzare la direttiva in altri luoghi) e quando uso questa direttiva con un ng-repeat, non funziona.

Ho letto tutta la documentazione e le risposte di Stack Overflow su questo argomento e ho compreso i problemi. Credo di aver evitato tutti i soliti trucchi.

Quindi capisco che il mio codice fallisce a causa dell'ambito creato dalla ng-repeatdirettiva. La mia direttiva crea un ambito isolato ed esegue un'associazione dati bidirezionale a un oggetto nell'ambito padre. La mia direttiva assegnerà un nuovo valore oggetto a questa variabile associata e questo funziona perfettamente quando la mia direttiva viene utilizzata senza ng-repeat(la variabile genitore viene aggiornata correttamente). Tuttavia, con ng-repeat, l'assegnazione crea una nuova variabile ng-repeatnell'ambito e la variabile padre non vede la modifica. Tutto questo è come previsto in base a ciò che ho letto.

Ho anche letto che quando ci sono più direttive su un dato elemento, viene creato un solo ambito. E che a prioritypuò essere impostato in ogni direttiva per definire l'ordine in cui le direttive vengono applicate; le direttive sono ordinate per priorità e quindi vengono chiamate le loro funzioni di compilazione (cercare la parola priorità su http://docs.angularjs.org/guide/directive ).

Quindi speravo di poter usare la priorità per assicurarmi che la mia direttiva ng-repeatvenga eseguita per prima e finisca per creare un ambito isolato e, quando viene eseguito, riutilizza l'ambito isolato invece di creare un ambito che eredita in modo prototipico dall'ambito padre. La ng-repeatdocumentazione afferma che tale direttiva viene eseguita a livello di priorità 1000. Non è chiaro se 1sia un livello di priorità più alto o un livello di priorità più basso. Quando ho usato il livello di priorità 1nella mia direttiva, non ha fatto la differenza, quindi ho provato 2000. Ma questo peggiora le cose: le mie associazioni a due vie diventano undefinede la mia direttiva non mostra nulla.

Ho creato un violino per mostrare il mio problema . Ho commentato l' priorityimpostazione nella mia direttiva. Ho un elenco di oggetti nome e una direttiva chiamata name-rowche mostra i campi nome e cognome nell'oggetto nome. Quando si fa clic su un nome visualizzato, voglio che imposti una selectedvariabile nell'ambito principale. L'array di nomi, la selectedvariabile viene passata alla name-rowdirettiva utilizzando l'associazione dati a due vie.

So come farlo funzionare chiamando le funzioni nell'ambito principale. So anche che se si selectedtrova all'interno di un altro oggetto e mi lego all'oggetto esterno, le cose funzionerebbero. Ma al momento non sono interessato a queste soluzioni.

Invece, le domande che ho sono:

  • Come posso impedire la ng-repeatcreazione di un ambito che eredita in modo prototipico dall'ambito padre e invece utilizzare l'ambito isolato della mia direttiva?
  • Perché il livello di priorità 2000nella mia direttiva non funziona?
  • Utilizzando Batarang, è possibile sapere che tipo di ambito è in uso?

1
Normalmente, non si desidera utilizzare un ambito isolato se la direttiva verrà utilizzata sullo stesso elemento con altre direttive. Dato che stai creando le tue proprietà di ambito e devi lavorare con ng-repeat, ti suggerisco di usare scope: trueper la tua direttiva. Vedi anche (se non l'hai già fatto) stackoverflow.com/questions/14914213/… Inoltre, solo perché una direttiva verrà utilizzata in più posizioni non significa che dovremmo usare automaticamente un ambito isolato.
Mark Rajcok

Ho letto molte delle tue risposte (sono più che eccellenti, grazie per averle scritte), ma non mi è mai venuto in mente di leggere le tue domande :-). Ho letto a cosa ti sei collegato. Mi sembra che le direttive con ambito isolato non possano essere mescolate con altre direttive. Concordo con l'opinione che tali direttive siano componenti e quindi non debbano essere mescolate con altre direttive. L'unica eccezione (finora) per me sarebbe ng-repeat. Penso che sia prezioso poter combinare direttive autonome con ng-repeat. Continua ...
Deepak Nulu

Continua dall'alto ... Quindi, se dovrebbe esserci una sola direttiva con un ambito per un elemento, allora ng-repeatnon dovrebbe avere un ambito. ng-repeatavere un ambito ha senso per il tipico caso d'uso, quindi non sto suggerendo che venga modificato. Invece, come ho commentato nella risposta di Alex Osborn, penso che creerò una direttiva di ripetizione basata su ng-repeatquella che non crea il proprio ambito. Questo può quindi essere utilizzato per ripetere le direttive che hanno i propri ambiti isolati. Continua ...
Deepak Nulu

Il codice che ripete una direttiva ora deve sapere se usare ng-repeato la direttiva di ripetizione personalizzata senza ambito. Penso che sia giusto che il "chiamante" lo sappia, ma non va bene che un "chiamato" (la direttiva venga ripetuta) sappia se viene ripetuto o meno. Continua ...
Deepak Nulu

Diventando un po 'pazzo con i commenti qui ... :-) ngRepeat deve creare il proprio ambito. Perché ritieni di aver bisogno di un ambito isolato qui?
Josh David Miller

Risposte:


203

Ok, attraverso molti dei commenti sopra, ho scoperto la confusione. Innanzitutto, un paio di punti di chiarimento:

  • ngRepeat non influisce sull'ambito di isolamento scelto
  • i parametri passati in ngRepeat per l'uso su attributi del vostro direttiva non utilizzano un ambito prototipicamente-ereditaria
  • il motivo per cui la tua direttiva non funziona non ha nulla a che fare con l'ambito isolato

Ecco un esempio dello stesso codice ma con la direttiva rimossa:

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

Ecco un JSFiddle che dimostra che non funzionerà. Ottieni esattamente gli stessi risultati della tua direttiva.

Perché non funziona? Perché gli ambiti in AngularJS usano l'ereditarietà prototipica. Il valore selectedsull'ambito genitore è una primitiva . In JavaScript, questo significa che verrà sovrascritto quando un figlio imposta lo stesso valore. Esiste una regola d'oro negli ambiti AngularJS: i valori del modello dovrebbero sempre contenere un .. Cioè, non dovrebbero mai essere primitivi. Vedi questa risposta SO per maggiori informazioni.


Ecco un'immagine di come appaiono inizialmente gli ambiti.

inserisci qui la descrizione dell'immagine

Dopo aver fatto clic sul primo elemento, gli ambiti ora hanno questo aspetto:

inserisci qui la descrizione dell'immagine

Si noti che è selectedstata creata una nuova proprietà nell'ambito ngRepeat. L'ambito del controller 003 non è stato modificato.

Probabilmente puoi indovinare cosa succede quando clicchiamo sul secondo elemento:

inserisci qui la descrizione dell'immagine


Quindi il tuo problema non è affatto causato da ngRepeat - è causato dall'infrangere una regola d'oro in AngularJS. Il modo per risolverlo è semplicemente usare una proprietà dell'oggetto:

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

Ecco un secondo JSFiddle che mostra anche che funziona.

Ecco come appaiono inizialmente gli ambiti:

inserisci qui la descrizione dell'immagine

Dopo aver fatto clic sul primo elemento:

inserisci qui la descrizione dell'immagine

Qui, l'ambito del controller viene influenzato, come desiderato.

Inoltre, per dimostrare che questo funzionerà ancora con la tua direttiva con un ambito isolato (perché, ancora una volta, questo non ha nulla a che fare con il tuo problema), ecco un JSFiddle anche per questo, la vista deve riflettere l'oggetto. Noterai che l'unica modifica necessaria è stata quella di utilizzare un oggetto invece di una primitiva .

Obiettivi inizialmente:

inserisci qui la descrizione dell'immagine

Ambiti dopo aver fatto clic sul primo elemento:

inserisci qui la descrizione dell'immagine

Per concludere: ancora una volta, il tuo problema non è con l'ambito isolato e non è con il modo in cui funziona ngRepeat. Il tuo problema è che stai infrangendo una regola che è nota per portare a questo stesso problema. I modelli in AngularJS dovrebbero sempre avere un file ..


I miei esperimenti con le direttive con gli ambiti isolati e l'associazione dati a 2 vie mi hanno portato a credere che la .regola d'oro sia richiesta solo per gli ambiti che hanno un'eredità prototipica. Con gli ambiti isolati, le variabili a cui sei interessato sono definite nella scope: {}definizione nella direttiva, e quindi tali variabili esisteranno nell'ambito isolato dall'inizio. Inoltre, non mascherano le variabili padre perché l'ambito dell'isolato non eredita in modo prototipico dall'ambito padre. cioè non c'è alcun ambito "genitore" da mascherare.
Deepak Nulu

1
Avrebbe dovuto anche dire: la tua direttiva non sta creando un ambito figlio, ma potrebbe essere facilmente utilizzato in un contesto che richiede un ambito figlio. ngRepeat è un caso. Così è la transclusione. Fidati di me - usa il ..
Josh David Miller

1
Discussione sul gruppo Google di AngularJS dove @JoshDavidMiller è riuscito finalmente a chiarire la mia confusione!
Deepak Nulu

2
Da dove prendete tutti questi fantastici diagrammi? Ho visto anche un altro utente pubblicare questi.
Asad Saeeduddin

3
@ Asad, ho appena messo lo strumento su GitHub, si chiama Peri $ scope .
Mark Rajcok

6

Senza cercare direttamente di evitare di rispondere alle tue domande, dai un'occhiata al seguente violino:

http://jsfiddle.net/dVPLM/

Il punto chiave è che invece di cercare di combattere e modificare il comportamento convenzionale di Angular, potresti strutturare la tua direttiva su cui lavorare ng-repeatinvece di provare a sovrascriverla.

Nel tuo modello:

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

Nella tua direttiva:

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

In risposta alle tue domande:


Grazie per una risposta così rapida. Se quello che sto tentando va contro il grano, sono un po 'rattristato (AngularJS è comunque un framework fantastico). Una direttiva è concepita per essere un componente riutilizzabile. Quando è scritto, l'autore non dovrebbe preoccuparsi se verrà utilizzato con ng-repeato meno. Forse quando viene scritto per la prima volta, non viene mai utilizzato con ng-repeat. E un po 'di tempo in futuro, potrebbe abituarsi ng-repeate, a quel punto, dovrebbe funzionare senza essere riscritto. Si spera che una futura versione di AngularJS lo renderà possibile.
Deepak Nulu

Vorrei chiarire il mio commento sopra. Penso che sia possibile scrivere una direttiva che non si preoccupi del fatto che venga utilizzata ng-repeato meno. Ma sembra che dovrei passare una funzione alla direttiva in modo che possa modificare le variabili nello scope genitore, invece di essere in grado di mutare un'associazione bidirezionale nello scope stesso della direttiva. Le direttive vincolanti a due vie e riutilizzabili sono le mie due cose preferite di AngularJS e si ng-repeatstanno rivelando un neo. Forse posso scrivere un ng-repeatequivalente che non crea un proprio ambito.
Deepak Nulu
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.