Zadzwoń do AngularJS ze starszego kodu

180

Korzystam z AngularJS do tworzenia formantów HTML, które współdziałają ze starszą aplikacją Flex. Wszystkie wywołania zwrotne z aplikacji Flex muszą być dołączone do okna DOM.

Na przykład (w AS3)

ExternalInterface.call("save", data);

Zadzwonię

window.save = function(data){
    // want to update a service 
    // or dispatch an event here...
}

Z poziomu funkcji zmiany rozmiaru JS chciałbym wywołać zdarzenie, które kontroler może usłyszeć. Wydaje się, że najlepiej jest stworzyć usługę. Czy możesz zaktualizować usługę spoza AngularJS? Czy kontroler może nasłuchiwać zdarzeń z usługi? W jednym eksperymencie (kliknij, aby zobaczyć skrzypce) zrobiłem to, wydaje się, że mogę uzyskać dostęp do usługi, ale aktualizacja danych usługi nie jest odzwierciedlona w widoku (w przykładzie <option>należy dodać<select> ).

Dzięki!

kreek
źródło
1
Zauważ, że w jsfiddle powyżej wtryskiwacz uzyskuje się bez celowania elementu w aplikacji za pomocą var injector = angular.injector(['ng', 'MyApp']);. Dzięki temu uzyskasz zupełnie nowy kontekst i duplikat myService. Oznacza to, że otrzymasz dwa wystąpienia usługi i modelu i dodasz dane w niewłaściwe miejsce. Zamiast tego należy kierować element za pomocą aplikacji angular.element('#ng-app').injector(['ng', 'MyApp']). W tym momencie możesz użyć $ Apply, aby zawinąć zmiany modelu.
Thanh Nguyen

Odpowiedzi:

293

Interakcja spoza kąta do kąta jest taka sama jak debugowanie aplikacji kątowej lub integracja z biblioteką strony trzeciej.

W przypadku dowolnego elementu DOM możesz to zrobić:

  • angular.element(domElement).scope() aby uzyskać bieżący zakres dla elementu
  • angular.element(domElement).injector() aby uzyskać bieżący aplikator
  • angular.element(domElement).controller()zdobyć ng-controllerinstancję.

Z wtryskiwacza można uzyskać dostęp do dowolnej usługi w aplikacji kątowej. Podobnie z zakresu można wywoływać dowolne metody, które zostały do ​​niego opublikowane.

Należy pamiętać, że wszelkie zmiany w modelu kątowym lub wszelkie wywołania metod w zakresie muszą być opakowane w $apply()następujący sposób:

$scope.$apply(function(){
  // perform any model changes or method invocations here on angular app.
});
Misko Hevery
źródło
1
to działa, ale chciałbym, aby istniał sposób na przejście bezpośrednio z modułu do jego zakresu - czy to możliwe? [ng-app]
Konieczność
5
Nie mogę tego uruchomić: dzwonię angular.element(document.getElementById(divName)).scope(), ale nie jestem w stanie wywoływać z niego żadnych funkcji, po prostu zwraca „niezdefiniowane” w konsoli.
Emil,
1
Nawet ja mam ten sam problem, jak opisany powyżej przez @Emil, powraca niezdefiniowany. Jakaś pomoc ?
Bibin
5
element (). scope () nie będzie działać, jeśli dane debugowania są wyłączone, co jest zaleceniem dla produkcji. Czy to nie czyni go bezużytecznym w tym scenariuszu? Będzie to tylko do testowania / debugowania.
K. Norbert,
4
Nie będziesz chciał tego robić w Angular 1.3. Zespół Angular nie zamierzał, abyśmy wywoływali „.scope ()” na elementach kodu produkcyjnego. To miało być narzędzie do debugowania. Począwszy od Angular 1.3, możesz to wyłączyć. Angular przestanie dołączać zakres do elementu za pomocą funkcji .data jQuery. To przyspieszy twoją aplikację. Ponadto przekazanie lunet do funkcji buforowania jquery spowoduje wycieki pamięci. Zdecydowanie powinieneś to wyłączyć, aby przyspieszyć swoją aplikację. Witryna Angulara zawiera przewodnik po produkcji, z którego należy skorzystać, aby dowiedzieć się więcej.
mroźny
86

Misko podało prawidłową odpowiedź (oczywiście), ale niektórzy z nas początkujących mogą potrzebować jej dalszego uproszczenia.

Jeśli chodzi o wywoływanie kodu AngularJS ze starszych aplikacji, pomyśl o kodzie AngularJS jako o „mikro aplikacji” istniejącej w chronionym kontenerze w starszej aplikacji. Nie możesz bezpośrednio do niego dzwonić (z bardzo dobrego powodu), ale możesz wykonywać połączenia zdalne za pomocą obiektu $ scope.

Aby użyć obiektu $ scope, musisz uzyskać uchwyt $ scope. Na szczęście jest to bardzo łatwe do zrobienia.

Możesz użyć identyfikatora dowolnego elementu HTML w swoim „mikro-aplikacji” HTML AngularJS, aby uzyskać uchwyt zakresu $ aplikacji AngularJS.

