AngularJS + JQuery: Jak uzyskać dynamiczną zawartość działającą w angularjs

84

Pracuję nad aplikacją Ajax, używając zarówno jQuery, jak i AngularJS.

Kiedy aktualizuję zawartość (która zawiera powiązania AngularJS) elementu div przy użyciu htmlfunkcji jQuery , powiązania AngularJS nie działają.

Poniżej znajduje się kod tego, co próbuję zrobić:

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

Mam dynamiczną zawartość wewnątrz elementu div z identyfikatorem #dynamicContenti mam przycisk odświeżania, który aktualizowałby zawartość tego elementu div po kliknięciu odświeżania. Przyrost działa zgodnie z oczekiwaniami, jeśli nie odświeżę zawartości, ale po odświeżeniu wiązanie AngularJS przestaje działać.

Może to nie być poprawne w AngularJS, ale początkowo zbudowałem aplikację w jQuery, a później zacząłem używać AngularJS, więc nie mogę przenieść wszystkiego do AngularJS. Każda pomoc w uruchomieniu tego narzędzia w AngularJS jest bardzo mile widziana.

Radża
źródło
1
Czy jest jakiś szczególny powód, dla którego warto używać JQuery do tej funkcji? Ponieważ jest to ładnie i łatwo omówione przez kątowe: < jsfiddle.net/pkozlowski_opensource/YCrFD/2 >
pkozlowski.opensource
3
To jest tylko uproszczona wersja mojego prawdziwego przypadku użycia, aby pokazać problem. W rzeczywistej aplikacji dynamiczna zawartość jest generowana przez grails taglib, która jest przekazywana do jquery jako html. Więc nie mogę przenieść całej logiki z tagliba Grails do angularjs, aby uczynić go czystym angularjs.
Raja
1
@ pkozlowski.opensource, czy link jest martwy? Powinieneś także dołączyć do outdoors.stackexchange.com :)
Liam,

Odpowiedzi:

115

Musisz wywołać $compilełańcuch HTML przed wstawieniem go do DOM, aby angular miał szansę wykonać powiązanie.

Na twoich skrzypcach wyglądałoby to mniej więcej tak.

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

Oczywiście, $compileaby to zadziałało , należy je wstrzyknąć do kontrolera.

Przeczytaj więcej w $compiledokumentacji .

Noah Freitas
źródło
2
Dzięki Noah, kompilacja działa, gdy robię to w metodzie kontrolera i bezpośrednio wiążę tę metodę kontrolera z kliknięciem przycisku. Oto działający przykład . Ale w mojej rzeczywistej aplikacji musiałem wywołać metodę kontrolera z innej funkcji jquery. więc złapałem odniesienie do obiektu zakresu i wywołałem metodę refresh, aby ponownie skompilować, która nie działa. Oto przykład . Czy możesz pomóc w uruchomieniu tego przykładu.
Raja
1
@Raja Kiedy wywołujesz metody / wyrażenia kątowe poza strukturą kątową, powinieneś wywołać $ scope. $ Apply
Artem Andreev
1
@ArtemAndreev dokładnie tak. @Raja można również przesunąć wywołanie $scope.$apply()do refresh()samej funkcji, jeśli ma to sens dla Twojej architektury: jsfiddle.net/nfreitas/8xkeh/1
Noah Freitas
3
Odnośnik do angular-1.0.1.min.jsoryginalnych przykładów to 404. Oto zaktualizowane robocze wersje przykładu @ ArtemAndreev-s: jsfiddle.net/pmorch/fABdD/186 i @NoahFreitas przykład: jsfiddle.net/pmorch/8xkeh/43
Peter V. Mørch
1
Używanie $ compile i manipulowanie DOM wewnątrz kontrolera doprowadzi cię do ścieżki posiadania niesprawdzalnego kodu. Dyrektywy powinny być używane do zmiany DOM, nigdy kontrolera.
Enzey,
57

Inne rozwiązanie na wypadek, gdybyś nie miał kontroli nad zawartością dynamiczną

Działa to, jeśli nie załadowałeś swojego elementu przez dyrektywę (np. Jak w przykładzie w skomentowanym jsfiddles).

Podsumuj swoje treści

Zawiń zawartość w element div, aby można go było wybrać, jeśli używasz JQuery . Możesz także zdecydować się na użycie natywnego javascript, aby uzyskać swój element.

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

Użyj wtryskiwacza kątowego

Możesz użyć poniższego kodu, aby uzyskać odwołanie do $ compile, jeśli go nie masz.

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

Podsumowanie

Oryginalny post wydawał się zakładać, że masz pod ręką odniesienie do $ compile. Jest to oczywiście łatwe, gdy masz odniesienie, ale ja nie, więc to była odpowiedź dla mnie.

Jedno zastrzeżenie poprzedniego kodu

Jeśli używasz pakietu asp.net/mvc ze scenariuszem minify, będziesz mieć kłopoty podczas wdrażania w trybie wydania. Problem pojawia się w postaci błędu Uncaught Error: [$ injector: unpr], który jest spowodowany przez minifier mylący z kątowym kodem javascript.

Oto sposób, aby temu zaradzić :

Zastąp poprzedni fragment kodu następującym przeciążeniem.

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

To wywołało we mnie wiele smutku, zanim złożyłem to w całość.

jwize
źródło
1
Dzięki, ponieważ powyższy kod zadziałał dla mnie, zakres i wyjaśnienie dotyczące kompilacji jest dostępne. czasami tęsknisz za łatwymi częściami :)
Abs
Musiałem $ strawić po kompilacji.
Knu
2
Absolutny ratownik, dodałem warunkowe sprawdzenie kąta w moim kodzie, co pozwala mu korzystać z całej dobroci kątowej po podłączeniu do aplikacji kątowej
LiamRyan
18

Dodatek do odpowiedzi @ jwize

Ponieważ angular.element(document).injector()dawał błąd injector is not defined Tak więc stworzyłem funkcję, którą możesz uruchomić po wywołaniu AJAX lub po zmianie DOM za pomocą 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();
                    }]);
            }

        }

użyj go, przekazując tylko selektor nowego elementu. lubię to

compileAngularElement( '.user' ) ; 
shyammakwana.me
źródło
Dzięki, to uratowało mi życie dzięki pakietowi, który używał UIKit do swoich okien dialogowych, z HTML generowanym w locie wewnątrz funkcji $ scope. Na marginesie, musiałem zmienić selektor $ target, aby odpowiadał moim potrzebom, w moim przypadku $ ["data-ng-controller]") i zakomentować $ scope.apply (); ponieważ ta funkcja została już wywołana w wywołaniu zwrotnym kątowym (więc aplikacja już miała miejsce)
zontar
@zontar możesz również uczynić targetdynamicznym, biorąc go z argumentu.
shyammakwana.me