2012-05-07 8 views
179

Ich verwende AngularJS, um HTML-Steuerelemente zu erstellen, die mit einer älteren Flex-Anwendung interagieren. Alle Rückrufe von der Flex-App müssen an das DOM-Fenster angehängt werden.Rufen Sie AngularJS aus dem Legacy-Code

Zum Beispiel (in AS3)

ExternalInterface.call("save", data); 

Wird

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

Von innerhalb der JS aufrufen Funktion die Größe Ich möchte ein Ereignis versenden, dass ein Controller hören kann. Es scheint, dass die Schaffung eines Dienstes der richtige Weg ist. Können Sie einen Service von außerhalb von AngularJS aktualisieren? Kann ein Controller auf Ereignisse von einem Dienst warten? In einem experiment (click for fiddle) Ich schien es, als ob ich auf einen Dienst zugreifen kann, aber die Aktualisierung der Daten des Dienstes wird nicht in der Ansicht wiedergegeben (im Beispiel sollte ein <option> dem <select> hinzugefügt werden).

Danke!

+1

Beachten Sie, dass in der Jfiddle über dem Injektor erhalten wird, ohne ein Element innerhalb der App mit 'var injector = angular.injector (['ng', 'MyApp']); Dadurch erhalten Sie einen völlig neuen Kontext und einen doppelten 'myService'. Das bedeutet, dass Sie zwei Instanzen des Dienstes und des Modells erhalten und Daten an der falschen Stelle hinzufügen. Sie sollten stattdessen ein Element innerhalb der App mit 'angular.element ('# ng-app') ansteuern. Injector (['ng', 'MyApp'])'. An dieser Stelle können Sie $ apply verwenden, um Modelländerungen zu umbrechen. –

Antwort

290

Interop von außerhalb von eckig bis eckig ist das gleiche wie debuggen Winkelanwendung oder Integration mit Third-Party-Bibliothek.

Für DOM-Element können Sie dies tun:

  • angular.element(domElement).scope() den aktuellen Rahmen für das Element der aktuellen App Injektor
  • angular.element(domElement).injector() zu bekommen zu bekommen
  • angular.element(domElement).controller() einen Halt der ng-controller Instanz zu erhalten .

Aus dem Injektor können Sie einen Service in Winkelanwendung erhalten. Ähnlich können Sie aus dem Bereich alle Methoden aufrufen, die für sie veröffentlicht wurden.

Beachten Sie, dass Änderungen an das Winkelmodell oder jede Methode Anrufungen auf dem Umfang müssen in $apply() wie diese gewickelt werden:

$scope.$apply(function(){ 
    // perform any model changes or method invocations here on angular app. 
}); 
+1

das funktioniert, aber ich wünschte, es gäbe einen Weg, von einem Modul direkt zu seinem Umfang zu kommen - ist das möglich? Zurück gehen und den '[ng-app]' root-node auswählen, scheint rückwärts zu sein, wenn ich bereits einen Verweis auf das Modul habe ... –

+0

Ich bin in letzter Zeit auf dieselbe Sache gestoßen. Ich komme an das dom-Element, so dass es kein Problem ist, zum Scope zu gelangen, es würde mehr Sinn machen, über das global registrierte Modul zu gehen. – Samuel

+5

Ich kann das nicht zum Laufen bringen: Ich rufe 'angular.element (document.getElementById (divName)). Scope()', aber ich kann keine Funktionen von ihm aufrufen, es gibt nur "undefined" zurück in der Konsole. – Emil

13

Dank der früheren Post, kann ich mein Modell mit einem Update asynchrones Ereignis

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

Ich erkläre mein Modell

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

Und ich mein Modell aktualisieren von außerhalb meiner Reichweite

ws.onmessage = function (evt) { 
    dictt = JSON.parse(evt.data); 
    angular.element(document.getElementById('control-panel')).scope().$apply(function(scope){ 
     scope.filters = dictt.filters; 
    }); 
}; 
24

