Chiama AngularJS dal codice legacy


180

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

Ad 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 funzione di ridimensionamento JS mi piacerebbe inviare un evento che un controller può sentire. Sembra che la creazione di un servizio sia la strada da percorrere. È possibile aggiornare un servizio al di fuori di AngularJS? Un controller può ascoltare eventi da un servizio? In un esperimento (fai clic per suonare il violino), ho fatto sembrare di poter accedere a un servizio ma l'aggiornamento dei dati del servizio non si riflette nella vista (nell'esempio si <option>dovrebbe aggiungere a <select>).

Grazie!


1
Si noti che nella jsfiddle sopra l'iniettore si ottiene senza prendere di mira un elemento all'interno dell'app usando var injector = angular.injector(['ng', 'MyApp']);. In questo modo ti darà un contesto completamente nuovo e un duplicato myService. Ciò significa che finirai con due istanze del servizio e del modello e aggiungerai i dati nel posto sbagliato. Dovresti invece scegliere come target un elemento all'interno dell'app angular.element('#ng-app').injector(['ng', 'MyApp']). A questo punto puoi quindi usare $ apply per racchiudere le modifiche al modello.
Thanh Nguyen

Risposte:


293

L'interoperabilità dall'esterno dell'angolare all'angolare equivale al debug dell'applicazione angolare o all'integrazione con una libreria di terze parti.

Per qualsiasi elemento DOM puoi fare questo:

  • angular.element(domElement).scope() per ottenere l'ambito corrente per l'elemento
  • angular.element(domElement).injector() per ottenere l'attuale iniettore app
  • angular.element(domElement).controller()per ottenere una sospensione ng-controllerdell'istanza.

Dall'iniettore è possibile ottenere qualsiasi servizio in applicazioni angolari. Allo stesso modo dal campo di applicazione è possibile invocare tutti i metodi che sono stati pubblicati su di esso.

Tenere presente che eventuali modifiche al modello angolare o eventuali invocazioni di metodi sull'ambito devono essere racchiuse in $apply()questo modo:

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

1
funziona, ma vorrei che ci fosse un modo per passare da un Modulo direttamente al suo ambito - è possibile? Dover tornare indietro e selezionare il [ng-app]nodo radice sembra indietro quando ho già un riferimento al Modulo ...
mindplay.dk

5
Non riesco a farlo funzionare: sto chiamando angular.element(document.getElementById(divName)).scope(), ma non sono in grado di richiamare alcuna funzione da esso, restituisce semplicemente "indefinito" nella console.
Emil,

1
Anche se sto affrontando lo stesso problema descritto sopra da @Emil, sta tornando indefinito. Qualsiasi aiuto ?
Bibin,

5
element (). scope () non funzionerà se i dati di debug sono disattivati, che è la raccomandazione per la produzione. Questo non lo rende inutile in questo scenario? Questo sarà solo per test / debugging.
K. Norbert,

4
Non vorrai farlo in Angular 1.3. Il team di Angular non intendeva farci chiamare ".scope ()" su elementi nel codice di produzione. Doveva essere uno strumento di debug. Quindi, a partire da Angular 1.3, puoi disattivarlo. Angular interromperà il collegamento dell'ambito all'elemento utilizzando la funzione .data di jQuery. Questo accelererà la tua app. Inoltre, passare gli ambiti alle funzionalità di memorizzazione nella cache di jquery creerà perdite di memoria. Quindi, dovresti assolutamente disattivarlo, per accelerare la tua app. Il sito di Angular ha una guida alla produzione che dovresti usare per saperne di più.
gelido

86

Misko ha dato la risposta corretta (ovviamente), ma alcuni di noi neofiti potrebbero averne bisogno ulteriormente semplificata.

Quando si tratta di chiamare il codice AngularJS dall'interno delle app legacy, pensare al codice AngularJS come una "micro app" esistente all'interno di un contenitore protetto nell'applicazione legacy. Non è possibile effettuare chiamate direttamente (per un'ottima 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 della "micro-app" HTML di AngularJS per ottenere l'handle dell'app $ scope AngularJS.

Ad esempio, supponiamo di voler chiamare un paio di funzioni all'interno del nostro controller AngularJS come sayHi () e sayBye (). Nell'HTML di AngularJS (visualizza) abbiamo un div con l'id "MySuperAwesomeApp". È possibile utilizzare il seguente codice, combinato con jQuery per ottenere l'handle di $ scope:

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

Ora puoi chiamare le funzioni del tuo codice AngularJS tramite l'handle scope:

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

microappscope.sayHi();

