AngularJS + JQuery: Comment faire fonctionner le contenu dynamique dans angularjs

84

Je travaille sur une application Ajax utilisant à la fois jQuery et AngularJS.

Lorsque je mets à jour le contenu (qui contient des liaisons AngularJS) d'un div à l'aide de la htmlfonction de jQuery , les liaisons AngularJS ne fonctionnent pas.

Voici le code de ce que j'essaye de faire:

$(document).ready(function() {
  $("#refreshButton").click(function() {
    $("#dynamicContent").html("<button ng-click='count = count + 1' ng-init='count=0'>Increment</button><span>count: {{count}} </span>")
  });
});
</style><script src="http://docs.angularjs.org/angular-1.0.1.min.js"></script><style>.ng-invalid {
  border: 1px solid red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="">
  <div id='dynamicContent'>
    <button ng-click="count = count + 1" ng-init="count=0">
        Increment
      </button>
    <span>count: {{count}} </span>
  </div>


  <button id='refreshButton'>
    Refresh
  </button>
</div>

J'ai du contenu dynamique à l'intérieur d'un div avec l'ID #dynamicContent, et j'ai un bouton d'actualisation qui mettrait à jour le contenu de ce div lorsque l'actualisation est cliquée. L'incrément fonctionne comme prévu si je n'actualise pas le contenu, mais après l'actualisation, la liaison AngularJS cesse de fonctionner.

Cela n'est peut-être pas valide dans AngularJS, mais j'ai initialement créé une application avec jQuery et j'ai commencé à utiliser AngularJS plus tard, donc je ne peux pas tout migrer vers AngularJS. Toute aide pour faire fonctionner cela dans AngularJS est grandement appréciée.

Raja
la source
1
Y a-t-il une raison particulière d'utiliser JQuery pour cette fonctionnalité? Comme cela est bien et facilement couvert par angular: < jsfiddle.net/pkozlowski_opensource/YCrFD/2 >
pkozlowski.opensource
3
Ceci est juste une version simplifiée de mon cas d'utilisation réel pour montrer le problème. Dans l'application réelle, le contenu dynamique est généré par grails taglib qui est transmis à jquery au format HTML. Je ne peux donc pas porter toute la logique de grails taglib sur angularjs pour en faire de purs angularjs.
Raja
1
@ pkozlowski.opensource, ce lien est mort? Vous devriez également rejoindre outdoor.stackexchange.com :)
Liam

Réponses:

115

Vous devez appeler $compilela chaîne HTML avant de l'insérer dans le DOM afin que angular ait une chance d'effectuer la liaison.

Dans votre violon, cela ressemblerait à quelque chose comme ça.

$("#dynamicContent").html(
  $compile(
    "<button ng-click='count = count + 1' ng-init='count=0'>Increment</button><span>count: {{count}} </span>"
  )(scope)
);

De toute évidence, $compiledoit être injecté dans votre contrôleur pour que cela fonctionne.

En savoir plus dans la $compiledocumentation .

Noah Freitas
la source
2
Merci Noah, la compilation fonctionne lorsque je le fais dans une méthode de contrôleur et que je lie directement cette méthode de contrôleur pour cliquer sur l'événement du bouton. Voici un exemple de travail . Mais dans mon application actuelle, j'ai dû appeler la méthode du contrôleur à partir d'une fonction jquery différente. J'ai donc saisi la référence à l'objet scope et appelé la méthode de rafraîchissement pour recompiler ce qui ne fonctionne pas. Voici un exemple . Pouvez-vous aider à faire fonctionner cet exemple.
Raja
1
@Raja Lorsque vous appelez des méthodes / expressions angulaires en dehors du cadre angulaire, vous devez appeler $ scope. $ Apply () pour effectuer le cycle de vie approprié de la portée de la gestion des exceptions, de l'exécution des montres, etc. jsfiddle.net/fABdD/14
Artem Andreev
1
@ArtemAndreev exactement ça. @Raja, vous pouvez également déplacer l'appel vers $scope.$apply()la refresh()fonction elle-même si cela a du sens pour votre architecture: jsfiddle.net/nfreitas/8xkeh/1
Noah Freitas
3
Le lien vers angular-1.0.1.min.jsdans les exemples originaux était 404. Voici les versions de travail mises à jour de l'exemple @ ArtemAndreev-s: jsfiddle.net/pmorch/fABdD/186 et @NoahFreitas exemple: jsfiddle.net/pmorch/8xkeh/43
Peter V. Mørch
1
Utiliser $ compile et manipuler DOM dans un contrôleur vous mènera sur un chemin d'accès à un code non testable. Les directives doivent être utilisées pour changer le DOM jamais un contrôleur.
Enzey
57

