2013-07-14 8 views
21

Ho una semplice direttiva in cui il modello utilizza ng-repeat al suo interno. Ho bisogno di eseguire del codice per istanziare un componente jquery contro alcuni degli elementi creati dalla direttiva ng-repeat. Il problema è che se inserisco questo codice nella funzione di collegamento. La ng-repeat non ha ancora costruito quegli elementi in modo tale che nulla venga istanziato.AngularJS: collegamento a elementi in una direttiva che utilizza ng-repeat

App.directive('myDirective', ['$compile', '$timeout', function($compile, $timeout) { 
    return { 
    scope: { 
     domains: '=' 
    }, 
    templateUrl: '/app/partials/my_directive.html', 
    link: function($scope, element, attributes) { 
     element.find('.standard-help').tooltip('destroy'); 
     element.find('.standard-help').tooltip({placement: 'top', trigger: 'click hover focus'}); 
    } 
    }; 
} 

Il modello sarà simile al seguente. Sto cercando di collegare

<ul class="media-list domainList"> 
    <li class="media" style="position:relative;" ng-repeat="domain in domains"> 
    <a class="domainHeader" href="javascript://"> 
     <span class="domainHeader">{{domain.tag}}</span> 
    </a> 
    <div class="media-body" style="margin-left: 52px;"> 
     <ul class="standardsList"> 
      <li ng-class="{ standardDisplayed: lessonLayout == 'standards' }" ng-hide="standard.lessons.length == 0" ng-repeat="standard in domain.standards"> 
       <a href="javascript://" title="{{standard.description}}" ng-show="lessonLayout == 'standards'" class="standard-help pull-right"><i class="icon-question-sign"></i></a> 
       <h6 ng-show="lessonLayout == 'standards'">{{standard.tag}}</h6> 
       <ul class="lessonsList"> 
        <li ng-class="{ lesson: true }" ng-repeat="lesson in standard.lessons" ng-click="onLessonSelected(lesson)"> 
         <i class="icon-lock lesson-locked"></i> 
         <div>{{lesson.title}}</div> 
        </li> 
       </ul> 
      </li> 
     </ul> 
    </div> 
    </li> 
</ul> 

Ho provato con $ watch() e $ osservare() per registrare un callback quando i domini cambiano e creare un'istanza del codice tooltip poi. Tuttavia, non riesco a convincermi a chiamarmi al momento giusto. Qualche idea su cosa mi manca?

+0

Forse si potrebbe forzare la compilazione dalla funzione di collegamento. element = angular.element ($ compile (template) (scope)); – dimirc

+0

Se il problema è in attesa del rendering di dom, è possibile includere gli elementi della funzione di collegamento in un timeout $. '$ Timeout (function() {element.find (blabla); element.find (foofoo);}, 0)'. Se questa non è la soluzione, allora il problema non risiede nel rendering dom. – rGil

+3

Sembra che questo sarebbe un problema comune, quindi devo pensare che ci sono modi migliori per farlo senza ricorrere a hack come usare timeout. – chubbsondubs

risposta

23

Ho scoperto che se è stata creata un'altra direttiva che ho aggiunto all'elemento in cui la ng-repeat era stata creata, sarebbe stata notificata per ogni elemento la ripetizione creata. Quindi potrei semplicemente $ emettere un evento che la direttiva genitore potrebbe ascoltare. A quel punto potrebbe eseguire il collegamento agli elementi ripetuti lì. Questo ha funzionato molto bene specialmente per più ripetizioni di ng all'interno della dom perché potevo separarle dal loro tipo di evento che sarebbe passato alla nuova direttiva.

Qui è la mia direttiva:

App.directive("onRepeatDone", function() { 
    return { 
     restrict: 'A', 
     link: function($scope, element, attributes) { 
      $scope.$emit(attributes["onRepeatDone"] || "repeat_done", element); 
     } 
    } 
}); 

Qui è l'utilizzo di questa nuova direttiva nel modello:

<ul> 
    <li on-repeat-done="domain_done" ng-repeat="domain in domains">...</li> 
</ul> 

Poi all'interno della direttiva madre ho potuto effettuare le seguenti operazioni:

link: function($scope, element, attributes) { 
    $scope.$on('domain_done', function(domainElement) { 
     domainElement.find('.someElementInsideARepeatedElement').click(...); 
    }); 
} 
+0

Grazie, questa è una risposta molto utile. Ho una domanda correlata: esiste un modello standard per rilevare che tutte le ripetizioni sono state fatte? Potrei non voler licenziare un evento per ogni bambino aggiunto, ma solo una volta al termine. – mariachimike

+4

È possibile inserire un'istruzione if nella direttiva per attivare solo lo scope.it == ambito finale. $ Last. – chubbsondubs

+0

Questo è stato estremamente utile - grazie! – mariachimike

10

$watch dovrebbe essere in grado di ottenere ciò che si vuole fare:

link: function(scope, elem, attrs) { 
    var init = function() { 
     // your initialization code 
    }; 
    var ulElem = elem.children()[0]; // ul element 

    var unreg = scope.$watch(function() { 
     return ulElem.children.length === scope.domains.length; // check if all "li" are generated 
    }, function() { 
     // at this point, the ul is rendered 
     init(); 
     unreg(); // unregister the watcher for performance, since the init function only need to be called once 
    }); 
} 

Ho creato un plunk per dimostrare questa soluzione.

+0

Grazie per la risposta, ma ho trovato un modo più efficace di gestirlo. – chubbsondubs

+0

Grazie. Stavo cercando di tenere traccia quando le repliche da 4 ng erano finite e questa era una soluzione migliore rispetto all'approccio direttivo della direttiva chubbsondubs. – scottt732

+2

Mi è piaciuto anche questo approccio - mi sembrava eccessivo avere un'altra direttiva. Grazie. –

1

Penso che questo sia dovuto al bug angolare. Puoi vedere questo here

Una soluzione è di rimuovere l'url modello e utilizzare il modello nella direttiva stessa o utilizzare $ templateCache.get ('/ app/parziali/my_directive.html')

Questo ha funzionato per me :)

-1

$watch dovrebbe funzionare in questo contesto. provalo

2

Ho appena trovato questo problema esatto e ho trovato quello che credo sia una soluzione migliore. Fondamentalmente, è possibile utilizzare $timeout per accodare un'attività dopo che il ng-repeat s ha terminato il rendering.

Nota: questo non significa che si sta effettivamente utilizzando il timer o qualsiasi tipo di ritardo; è solo un modo semplice per aggiungere una funzione alla fine del ciclo $digest.

Di seguito è riportato un estratto del mio codice.La direttiva utilizza ng-repeat per visualizzare le schede, quindi, devo aspettare fino a dopo che è stato eseguito il rendering per eseguire il mio metodo tabSelected() che aggiunge la classe active all'elemento HTML.

link($scope:ng.IScope, element:JQuery, attributes:ng.IAttributes):void { 
    if (this.autoActivateFirstTab && this.tabs && this.tabs.length > 0) { 
    var self = this; 

    var selectFirstTab = function() { 
     self.tabSelected(self.tabs[0]); 
    }; 

    // Add a $timeout task to the end of the $digest loop to select the 
    // first tab after the loop finishes 
    self.$timeout(selectFirstTab, 0); 
    } 
} 

ho trovato la soluzione da http://blog.brunoscopelliti.com/run-a-directive-after-the-dom-has-finished-rendering che ha una live demo che mostra in azione.

Parole di separazione: Se si sta verificando il codice (come si dovrebbe) con Karma, è necessario chiamare $timeout.flush() nel test per assicurarsi che venga eseguito.

Finii dover eseguire il seguente all'interno del mio test per farlo funzionare correttamente:

// Re-digest to ensure we see the changes 
scope.$digest(); 

// Flush any pending $timeout tasks to ensure we see changes 
try { 
    this.$timeout.verifyNoPendingTasks(); 
} catch (aException) { 
    this.$timeout.flush(); 
} 
+0

L'utilizzo di timeout $ non è mai una soluzione migliore quando è possibile utilizzare correttamente gli eventi. L'uso dei timer dipende tanto dalla piattaforma sottostante che esegue il codice. Ciò significa che può essere molto inaffidabile perché una CPU del telefono non è la stessa di un desktop e, a seconda di ciò che sta succedendo sulla CPU, potrebbe comunque rovinare. Quindi potrebbe funzionare una volta quindi non funzionare perché la CPU stava facendo qualcosa di intenso. La linea di fondo è che non è affidabile. Per non parlare del fatto che di solito devi giocarlo in modo molto conservativo per assicurarti di non accidentalmente farlo scoppiare nel mezzo del ripetizione del rendering, quindi è sempre più lento. – chubbsondubs

+0

@chubbsondubs Sono d'accordo in generale, ma il punto di questo '$ timeout' non è in realtà aspettare un particolare periodo di tempo; semplicemente aggiunge un evento alla fine del ciclo corrente '$ digest', quindi dovrebbe funzionare su qualsiasi piattaforma in quanto non dipende da alcun tempo effettivo (quindi perché il numero di millisecondi è 0). Questo cambia la tua visione di questa particolare soluzione? Mi piace sempre il feedback :-) –

+1

Questa è la soluzione migliore. – JayIsTooCommon

Problemi correlati