größte Erklärung des Konzepts ich gefunden habe, hier befindet: https://groups.google.com/forum/#!msg/angular/kqFrwiysgpA/eB9mNbQzcHwJ

Um Ihnen das Klicken zu sparen:

+14

Das oben beschriebene funktioniert, wenn App und Controller in demselben Element nebeneinander existieren. Bei komplexeren Apps, die eine ng-view-Direktive für eine Vorlage verwenden, müssen Sie das erste Element in der Ansicht und nicht das DOM-Element der gesamten App abrufen. Ich musste Elemente mit einem document.getElementsByClassName ('ng-scope'); Knotenliste, um herauszufinden, welches DOM-Element das richtige Scope ist. – goosemanjack

+0

Ich weiß, dass dies ein wirklich alter Thread ist, aber ich glaube, ich stoße auf dieses Problem. Hat jemand Code, der zeigt, wie man die Liste durchläuft, um herauszufinden, welches DOM-Element zu greifen ist? – JerryKur

+0

Ignoriere meine Frage. Ich war in der Lage, diese Arbeit zu bekommen, indem ich einfach document.getElementById ('irgendeine-Kontrolle-das-hat-eine-NG-Direktive'). Scope() benutzt. – JerryKur

85

Misko gab die richtige Antwort (offensichtlich), aber einige von uns Neulingen brauchen es möglicherweise weiter vereinfacht.

Wenn AngularJS-Code innerhalb von Legacy-Apps aufgerufen wird, sollten Sie sich den AngularJS-Code als "Mikro-App" in einem geschützten Container in Ihrer Legacy-Anwendung vorstellen.Sie können nicht direkt (aus sehr gutem Grund) darauf zugreifen, aber Sie können Remote-Aufrufe über das $ scope-Objekt ausführen.

Um das Objekt $ scope zu verwenden, müssen Sie das Handle von $ scope erhalten. Zum Glück ist das sehr einfach.

Sie können die ID eines beliebigen HTML-Elements in Ihrem AngularJS "Mikro-App" HTML verwenden, um das Handle des AngularJS App $ scope zu erhalten.

Als Beispiel wollen wir ein paar Funktionen in unserem AngularJS-Controller wie sayHi() und sayBye() aufrufen. Im AngularJS HTML (view) haben wir ein div mit der ID "MySuperAwesomeApp". Sie können den folgenden Code ein, kombiniert mit jQuery den Griff $ Umfang zu erhalten:

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

Jetzt können Sie Ihre AngularJS Code-Funktionen über den Umfang Griff nennen:

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

microappscope.sayHi(); 

microappscope.sayBye(); 

Dinge zu machen bequemer, können Sie eine Funktion verwenden, den Umfang Griff, wann immer Sie es wollen greifen zuzugreifen:

function microappscope(){ 

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

} 

Ihre Anrufe dann würde wie folgt aussehen:

microappscope().sayHi(); 

microappscope().sayBye(); 

Sie können ein funktionierendes Beispiel siehe hier:

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

ich dies auch für die Ottawa AngularJS Gruppe in einer Diashow gezeigt (nur die letzten 2 Rutschen überspringen)

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

+4

Beachten Sie, dass Link-Only-Antworten abgeraten werden, SO-Antworten sollten der Endpunkt einer Suche nach einer Lösung sein (im Gegensatz zu einem weiteren Zwischenstopp von Referenzen, die im Laufe der Zeit abgestanden werden). Bitte beachten Sie, dass Sie hier eine eigenständige Zusammenfassung hinzufügen und den Link als Referenz beibehalten. – kleopatra

+0

Nette zusätzliche Erläuterung. Vielen Dank. – Joe

+1

Schöne Erklärung! erlaubte mir, eine Formvalidierung zu umgehen, indem ich folgendes tat: ' ' – Purefan

12