Une autre solution au cas où vous ne contrôlez pas le contenu dynamique

Cela fonctionne si vous n'avez pas chargé votre élément via une directive (c'est-à-dire comme dans l'exemple des jsfiddles commentés).

Récapitulez votre contenu

Enveloppez votre contenu dans un div afin de pouvoir le sélectionner si vous utilisez JQuery . Vous pouvez également choisir d'utiliser du javascript natif pour obtenir votre élément.

<div class="selector">
    <grid-filter columnname="LastNameFirstName" gridname="HomeGrid"></grid-filter>
</div>

Utiliser l'injecteur angulaire

Vous pouvez utiliser le code suivant pour obtenir une référence à $ compile si vous n'en avez pas.

$(".selector").each(function () {
    var content = $(this);
    angular.element(document).injector().invoke(function($compile) {
        var scope = angular.element(content).scope();
        $compile(content)(scope);
    });
});

Sommaire

Le message original semblait supposer que vous aviez une référence $ compile à portée de main. C'est évidemment facile lorsque vous avez la référence, mais je ne l'ai pas fait, donc c'était la réponse pour moi.

Une mise en garde du code précédent

Si vous utilisez un bundle asp.net/mvc avec un scénario minify, vous aurez des problèmes lorsque vous déployez en mode version. Le problème se présente sous la forme d'une erreur non interceptée: [$ injector: un] qui est causée par le minificateur jouant avec le code javascript angulaire.

Voici comment y remédier :

Remplacez l'extrait de code précédent par la surcharge suivante.

...
angular.element(document).injector().invoke(
[
    "$compile", function($compile) {
        var scope = angular.element(content).scope();
        $compile(content)(scope);
    }
]);
...

Cela m'a causé beaucoup de chagrin avant de le reconstituer.

jwize
la source
1
Merci car le code ci-dessus a fonctionné pour moi et l'explication sur la disponibilité de la compilation. parfois vous manquez les parties faciles :)
Abs
J'ai dû $ digérer après la compilation.
Knu le
2
Bouée de sauvetage absolue, j'ai ajouté une vérification conditionnelle d'angular dans mon code qui lui permet d'utiliser toute la bonté angulaire lorsqu'il est branché sur une application angulaire
LiamRyan
18

Ajout à la réponse de @ jwize

Parce que angular.element(document).injector()donnait une erreur injector is not defined Donc, j'ai créé une fonction que vous pouvez exécuter après un appel AJAX ou lorsque DOM est modifié à l'aide de jQuery.

  function compileAngularElement( elSelector) {

        var elSelector = (typeof elSelector == 'string') ? elSelector : null ;  
            // The new element to be added
        if (elSelector != null ) {
            var $div = $( elSelector );

                // The parent of the new element
                var $target = $("[ng-app]");

              angular.element($target).injector().invoke(['$compile', function ($compile) {
                        var $scope = angular.element($target).scope();
                        $compile($div)($scope);
                        // Finally, refresh the watch expressions in the new element
                        $scope.$apply();
                    }]);
            }

        }

utilisez-le en passant juste le sélecteur de nouvel élément. comme ça

compileAngularElement( '.user' ) ; 
shyammakwana.me
la source
Merci, cela m'a sauvé la vie avec un package qui utilisait UIKit pour ses dialogues, avec du HTML généré à la volée dans une fonction $ scope. En remarque, j'ai dû changer le sélecteur de la cible $ pour répondre à mes besoins, dans mon cas $ ["data-ng-controller]"), et commenter $ scope.apply (); puisque cette fonction était déjà appelée dans un rappel angulaire (donc
Apply
@zontar vous pouvez également rendre targetdynamique en le prenant à partir d'un argument.
shyammakwana.me