Jako przykład załóżmy, że chcemy wywołać kilka funkcji w naszym kontrolerze AngularJS, takich jak sayHi () i sayBye (). W AngularJS HTML (widok) mamy div z identyfikatorem „MySuperAwesomeApp”. Możesz użyć następującego kodu w połączeniu z jQuery, aby uzyskać uchwyt $ scope:

var microappscope = angular.element($("#MySuperAwesomeApp")).scope();

Teraz możesz wywoływać funkcje kodu AngularJS za pomocą uchwytu zakresu:

// we are in legacy code land here...

microappscope.sayHi();

microappscope.sayBye();

Aby było wygodniej, możesz użyć funkcji, aby chwycić uchwyt lunety w dowolnym momencie, aby uzyskać do niego dostęp:

function microappscope(){

    return angular.element($("#MySuperAwesomeApp")).scope();

}

Twoje połączenia wyglądałyby następująco:

microappscope().sayHi();

microappscope().sayBye();

Możesz zobaczyć działający przykład tutaj:

http://jsfiddle.net/peterdrinnan/2nPnB/16/

Pokazałem to również w pokazie slajdów dla grupy Ottawa AngularJS (wystarczy przejść do ostatnich 2 slajdów)

http://www.slideshare.net/peterdrinnan/angular-for-legacyapps

Peter Drinnan
źródło
4
Zwróć uwagę, że odradzane są odpowiedzi tylko do linku, więc odpowiedzi SO powinny być punktem końcowym poszukiwania rozwiązania (w porównaniu z kolejnym postojem referencji, które z czasem stają się nieaktualne). Rozważ dodanie tutaj autonomicznego streszczenia, zachowując link jako odniesienie.
kleopatra
Ładne dodatkowe wyjaśnienie. Dzięki.
Joe
1
Piękne wyjaśnienie! pozwolił mi ominąć sprawdzanie poprawności formularza, wykonując następujące czynności:<input type="button" onclick="angular.element(this).scope().edit.delete();" value="delete">
Purefan,
24

Najlepsze wyjaśnienie koncepcji, którą znalazłem, znajduje się tutaj: https://groups.google.com/forum/#!msg/angular/kqFrwiysgpA/eB9mNbQzcHwJ

Aby zaoszczędzić kliknięcie:

// get Angular scope from the known DOM element
e = document.getElementById('myAngularApp');
scope = angular.element(e).scope();
// update the model with a wrap in $apply(fn) which will refresh the view for us
scope.$apply(function() {
    scope.controllerMethod(val);
}); 
Mądry człowiek
źródło
14
Powyższe działa, gdy aplikacja i kontroler współistnieją w tym samym elemencie. W przypadku bardziej złożonych aplikacji, które wykorzystują dyrektywę ng-view do szablonu, musisz uzyskać pierwszy element w widoku, a nie element DOM całej aplikacji. Musiałem przeszukiwać elementy za pomocą document.getElementsByClassName („ng-scope”); lista węzłów, aby dowiedzieć się, jaki element DOM zakresu należy pobrać.
goosemanjack
Wiem, że to naprawdę stary wątek, ale myślę, że wpadam na ten problem. Czy ktoś ma jakiś kod, który pokazuje, jak przejść listę, aby dowiedzieć się, jaki element DOM należy pobrać?
JerryKur
Zignoruj ​​moje pytanie. Udało mi się uzyskać tę pracę, używając po prostu document.getElementById („any-Control-That-Has-An-NG-Directive”). Scope ().
JerryKur
jeśli korzystasz z ng-view i dzielisz swoje widoki na własne pliki. możesz umieścić i idw górnym elemencie HTML, a następnie zrobić document.getElementById()ten identyfikator. To daje dostęp do zakresu tego kontrolera. metody / właściwości itp. ... po prostu podkreślając komentarz goosemanjacka.
ftravers
13

W nawiązaniu do innych odpowiedzi. Jeśli nie chcesz uzyskać dostępu do metody w kontrolerze, ale chcesz uzyskać bezpośredni dostęp do usługi, możesz zrobić coś takiego:

// Angular code* :
var myService = function(){
    this.my_number = 9;
}
angular.module('myApp').service('myService', myService);


// External Legacy Code:
var external_access_to_my_service = angular.element('body').injector().get('myService');
var my_number = external_access_to_my_service.my_number 
Alec Hewitt
źródło
13

Dzięki poprzedniej wiadomości mogę zaktualizować mój model za pomocą zdarzenia asynchronicznego.

<div id="control-panel" ng-controller="Filters">
    <ul>
        <li ng-repeat="filter in filters">
        <button type="submit" value="" class="filter_btn">{{filter.name}}</button>
        </li>
    </ul>
</div>

Deklaruję mój model

function Filters($scope) {
    $scope.filters = [];
}

I aktualizuję mój model spoza mojego zakresu

ws.onmessage = function (evt) {
    dictt = JSON.parse(evt.data);
    angular.element(document.getElementById('control-panel')).scope().$apply(function(scope){
        scope.filters = dictt.filters;
    });
};
Guillaume Vincent
źródło
6

