2012-05-07 23 views
179

Sto utilizzando AngularJS per creare controlli HTML che interagiscono con un'applicazione legacy Flex. Tutti i callback dall'app Flex devono essere collegati alla finestra DOM.Chiama AngularJS dal codice precedente

Per esempio (in AS3)

ExternalInterface.call("save", data); 

Chiamerà

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

Dall'interno della JS funzione di ridimensionamento vorrei inviare un evento che un controllore può sentire. Sembra che creare un servizio sia la strada da percorrere. È possibile aggiornare un servizio al di fuori di AngularJS? Un controller può ascoltare gli eventi da un servizio? In uno experiment (click for fiddle) ho fatto sembrare che posso accedere a un servizio ma l'aggiornamento dei dati del servizio non si riflette nella vista (nell'esempio è necessario aggiungere <option> allo <select>).

Grazie!

+1

Si noti che nel jsfiddle sopra l'iniettore si ottiene senza puntare un elemento all'interno dell'app utilizzando 'var injector = angular.injector (['ng', 'MyApp']);'. In questo modo avrai un contesto completamente nuovo e un duplicato 'myService'. Ciò significa che ti ritroverai con due istanze del servizio e del modello e aggiungerai i dati nel posto sbagliato. Dovresti invece indirizzare un elemento all'interno dell'app usando 'angular.element ('# ng-app'). Injector (['ng', 'MyApp'])'. A questo punto puoi usare $ apply per avvolgere le modifiche del modello. –

risposta

290

L'intervallo dall'esterno di angolare a quello angolare è uguale all'applicazione angolare di debug o all'integrazione con la libreria di terze parti.

Per qualsiasi elemento DOM si può fare questo:

  • angular.element(domElement).scope() per ottenere l'ambito corrente per l'elemento
  • angular.element(domElement).injector() per ottenere la corrente applicazione iniettore
  • angular.element(domElement).controller() di ottenere una sospensione dell'istanza ng-controller .

Dall'iniettore è possibile ottenere qualsiasi servizio in un'applicazione angolare. Allo stesso modo dall'ambito è possibile richiamare qualsiasi metodo che è stato pubblicato su di esso.

Tenete a mente che tutte le modifiche apportate al modello angolare o qualsiasi chiamate di metodo sulla portata devono essere avvolti in $apply() come questo:

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

funziona, ma mi piacerebbe che ci fosse un modo per ottenere direttamente da un modulo al suo scopo: è possibile? Dovendo tornare indietro selezionare il nodo-radice '[ng-app]' sembra indietro quando ho già un riferimento al modulo ... –

+0

Ho incontrato la stessa cosa di recente. Posso accedere all'elemento dom in modo che non sia un po 'troppo per raggiungere l'ambito, ma sarebbe più sensato passare attraverso il modulo registrato a livello globale. – Samuel

+5

Non riesco a farlo funzionare: sto chiamando 'angular.element (document.getElementById (divName)). Scope()', ma non sono in grado di invocare alcuna funzione da esso, ma restituisce "undefined" nella console. – Emil

13

Grazie al post precedente, posso aggiornare il mio modello con un evento asincrono.

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

dichiaro il mio modello

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

E posso aggiornare il mio modello da fuori dalla mia portata

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

più grande spiegazione del concetto che ho trovato si trova qui: https://groups.google.com/forum/#!msg/angular/kqFrwiysgpA/eB9mNbQzcHwJ

Per risparmiare il clic:

// 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); 
}); 
+14

Quanto sopra funziona quando l'app e il controller coesistono nello stesso elemento. Per le app più complesse che utilizzano una direttiva ng-view su un modello, è necessario ottenere il primo elemento all'interno della vista, non l'elemento DOM dell'intera app. Ho dovuto aggirare gli elementi con un document.getElementsByClassName ('ng-scope'); elenco dei nodi per capire l'elemento DOM dell'ambito corretto da afferrare. – goosemanjack

+0

So che questo è un thread molto vecchio, ma penso che mi stia imbattendo in questo problema. Qualcuno ha qualche codice che mostra come percorrere l'elenco per capire l'elemento DOM da afferrare? – JerryKur

+0

Ignora la mia domanda. Sono stato in grado di ottenere questo lavoro semplicemente usando document.getElementById ('any-Control-That-Has-An-NG-Directive'). Scope(). – JerryKur

85

Misko ha dato la risposta corretta (ovviamente), ma alcuni di noi neofiti potrebbero aver bisogno di ulteriore semplificazione.

Se si tratta di chiamare il codice AngularJS da applicazioni legacy, pensare al codice AngularJS come a una "micro app" esistente all'interno di un contenitore protetto nell'applicazione legacy.Non è possibile effettuare chiamate direttamente (per una buona ragione), ma è possibile effettuare chiamate remote tramite l'oggetto $ scope.

Per utilizzare l'oggetto $ scope, è necessario ottenere l'handle di $ scope. Fortunatamente questo è molto facile da fare.

