2013-05-11 11 views
60

Se si preferisce vedere la domanda in codice di lavoro, inizia da qui: http://jsbin.com/ayigub/2/editUtilizzando una direttiva all'interno di un ng-repeat, e un misterioso potere di portata '@'

considerare questo modo quasi equivalente a scrivere un semplice direcive :

app.directive("drinkShortcut", function() { 
    return { 
    scope: { flavor: '@'}, 
    template: '<div>{{flavor}}</div>' 
    }; 
}); 

app.directive("drinkLonghand", function() { 
    return { 
    scope: {}, 
    template: '<div>{{flavor}}</div>', 
    link: function(scope, element, attrs) { 
     scope.flavor = attrs.flavor; 
    } 
    }; 
}); 

quando utilizzate da sole, le due direttive funzionano e si comportano in modo identico:

<!-- This works --> 
    <div drink-shortcut flavor="blueberry"></div> 
    <hr/> 

    <!-- This works --> 
    <div drink-longhand flavor="strawberry"></div> 
    <hr/> 

Tuttavia, quando utilizzato in un ng-repeat, solo la shortc Versione ut funziona:

<!-- Using the shortcut inside a repeat also works --> 
    <div ng-repeat="flav in ['cherry', 'grape']"> 
    <div drink-shortcut flavor="{{flav}}"></div> 
    </div> 
    <hr/> 

    <!-- HOWEVER: using the longhand inside a repeat DOESN'T WORK -->  
    <div ng-repeat="flav in ['cherry', 'grape']"> 
    <div drink-longhand flavor="{{flav}}"></div> 
    </div> 

Le mie domande sono:

  1. Perché la versione Longhand non funziona all'interno di un ng di ripetizione?
  2. Come si può far funzionare la versione longhand all'interno di una ng-repeat?

risposta

100

In drinkLonghand, si utilizza il codice

scope.flavor = attrs.flavor; 

Durante la fase di linking, gli attributi interpolati non sono ancora state valutate, quindi i loro valori sono undefined. (Funzionano al di fuori dello ng-repeat perché in questi casi non si utilizza l'interpolazione delle stringhe, si passa semplicemente a una normale stringa ordinaria, ad esempio "fragola".) Questo è menzionato nello Directives developer guide, insieme a un metodo su Attributes che non è presente in the API documentation chiamato $observe:

Usa $observe per osservare la variazione di valore di attributi che contengono interpolazione (es src="{{bar}}"). Non solo è molto efficiente, ma è anche l'unico modo per ottenere facilmente il valore effettivo perché durante la fase di collegamento l'interpolazione non è stata ancora valutata e quindi il valore è in questo momento impostato su undefined.

Così, per risolvere questo problema, la vostra direttiva drinkLonghand dovrebbe essere così:

app.directive("drinkLonghand", function() { 
    return { 
    template: '<div>{{flavor}}</div>', 
    link: function(scope, element, attrs) { 
     attrs.$observe('flavor', function(flavor) { 
     scope.flavor = flavor; 
     }); 
    } 
    }; 
}); 

Tuttavia, il problema con questo è che non utilizza un ambito isolato; Così, la linea

scope.flavor = flavor; 

ha il potenziale per sovrascrivere una preesistente variabile in ambito denominato flavor. Anche l'aggiunta di un ambito isolato vuoto non funziona; questo perché Angular tenta di interpolare la stringa in base all'ambito della direttiva, su cui non esiste alcun attributo chiamato flav. (È possibile verificare ciò aggiungendo scope.flav = 'test'; sopra la chiamata a attrs.$observe.)

Naturalmente, si potrebbe risolvere il problema con una definizione di ambito isolato come

scope: { flav: '@flavor' } 

o creando un ambito bambino non isolare

scope: true 

o non basandosi su un template con {{flavor}} e invece fare qualche manipolazione DOM diretta come

attrs.$observe('flavor', function(flavor) { 
    element.text(flavor); 
}); 

ma che sconfigge lo scopo dell'esercizio (ad es. sarebbe più semplice usare semplicemente il metodo drinkShortcut). Così, per rendere questa direttiva lavoro, ci rompere le $interpolate service a fare l'interpolazione noi stessi sul $parent campo di applicazione della direttiva:

app.directive("drinkLonghand", function($interpolate) { 
    return { 
    scope: {}, 
    template: '<div>{{flavor}}</div>', 
    link: function(scope, element, attrs) { 
     // element.attr('flavor') == '{{flav}}' 
     // `flav` is defined on `scope.$parent` from the ng-repeat 
     var fn = $interpolate(element.attr('flavor')); 
     scope.flavor = fn(scope.$parent); 
    } 
    }; 
}); 

Naturalmente, questo funziona solo per il valore iniziale di scope.$parent.flav; se il valore è in grado di cambiare, è necessario use $watch e rivalutare il risultato della funzione di interpolazione fn (non sono sicuro in cima alla mia testa come sapresti cosa fare a $watch; potresti dover passare solo in una funzione). scope: { flavor: '@' } è una bella scorciatoia per evitare di dover gestire tutta questa complessità.

[Update]

di rispondere alla domanda dai commenti:

Come è il metodo di scelta rapida soluzione di questo problema dietro le quinte? Sta usando il servizio $ interpolate come hai fatto tu, o sta facendo qualcos'altro?

Non ero sicuro di questo, quindi ho cercato nella fonte. Ho trovato quanto segue in compile.js:

forEach(newIsolateScopeDirective.scope, function(definiton, scopeName) { 
    var match = definiton.match(LOCAL_REGEXP) || [], 
     attrName = match[2]|| scopeName, 
     mode = match[1], // @, =, or & 
     lastValue, 
     parentGet, parentSet; 

    switch (mode) { 

    case '@': { 
     attrs.$observe(attrName, function(value) { 
     scope[scopeName] = value; 
     }); 
     attrs.$$observers[attrName].$$scope = parentScope; 
     break; 
    } 

così sembra che attrs.$observe può essere detto internamente per utilizzare un ambito diverso da quello corrente di basare l'osservazione attributo (la penultima riga, al di sopra della break). Anche se potrebbe essere una tentazione di usarlo tu stesso, tieni presente che qualsiasi cosa con il prefisso double-dollar $$ deve essere considerato privato per l'API privata di Angular ed è soggetto a modifiche senza preavviso (per non parlare del fatto che lo si ottiene gratuitamente quando si usa la modalità @).

+0

Brandon, questa è una risposta davvero fantastica, grazie. Solo una domanda successiva (sentiti libero di aggiungere come modifica): come è il metodo di scelta rapida per risolvere questo problema dietro le quinte? Sta usando il servizio $ interpolate come hai fatto tu, o sta facendo qualcos'altro? – Jonah

+0

Grazie! Lieto che tu la pensi così. Non ero sicuro di questo, quindi ho trovato la soluzione e ho aggiornato la mia risposta. –

+1

Grazie ancora. Questa è stata davvero una delle risposte più esaurienti a una domanda un po 'difficile che ho ricevuto su SO, e * esattamente * quello che stavo cercando. Vorrei poterlo alterare di più. Ma a proposito, potresti essere interessato a questa domanda che ti ho chiesto stamattina, un potenziale errore nei documenti angolari, e probabilmente molto più facile rispondere di questo: http://stackoverflow.com/questions/16495684/angular- controllori-come-sono-questi-metodi-magici-lavorando-in-questa-unità-test – Jonah

Problemi correlati