microappscope.sayBye();

Per rendere le cose più convenienti, è possibile utilizzare una funzione per afferrare l'handle scope ogni volta che si desidera accedervi:

function microappscope(){

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

}

Le chiamate sarebbero quindi così:

microappscope().sayHi();

microappscope().sayBye();

Puoi vedere un esempio funzionante qui:

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

Ho anche mostrato questo in una presentazione per il gruppo Ottawa AngularJS (salta alle ultime 2 diapositive)

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


4
Si noti che le risposte solo al collegamento sono sconsigliate, le risposte SO dovrebbero essere il punto finale di una ricerca di una soluzione (rispetto a un altro scalo di riferimento, che tende a diventare stantio nel tempo). Si prega di considerare l'aggiunta di una sinossi stand-alone qui, mantenendo il collegamento come riferimento.
Kleopatra,

Bel chiarimento aggiuntivo. Grazie.
Joe,

1
Bella spiegazione! mi ha permesso di eludere una convalida del modulo in questo modo:<input type="button" onclick="angular.element(this).scope().edit.delete();" value="delete">
Purefan il

24

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

Per salvarti 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 app più complesse che utilizzano una direttiva ng-view per un modello, è necessario ottenere il primo elemento all'interno della vista, non l'elemento DOM dell'intera app. Ho dovuto cercare tra gli elementi un document.getElementsByClassName ('ng-scope'); elenco dei nodi per capire l'elemento DOM dell'ambito corretto da afferrare.
goosemanjack

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

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

se utilizzi ng-view e dividi le tue visualizzazioni in file propri. puoi mettere e idnell'elemento HTML in alto quindi fare document.getElementById()quell'id. Questo ti dà accesso all'ambito di quel controller. metodi / proprietà ecc ... solo mettendo un punto fine sul commento di goosemanjack.
ftravers,

13

Oltre alle altre risposte. Se non si desidera accedere a un metodo in un controller, ma si desidera accedere direttamente al servizio, è possibile fare qualcosa del genere:

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

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 aggiorno il mio modello dall'esterno del mio ambito

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

6

Un modo più sicuro e performante, specialmente quando i dati di debug sono disattivati, è utilizzare una variabile condivisa per contenere una funzione di callback. Il controller angolare implementa questa funzione per riportare 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;
}
}]);

Generalizzato come 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 è dato alla funzione di collegamento della direttiva) in un oggetto globale "scopes" che è un detentore per tutti gli ambiti. Il valore fornito all'attributo direttiva viene utilizzato come nome della proprietà dell'ambito in questo oggetto globale.

Guarda la demo qui . Come mostrato nella demo, è possibile attivare eventi jQuery quando l'ambito viene archiviato e rimosso dall'oggetto globale "ambiti".

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

Si noti che non ho testato l'on ('scopeDestroyed') quando l'elemento reale viene rimosso dal DOM. Se non funziona, può essere utile attivare l'evento sul documento stesso anziché sull'elemento. (vedi app.js) nel plunker demo.


3

So che questa è una vecchia domanda ma stavo cercando opzioni per farlo di recente, quindi ho pensato di mettere le mie scoperte qui nel caso fosse 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 potrebbe essere utile per sottrarre tali modifiche. Se un codice esterno interagisce direttamente con il tuo controller angolare, componente o direttiva, stai accoppiando pesantemente la tua app con il tuo codice legacy, che è una cattiva notizia.

Quello che ho finito per usare nel mio caso è una combinazione di globi accessibili dal browser (ad es. Finestra) e gestione degli eventi. Il mio codice ha un motore di generazione di moduli intelligente che richiede l'output JSON da un CMS per avviare i moduli. Ecco cosa 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 ....

Il servizio Schema del modulo viene creato utilizzando l'iniettore angolare come previsto:

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

E nei miei 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(); 

Finora si tratta di pura programmazione orientata al servizio angolare e javascript. Ma l'integrazione legacy arriva 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 vantaggi e svantaggi. I vantaggi e l'uso di questo approccio dipendono dalla tua interfaccia utente. Gli approcci precedentemente suggeriti non funzionano nel mio caso poiché il mio schema del modulo e il codice legacy non hanno controllo e conoscenza degli ambiti angolari. Quindi la configurazione della mia app in base angular.element('element-X').scope(); potrebbe potenzialmente interrompere l'app se cambiamo gli ambiti intorno. Ma se la tua app è a conoscenza dell'ambito e può fare affidamento sul fatto che non cambia spesso, ciò che è stato suggerito in precedenza è un approccio praticabile.

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.