Bardziej bezpiecznym i wydajniejszym sposobem, zwłaszcza gdy dane debugowania są wyłączone, jest użycie zmiennej współdzielonej do przechowywania funkcji zwrotnej. Twój kontroler kątowy implementuje tę funkcję, aby zwrócić swoje elementy wewnętrzne do kodu zewnętrznego.

var sharedVar = {}
myModule.constant('mySharedVar', sharedVar)
mymodule.controller('MyCtrl', [ '$scope','mySharedVar', function( $scope, mySharedVar) {

var scopeToReturn = $scope;

$scope.$on('$destroy', function() {
        scopeToReturn = null;
    });

mySharedVar.accessScope = function() {
    return scopeToReturn;
}
}]);

Uogólniona jako dyrektywa wielokrotnego użytku:

Stworzyłem dyrektywę „exposeScope”, która działa w podobny sposób, ale jej użycie jest prostsze:

<div ng-controller="myController" expose-scope="aVariableNameForThisScope">
   <span expose-scope='anotherVariableNameForTheSameScope" />
</div>

Przechowuje to aktualny zakres (przypisany do funkcji link dyrektywy) w globalnym obiekcie „scopes”, który jest uchwytem dla wszystkich zakresów. Wartość podana w atrybucie dyrektywy jest używana jako nazwa właściwości zakresu w tym obiekcie globalnym.

Zobacz demo tutaj . Jak pokazałem w wersji demo, możesz wyzwalać zdarzenia jQuery, gdy zakres jest przechowywany i usuwany z globalnego obiektu „zakresów”.

<script type="text/javascript" >
    $('div').on('scopeLinked', function(e, scopeName, scope, allScopes) {
      // access the scope variable or the given name or the global scopes object
    }.on('scopeDestroyed', function(e, scopeName, scope, allScopes) {
      // access the scope variable or the given name or the global scopes object
    }

</script>

Zauważ, że nie testowałem on („scopeDestroyed”), gdy rzeczywisty element jest usuwany z DOM. Jeśli to nie pomoże, wyzwalanie zdarzenia w samym dokumencie zamiast elementu może pomóc. (patrz skrypt app.js) w plunkerze demo.

Cagatay Kalan
źródło
3

Wiem, że to stare pytanie, ale ostatnio szukałem opcji, aby to zrobić, więc pomyślałem, że umieściłem tutaj moje odkrycia na wypadek, gdyby były przydatne dla każdego.

W większości przypadków, jeśli istnieje potrzeba, aby zewnętrzny starszy kod współdziałał ze stanem interfejsu użytkownika lub wewnętrznym działaniem aplikacji, usługa może być przydatna do usunięcia tych zmian. Jeśli zewnętrzny kod oddziałuje bezpośrednio z kontrolerem kątowym, komponentem lub dyrektywą, mocno łączysz swoją aplikację ze starszym kodem, co jest złą wiadomością.

W moim przypadku użyłem kombinacji globali dostępnych w przeglądarce (tj. Okna) i obsługi zdarzeń. Mój kod ma inteligentny silnik generowania formularzy, który wymaga danych wyjściowych JSON z CMS do inicjalizacji formularzy. Oto co zrobiłem:

function FormSchemaService(DOM) {
    var conf = DOM.conf;

    // This event is the point of integration from Legacy Code 
    DOM.addEventListener('register-schema', function (e) {

       registerSchema(DOM.conf); 
    }, false);

    // service logic continues ....

Usługa schematu formularza jest tworzona za pomocą kątowego wtryskiwacza zgodnie z oczekiwaniami:

angular.module('myApp.services').
service('FormSchemaService', ['$window' , FormSchemaService ])

A w moich kontrolerach: function () {'use strict';

angular.module('myApp').controller('MyController', MyController);

MyEncapsulatorController.$inject = ['$scope', 'FormSchemaService'];

function MyController($scope, formSchemaService) {
    // using the already configured formSchemaService
    formSchemaService.buildForm(); 

Do tej pory jest to programowanie zorientowane na usługi oparte na języku i javascript. Ale integracja starszych wersji jest tutaj:

<script type="text/javascript">

   (function(app){
        var conf = app.conf = {
       'fields': {
          'field1: { // field configuration }
        }
     } ; 

     app.dispatchEvent(new Event('register-schema'));

 })(window);
</script>

Oczywiście każde podejście ma swoje zalety i wady. Zalety i zastosowanie tego podejścia zależy od interfejsu użytkownika. Poprzednio sugerowane podejścia nie działają w moim przypadku, ponieważ mój schemat formularza i starszy kod nie mają kontroli i znajomości zakresów kątowych. Stąd konfigurowanie aplikacji w oparciu o angular.element('element-X').scope(); może potencjalnie uszkodzić aplikację, jeśli zmienimy zakresy. Ale jeśli Twoja aplikacja ma wiedzę na temat zakresu i może polegać na tym, że nie zmienia się często, sugerowane wcześniej podejście jest realne.

Mam nadzieję że to pomoże. Wszelkie opinie są również mile widziane.

Shakus
źródło