Weiter zu den anderen Antworten. Wenn Sie nicht über ein Verfahren in einem Controller zugreifen möchten aber auf den Dienst zugreifen wollen direkt Sie etwas tun können:

// 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 
6

Mehr sichere und performante Art und Weise vor allem, wenn Debug-Daten ausgeschaltet ist ein verwenden, geteilte Variable, um eine Rückruffunktion zu halten. Ihr Winkelregler implementiert diese Funktion, um seine Interna an den externen Code zurückzugeben.

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; 
} 
}]); 

als wiederverwendbare Richtlinie generali:

Ich habe eine Richtlinie 'exposeScope', die in ähnlicher Weise funktioniert, aber Nutzung ist einfacher:

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

Dieser den aktuellen Bereich speichert (das ist der Link-Funktion der Direktive gegeben) in einem globalen 'scopes' Objekt, das ein Halter für alle Bereiche ist. Der für das Attribut directory bereitgestellte Wert wird als der Name der Eigenschaft des Bereichs in diesem globalen Objekt verwendet.

Sehen Sie die Demo here. Wie ich in der Demo gezeigt habe, können Sie jQuery-Ereignisse auslösen, wenn der Bereich gespeichert und aus dem globalen Bereich "Bereiche" entfernt wird.

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

Beachten Sie, dass ich nicht getestet haben die auf (‚scopeDestroyed‘), wenn das eigentliche Element aus dem DOM entfernt wird.Wenn dies nicht funktioniert, kann das Auslösen des Ereignisses im Dokument selbst anstelle des Elements hilfreich sein. (siehe das app.js) -Skript im Demo-Plocker.

3

Ich weiß, dass dies eine alte Frage ist, aber ich habe mir Optionen angesehen, um das vor kurzem zu tun, also dachte ich, ich würde meine Ergebnisse hier veröffentlichen, falls es für irgendjemanden nützlich ist.

In den meisten Fällen, wenn externe Code-Legacy benötigt wird, um mit dem Zustand der Benutzeroberfläche oder den internen Funktionen der Anwendung zu interagieren, könnte ein Dienst nützlich sein, um diese Änderungen wegzuspulen. Wenn ein externer Code direkt mit Ihrem eckigen Controller, Ihrer Komponente oder Ihrer Direktive interagiert, koppeln Sie Ihre App stark mit Ihrem alten Code, was eine schlechte Nachricht ist.

Was ich in meinem Fall verwendet habe, ist eine Kombination von Browser-zugänglichen globalen (d. H. Fenster) und Ereignisbehandlung. Mein Code verfügt über eine intelligente Formulargenerierungs-Engine, die JSON-Ausgabe von einem CMS erfordert, um die Formulare zu initialisieren. Hier ist, was ich getan habe:

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 .... 

Formular Schema Dienst wird unter Verwendung von Winkel Injektor geschaffen wie erwartet:

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

Und in meinem Controller: function() { 'use strict';

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

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

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

Bis jetzt ist dies reine eckige und JavaScript Service orientierte Programmierung. Aber die Legacy-Integration kommt hier:

<script type="text/javascript"> 

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

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

})(window); 
</script> 

Offensichtlich hat jeder Ansatz seine Vor- und Nachteile. Die Vorteile und der Nutzen dieses Ansatzes hängen von Ihrer Benutzeroberfläche ab. Die zuvor vorgeschlagenen Ansätze funktionieren in meinem Fall nicht, da mein Formularschema und mein Legacy-Code keine Kontrolle und Kenntnisse über Winkelbereiche besitzen. Daher könnte das Konfigurieren meiner App basierend auf angular.element('element-X').scope(); die App potenziell beschädigen, wenn wir die Bereiche ändern. Aber wenn Sie wissen, dass das App das Scoping kennt und darauf vertrauen kann, dass es sich nicht oft ändert, ist ein praktikabler Ansatz vorgeschlagen.

Hoffe, das hilft. Jede Rückmeldung ist ebenfalls willkommen.