web-dev-qa-db-fra.com

Appelez AngularJS à partir du code existant

J'utilise AngularJS pour créer des contrôles HTML qui interagissent avec une application Flex héritée. Tous les rappels de l'application Flex doivent être attachés à la fenêtre DOM.

Par exemple (en AS3)

ExternalInterface.call("save", data);

Appellera

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

Dans la fonction de redimensionnement JS, j'aimerais envoyer un événement qu'un contrôleur peut entendre. Il semble que la création d'un service est la voie à suivre. Pouvez-vous mettre à jour un service extérieur à AngularJS? Un contrôleur peut-il écouter les événements d'un service? Dans un expérience (cliquez pour violon) Je pensais pouvoir accéder à un service, mais la mise à jour des données du service ne se reflétait pas dans la vue (dans l'exemple, un <option> devrait être ajouté au <select>).

Merci!

180
kreek

Interopérabilité de l'extérieur de angular à angular est identique à l'application de débogage angular ou à l'intégration à une bibliothèque tierce.

Pour tout élément DOM, vous pouvez faire ceci:

  • angular.element(domElement).scope() pour obtenir la portée actuelle de l'élément
  • angular.element(domElement).injector() pour obtenir l'injecteur actuel de l'application
  • angular.element(domElement).controller() pour obtenir l'instance ng-controller.

À partir de l'injecteur, vous pouvez obtenir n'importe quel service dans l'application angular. De même, à partir de la portée, vous pouvez appeler toutes les méthodes qui y ont été publiées.

Gardez à l'esprit que toute modification apportée au modèle angular ou toute invocation de méthode sur l'étendue doit être encapsulée dans $apply() comme ceci:

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

Misko a donné la bonne réponse (évidemment), mais certains novices pourraient en avoir besoin d’être simplifiée davantage.

Lorsque vous appelez du code AngularJS depuis des applications héritées, considérez le code AngularJS comme une "micro-application" existant dans un conteneur protégé de votre application héritée. Vous ne pouvez pas y appeler directement (pour une très bonne raison), mais vous pouvez effectuer des appels à distance via l'objet $ scope.

Pour utiliser l'objet $ scope, vous devez obtenir le descripteur de $ scope. Heureusement, c'est très facile à faire.

Vous pouvez utiliser l'identifiant de n'importe quel élément HTML de votre code HTML "micro-application" AngularJS pour obtenir le descripteur de la portée de $ application AngularJS.

Par exemple, supposons que nous voulions appeler quelques fonctions de notre contrôleur AngularJS telles que sayHi () et sayBye (). Dans le AngularJS HTML (vue), nous avons un div avec l’id "MySuperAwesomeApp". Vous pouvez utiliser le code suivant, associé à jQuery pour obtenir le descripteur de $ scope:

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

Vous pouvez maintenant appeler vos fonctions de code AngularJS à l’aide du descripteur de portée:

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

microappscope.sayHi();

microappscope.sayBye();

Pour rendre les choses plus pratiques, vous pouvez utiliser une fonction pour saisir la poignée de l’étendue chaque fois que vous souhaitez y accéder:

function microappscope(){

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

}

Vos appels ressembleraient alors à ceci:

microappscope().sayHi();

microappscope().sayBye();

Vous pouvez voir un exemple de travail ici:

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

Je l'ai également montré dans un diaporama pour le groupe Ottawa AngularJS (passez directement aux 2 dernières diapositives)

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

86
Peter Drinnan

La meilleure explication du concept que j'ai trouvé se trouve ici: https://groups.google.com/forum/#!msg/angular/kqFrwiysgpA/eB9mNbQzcHwJ

Pour vous sauver le 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);
}); 
24
Wiseman

Suite aux autres réponses. Si vous ne souhaitez pas accéder à une méthode dans un contrôleur mais souhaitez accéder directement au service, vous pouvez procéder de la manière suivante:

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

Grâce au post précédent, je peux mettre à jour mon modèle avec un événement asynchrone.

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

Je déclare mon modèle

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

Et je mets à jour mon modèle de l'extérieur de ma portée

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

Une méthode plus sûre et plus performante, en particulier lorsque les données de débogage sont désactivées, consiste à utiliser une variable partagée pour gérer une fonction de rappel. Votre contrôleur angular implémente cette fonction pour renvoyer ses internes au code externe.

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

Généralisé en tant que directive réutilisable:

J'ai créé une directive 'exposeScope' qui fonctionne de la même manière mais dont l'utilisation est plus simple:

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

Ceci stocke l'étendue actuelle (qui est donnée à la fonction de lien de la directive) dans un objet 'étendues' global qui est le détenteur de toutes les portées. La valeur fournie à l'attribut de directive est utilisée comme nom de propriété de la portée dans cet objet global.

Voir la démo ici . Comme je l'ai montré dans la démo, vous pouvez déclencher des événements jQuery lorsque la portée est stockée et supprimée de l'objet global "Scopes".

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

Notez que, je n'ai pas testé le sur ('scopeDestroyed') lorsque l'élément réel est supprimé du DOM. Si cela ne fonctionne pas, déclencher l'événement sur le document lui-même au lieu de l'élément peut aider. (voir le script app.js) dans le démo plunker.

6
Cagatay Kalan

Je sais que c'est une vieille question mais je cherchais des options pour le faire récemment, alors je pensais mettre mes conclusions ici au cas où cela serait utile à qui que ce soit.

Dans la plupart des cas, s'il est nécessaire que le code hérité externe interagisse avec l'état de l'interface utilisateur ou le fonctionnement interne de l'application, un service peut être utile pour résumer ces modifications. Si un code externe interagit directement avec votre contrôleur, composant ou directive angular, vous couplez fortement votre application avec votre code hérité, ce qui est une mauvaise nouvelle.

Ce que j’ai finalement utilisé, c’est une combinaison de fonctions globales accessibles au navigateur (par exemple, d’une fenêtre) et de la gestion des événements. Mon code a un moteur de génération de formulaire intelligent qui nécessite la sortie JSON d'un CMS pour initialiser les formulaires. Voici ce que j'ai fait:

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

Le service de schéma de formulaire est créé à l'aide de l'injecteur angular, comme prévu:

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

Et dans mes contrôleurs: function () {'use strict';

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

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

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

Jusqu'ici, il s'agit d'une programmation pure angular et orientée services javascript. Mais l'intégration de l'héritage vient ici:

<script type="text/javascript">

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

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

 })(window);
</script>

De toute évidence, chaque approche a ses avantages et ses inconvénients. Les avantages et l’utilisation de cette approche dépendent de votre interface utilisateur. Les approches précédemment suggérées ne fonctionnent pas dans mon cas car mon schéma de formulaire et mon code hérité n’ont aucun contrôle ni aucune connaissance de la portée de angular. Par conséquent, la configuration de mon application sur angular.element('element-X').scope(); pourrait potentiellement casser l'application si nous modifions les portées. Mais si votre application a une connaissance de la portée et peut compter sur elle pour ne pas changer souvent, ce qui a été suggéré précédemment est une approche viable.

J'espère que cela t'aides. Tous les commentaires sont également les bienvenus.

3
Shakus