J'ai un modèle angulaire dans le DOM. Lorsque mon contrôleur obtient de nouvelles données d'un service, il met à jour le modèle dans la portée $ et rend à nouveau le modèle. Tout va bien jusqu'à présent.
Le problème est que je dois également faire un travail supplémentaire après que le modèle a été re-rendu et se trouve dans le DOM (dans ce cas, un plugin jQuery).
Il semble qu'il devrait y avoir un événement à écouter, comme AfterRender, mais je ne trouve rien de tel. Une directive serait peut-être une voie à suivre, mais elle semblait également être déclenchée trop tôt.
Voici un jsFiddle décrivant mon problème: Fiddle-AngularIssue
== MISE À JOUR ==
Sur la base de commentaires utiles, je suis donc passé à une directive pour gérer la manipulation du DOM et implémenté un modèle $ watch à l'intérieur de la directive. Cependant, j'ai toujours le même problème de base; le code à l'intérieur de l'événement $ watch se déclenche avant que le modèle n'ait été compilé et inséré dans le DOM, par conséquent, le plugin jquery évalue toujours une table vide.
Fait intéressant, si je supprime l'appel asynchrone, tout fonctionne bien, c'est donc un pas dans la bonne direction.
Voici mon Fiddle mis à jour pour refléter ces changements: http://jsfiddle.net/uNREn/12/
la source
Réponses:
Ce message est ancien, mais je change votre code en:
scope.$watch("assignments", function (value) {//I change here var val = value || null; if (val) element.dataTable({"bDestroy": true}); }); }
voir jsfiddle .
J'espère que ça t'aide
la source
Premièrement, les directives sont au bon endroit pour jouer avec le rendu. Mon conseil serait d'encapsuler le DOM manipulant les plugins jQuery par des directives comme celle-ci.
J'ai eu le même problème et j'ai trouvé cet extrait. Il utilise
$watch
et$evalAsync
pour garantir que votre code s'exécute après que des directives commeng-repeat
ont été résolues et des modèles comme ont{{ value }}
été rendus.app.directive('name', function() { return { link: function($scope, element, attrs) { // Trigger when number of children changes, // including by directives like ng-repeat var watch = $scope.$watch(function() { return element.children().length; }, function() { // Wait for templates to render $scope.$evalAsync(function() { // Finally, directives are evaluated // and templates are renderer here var children = element.children(); console.log(children); }); }); }, }; });
J'espère que cela peut vous aider à éviter certaines difficultés.
la source
link
fonction avec untemplateUrl
.Suivant les conseils de Misko, si vous voulez une opération asynchrone, alors au lieu de $ timeout () (qui ne fonctionne pas)
$timeout(function () { $scope.assignmentsLoaded(data); }, 1000);
utilisez $ evalAsync () (qui fonctionne)
$scope.$evalAsync(function() { $scope.assignmentsLoaded(data); } );
Violon . J'ai également ajouté un lien "supprimer une ligne de données" qui modifiera $ scope.assignments, simulant une modification des données / modèle - pour montrer que changer les données fonctionne.
La section Runtime de la page Présentation conceptuelle explique que evalAsync doit être utilisé lorsque vous avez besoin que quelque chose se produise en dehors du cadre de pile actuel, mais avant le rendu du navigateur. (Deviner ici ... "le frame de pile actuel" inclut probablement les mises à jour Angular DOM.) Utilisez $ timeout si vous avez besoin que quelque chose se produise après le rendu du navigateur.
Cependant, comme vous l'avez déjà découvert, je ne pense pas que le fonctionnement asynchrone soit nécessaire ici.
la source
$scope.$evalAsync($scope.assignmentsLoaded(data));
n'a aucun sens IMO. L'argument pour$evalAsync()
devrait être une fonction. Dans votre exemple, vous appelez la fonction au moment où une fonction doit être enregistrée pour une exécution ultérieure.J'ai trouvé que la solution la plus simple (bon marché et gaie) est simplement d'ajouter une plage vide avec ng-show = "someFunctionThatAlwaysReturnsZeroOrNothing ()" à la fin du dernier élément rendu. Cette fonction sera exécutée pour vérifier si l'élément span doit être affiché. Exécutez tout autre code dans cette fonction.
Je me rends compte que ce n'est pas la façon la plus élégante de faire les choses, cependant, cela fonctionne pour moi ...
J'ai eu une situation similaire, bien que légèrement inversée où je devais supprimer un indicateur de chargement au début d'une animation, sur les appareils mobiles, angular s'initialisait beaucoup plus rapidement que l'animation à afficher, et l'utilisation d'un ng-cloak était insuffisante car l'indicateur de chargement était supprimé bien avant que des données réelles ne soient affichées. Dans ce cas, je viens d'ajouter la fonction my return 0 au premier élément rendu, et dans cette fonction, j'ai inversé la variable qui cache l'indicateur de chargement. (bien sûr, j'ai ajouté un ng-hide à l'indicateur de chargement déclenché par cette fonction.
la source
$anchorScroll
service intégré soit appelé après le rendu du DOM et rien ne semblait faire l'affaire à part ça. Hackish mais fonctionne.Je pense que vous recherchez $ evalAsync http://docs.angularjs.org/api/ng.$rootScope.Scope#$evalAsync
la source
Enfin j'ai trouvé la solution, j'utilisais un service REST pour mettre à jour ma collection. Afin de convertir jquery datatable est le code suivant:
$scope.$watchCollection( 'conferences', function( old, nuew ) { if( old === nuew ) return; $( '#dataTablex' ).dataTable().fnDestroy(); $timeout(function () { $( '#dataTablex' ).dataTable(); }); });
la source
j'ai dû faire ça assez souvent. J'ai une directive et dois faire des trucs jquery après que le truc du modèle soit complètement chargé dans le DOM. donc je mets ma logique dans le lien: fonction de la directive et enveloppe le code dans un setTimeout (function () {.....}, 1); le setTimout se déclenchera après le chargement du DOM et 1 milliseconde est le temps le plus court après le chargement du DOM avant que le code ne s'exécute. cela semble fonctionner pour moi mais je souhaite que angular déclenche un événement une fois qu'un modèle a été chargé, afin que les directives utilisées par ce modèle puissent faire des tâches jquery et accéder aux éléments DOM. J'espère que cela t'aides.
la source
Vous pouvez également créer une directive qui exécute votre code dans la fonction de lien.
Voir cette réponse stackoverflow .
la source
Ni $ scope. $ EvalAsync () ou $ timeout (fn, 0) ne fonctionnaient de manière fiable pour moi.
J'ai dû combiner les deux. J'ai fait une directive et j'ai également mis une priorité plus élevée que la valeur par défaut pour une bonne mesure. Voici une directive pour cela (notez que j'utilise ngInject pour injecter des dépendances):
app.directive('postrenderAction', postrenderAction); /* @ngInject */ function postrenderAction($timeout) { // ### Directive Interface // Defines base properties for the directive. var directive = { restrict: 'A', priority: 101, link: link }; return directive; // ### Link Function // Provides functionality for the directive during the DOM building/data binding stage. function link(scope, element, attrs) { $timeout(function() { scope.$evalAsync(attrs.postrenderAction); }, 0); } }
Pour appeler la directive, procédez comme suit:
<div postrender-action="functionToRun()"></div>
Si vous voulez l'appeler après l'exécution d'une ng-repeat, j'ai ajouté un span vide dans mon ng-repeat et ng-if = "$ last":
<li ng-repeat="item in list"> <!-- Do stuff with list --> ... <!-- Fire function after the last element is rendered --> <span ng-if="$last" postrender-action="$ctrl.postRender()"></span> </li>
la source
Dans certains scénarios où vous mettez à jour un service et redirigez vers une nouvelle vue (page), puis votre directive est chargée avant que vos services ne soient mis à jour, vous pouvez utiliser $ rootScope. $ Broadcast si votre $ watch ou $ timeout échoue
Vue
<service-history log="log" data-ng-repeat="log in requiedData"></service-history>
Manette
app.controller("MyController",['$scope','$rootScope', function($scope, $rootScope) { $scope.$on('$viewContentLoaded', function () { SomeSerive.getHistory().then(function(data) { $scope.requiedData = data; $rootScope.$broadcast("history-updation"); }); }); }]);
Directif
app.directive("serviceHistory", function() { return { restrict: 'E', replace: true, scope: { log: '=' }, link: function($scope, element, attrs) { function updateHistory() { if(log) { //do something } } $rootScope.$on("history-updation", updateHistory); } }; });
la source
Je suis venu avec une solution assez simple. Je ne sais pas si c'est la bonne façon de procéder, mais cela fonctionne dans un sens pratique. Regardons directement ce que nous voulons être rendu. Par exemple, dans une directive qui inclut des
ng-repeat
s, je ferais attention à la longueur du texte (vous pouvez avoir d'autres choses!) Des paragraphes ou de tout le HTML. La directive sera comme ceci:.directive('myDirective', [function () { 'use strict'; return { link: function (scope, element, attrs) { scope.$watch(function(){ var whole_p_length = 0; var ps = element.find('p'); for (var i=0;i<ps.length;i++){ if (ps[i].innerHTML == undefined){ continue } whole_p_length+= ps[i].innerHTML.length; } //it could be this too: whole_p_length = element[0].innerHTML.length; but my test showed that the above method is a bit faster console.log(whole_p_length); return whole_p_length; }, function (value) { //Code you want to be run after rendering changes }); } }]);
Notez que le code s'exécute réellement après le rendu des changements de rendu plutôt complet. Mais je suppose que dans la plupart des cas, vous pouvez gérer les situations chaque fois que des changements de rendu se produisent. Vous pouvez également penser à comparer cette
p
longueur (ou toute autre mesure) avec votre modèle si vous souhaitez exécuter votre code une seule fois après la fin du rendu. J'apprécie toute réflexion / commentaire à ce sujet.la source
Vous pouvez utiliser le module 'jQuery Passthrough' des utils angular-ui . J'ai lié avec succès un plugin de carrousel tactile jQuery à certaines images que je récupère de manière asynchrone à partir d'un service Web et les rend avec ng-repeat.
la source