È possibile utilizzare l'id di qualsiasi elemento HTML all'interno del codice HTML "micro-app" di AngularJS per ottenere l'handle dell'app $ scope di AngularJS.

Ad esempio, diciamo che vogliamo chiamare un paio di funzioni all'interno del nostro controller AngularJS come sayHi() e sayBye(). Nel codice HTML di AngularJS (vista) abbiamo un div con l'id "MySuperAwesomeApp". È possibile utilizzare il seguente codice, combinato con jQuery per ottenere l'handle di $ portata:

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

Ora è possibile chiamare il codice funziona AngularJS per mezzo del manico campo di applicazione:

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

microappscope.sayHi(); 

microappscope.sayBye(); 

Per rendere le cose più conveniente, è possibile utilizzare una funzione per afferrare la maniglia in qualsiasi momento ambito si vuole accedervi:

function microappscope(){ 

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

} 

le chiamate sarebbero quindi simile a questa:

microappscope().sayHi(); 

microappscope().sayBye(); 

si può vedere un esempio di lavoro qui:

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

ho anche mostrato questo in una presentazione per il gruppo di Ottawa AngularJS (basta saltare agli ultimi 2 scivoli)

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

+4

Si noti che le risposte di solo collegamento sono scoraggiate, pertanto le risposte dovrebbero essere il punto finale di una ricerca di una soluzione (rispetto a un'altra sosta di riferimenti, che tende a diventare obsoleta nel tempo). Si prega di considerare l'aggiunta di una sinossi autonoma qui, mantenendo il collegamento come riferimento. – kleopatra

+0

Bel chiarimento aggiuntivo. Grazie. – Joe

+1

Bella spiegazione! mi ha permesso di aggirare una convalida del modulo in questo modo: ' ' – Purefan

12

Oltre alle altre risposte. Se non si desidera accedere un metodo in un controller, ma vuole accedere al servizio direttamente si può fare qualcosa di simile:

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

modo più sicuro e performante soprattutto quando i dati di debug è spento è quello di utilizzare un variabile condivisa per contenere una funzione di callback. Il controller angolare implementa questa funzione per restituire i suoi interni al codice esterno.

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

generalizzata come una direttiva riutilizzabile:

ho creato una direttiva 'exposeScope' che funziona in modo simile, ma l'utilizzo è più semplice:

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

Questo memorizza l'ambito corrente (che viene dato alla funzione di collegamento della direttiva) in un oggetto globale "ambiti" che è un detentore di tutti gli ambiti. Il valore fornito all'attributo direttiva viene utilizzato come nome della proprietà dell'oscilloscopio in questo oggetto globale.

Vedere la demo here. Come ho mostrato nella demo, è possibile attivare eventi jQuery quando l'oscilloscopio viene archiviato e rimosso dall'oggetto 'ambiti' globale.

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

noti che, non ho testato on ('scopeDestroyed') quando l'elemento effettivo viene rimosso dal DOM.Se non funziona, l'attivazione dell'evento sul documento stesso invece dell'elemento può essere d'aiuto. (vedi lo script app.js) nel plunker demo.

3

So che questa è una vecchia domanda, ma stavo guardando le opzioni per farlo di recente, quindi ho pensato di mettere le mie conclusioni qui nel caso in cui sia utile a chiunque.

Nella maggior parte dei casi, se è necessario che il codice legacy esterno interagisca con lo stato dell'interfaccia utente o il funzionamento interno dell'applicazione, un servizio può essere utile per astrarre tali modifiche. Se un codice esterno interagisce direttamente con il tuo controller angolare, componente o direttiva, stai accoppiando la tua app pesantemente con il tuo codice legacy che è una cattiva notizia.

Quello che ho finito per usare nel mio caso, è una combinazione di globali accessibili dal browser (cioè la finestra) e gestione degli eventi. Il mio codice ha un motore di generazione di moduli intelligenti che richiede l'output JSON da un CMS per inizializzare i moduli. Ecco quello che ho fatto:

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

Modulo di Servizio Schema viene creata usando iniettore angolare come previsto:

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

E nei miei controllori: function() { 'use strict';

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

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

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

Finora questa è pura programmazione angolare e orientata ai servizi javascript. Ma l'integrazione legacy viene qui:

<script type="text/javascript"> 

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

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

})(window); 
</script> 

Ovviamente ogni approccio ha i suoi pregi e difetti. I vantaggi e l'utilizzo di questo approccio dipendono dall'interfaccia utente. Gli approcci precedentemente suggeriti non funzionano nel mio caso poiché il mio schema di modulo e il codice precedente non hanno controllo e conoscenza degli ambiti angolari. Quindi la configurazione della mia app basata su angular.element('element-X').scope(); potrebbe interrompere l'app se cambiamo gli ambiti. Ma se la tua app ha conoscenze specifiche e può fare affidamento su di essa non cambiando spesso, ciò che viene suggerito in precedenza è un approccio praticabile.

Spero che questo aiuti. Qualsiasi feedback è anche il benvenuto.

Problemi correlati