Call AngularJS from legacy code

91,261

Solution 1

Interop from outside of angular to angular is same as debugging angular application or integrating with third party library.

For any DOM element you can do this:

  • angular.element(domElement).scope() to get the current scope for the element
  • angular.element(domElement).injector() to get the current app injector
  • angular.element(domElement).controller() to get a hold of the ng-controller instance.

From the injector you can get a hold of any service in angular application. Similarly from the scope you can invoke any methods which have been published to it.

Keep in mind that any changes to the angular model or any method invocations on the scope need to be wrapped in $apply() like this:

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

Solution 2

Misko gave the correct answer (obviously), but some of us newbies may need it simplified further.

When if comes to calling AngularJS code from within legacy apps, think of the AngularJS code as a "micro app" existing within a protected container in your legacy application. You cannot make calls to it directly (for very good reason), but you can make remote calls by way of the $scope object.

To use the $scope object, you need to get the handle of $scope. Fortunately this is very easy to do.

You can use the id of any HTML element within your AngularJS "micro-app" HTML to get the handle of the AngularJS app $scope.

As an example, let's say we want to call a couple of functions within our AngularJS controller such as sayHi() and sayBye(). In the AngularJS HTML (view) we have a div with the id "MySuperAwesomeApp". You can use the following code, combined with jQuery to get the handle of $scope:

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

Now you can call your AngularJS code functions by way of the scope handle:

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

microappscope.sayHi();

microappscope.sayBye();

To make things more convenient, you can use a function to grab the scope handle anytime you want to access it:

function microappscope(){

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

}

Your calls would then look like this:

microappscope().sayHi();

microappscope().sayBye();

You can see a working example here:

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

I also showed this in a slideshow for the Ottawa AngularJS group (just skip to the last 2 slides)

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

Solution 3

Greatest explanation of the concept I've found is situated here: https://groups.google.com/forum/#!msg/angular/kqFrwiysgpA/eB9mNbQzcHwJ

To save you the clicking:

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

Solution 4

Thanks to the previous post, I can update my model with an asynchronous event.

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

I declare my model

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

And i update my model from outside my scope

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

Solution 5

Further to the other answers. If you don't want to access a method in a controller but want to access the service directly you can do something like this:

// 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 
Share:
91,261

Related videos on Youtube

kreek
Author by

kreek

I have extensive experience designing and building scalable distributed web services. I’m currently a Staff Software Engineer with Ad Hoc LLC and the Department of Veterans Affairs. For the past three years I’ve helped turn the VA’s web offerings from a fractured experience into a one-stop shop. Allowing veterans to efficiently receive the benefits they’ve earned. I was one of the founding developers on vets.gov. A United States Digital Service project functioning as a start-up within the VA. Veteran outcomes improved dramatically via vets.gov. So much so that within two years of launch the VA secretary decided it should become the new va.gov. How we built the site received as much praise as the end-product. Agile methodologies, automated tests, CI/CD, cloud services (AWS), active monitoring, and user centered design were all foreign to the VA before vets.gov. Those practices are being propagated throughout the VA via my current project. The Veteran Service Platform, or VSP. VSP helps development teams succeed, by building on top of our APIs, and running our digital services playbook.

Updated on August 19, 2020

Comments

  • kreek
    kreek over 3 years

    I'm using AngularJS to build HTML controls that interact with a legacy Flex application. All callbacks from the Flex app must be attached to the DOM window.

    For example (in AS3)

    ExternalInterface.call("save", data);
    

    Will call

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

    From within the JS resize function I'd like to dispatch an event that a controller can hear. It seems that creating a service is the way to go. Can you update a service from outside of AngularJS? Can a controller listen for events from a service? In one experiment (click for fiddle) I did it seems like I can access a service but updating the service's data doesn't get reflected in the view (in the example an <option> should be added to the <select>).

    Thanks!

    • Thanh Nguyen
      Thanh Nguyen about 11 years
      Note that in the jsfiddle above the injector is obtained without targeting an element within the app using var injector = angular.injector(['ng', 'MyApp']);. Doing this will give you a completely new context and a duplicate myService. That means you'll end up with two instances of the service and model and will be adding data to the wrong place. You should instead target an element within the app using angular.element('#ng-app').injector(['ng', 'MyApp']). At this point you can then use $apply to wrap model changes.
  • mindplay.dk
    mindplay.dk almost 12 years
    this works, but I wish there was some way to get from a Module directly to it's scope - is that possible? Having to go back an select the [ng-app] root-node seems backwards when I already have a reference to the Module...
  • Samuel
    Samuel almost 12 years
    I've run into the same thing recently. I can get to the dom element so it's not a bit deal to get to the scope, just would make more sense to go via the globally registered module.
  • Emil
    Emil over 11 years
    I can't get this to work: I'm calling angular.element(document.getElementById(divName)).scope(), but I am not able to invoke any functions from it, it just returns "undefined" in the console.
  • goosemanjack
    goosemanjack about 11 years
    The above works when the app and controller co-exist in the same element. For more complex apps that utilize an ng-view directive to a template, you must get the first element within the view, not the DOM element of the entire app. I had to poke around elements with a document.getElementsByClassName('ng-scope'); node list to figure out the correct scope DOM element to grab.
  • nycynik
    nycynik almost 11 years
    works great for me, however I use a class selector, should work fine.
  • dykzei eleeot
    dykzei eleeot almost 11 years
    @Misko Hevery how do I get to domElement's ng-bind property?
  • Bibin
    Bibin almost 11 years
    Even I'm facing the same problem as described above by @Emil, it's returning undefined. Any help ?
  • kleopatra
    kleopatra almost 11 years
    Note that link-only answers are discouraged, SO answers should be the end-point of a search for a solution (vs. yet another stopover of references, which tend to get stale over time). Please consider adding a stand-alone synopsis here, keeping the link as a reference.
  • Danny Kopping
    Danny Kopping over 10 years
    Try listening for the document ready() event before trying to grab anything from Angular
  • Joe
    Joe over 10 years
    Nice additional clarification. Thanks.
  • JerryKur
    JerryKur almost 10 years
    I know this is a really old thread, but I think I am running into this issue. Does anyone have any code that shows how to walk the list to figure out the DOM element to grab?
  • JerryKur
    JerryKur almost 10 years
    Ignore my question. I was able to get this work by just using document.getElementById('any-Control-That-Has-An-NG-Directiv‌​e').scope().
  • K. Norbert
    K. Norbert over 9 years
    element().scope() will not work if debug data is turned off, which is the recommendation for production. Doesn't that make it useless in this scenario? This will only for testing/debugging.
  • Purefan
    Purefan over 9 years
    Beautiful explanation! allowed me to circumvent a form validation by doing this: <input type="button" onclick="angular.element(this).scope().edit.delete();" value="delete">
  • frosty
    frosty over 9 years
    You won't want to do this in Angular 1.3. The Angular team didn't intend to have us calling ".scope()" on elements in production code. It was meant to be a debug tool. So, starting in Angular 1.3, you can turn this off. Angular will stop attaching the scope to the element using jQuery's .data function. This will speed your app up. Additionally, handing off your scopes to jquery's caching features will create memory leaks. So, you should definitely turn this off, to speed up your app. Angular's site has a production guide that you should use to learn more.
  • ftravers
    ftravers almost 9 years
    if you utilize ng-view and split your views into their own files. you can put and id in the top HTML element then do document.getElementById() that id. This gives you access to the scope of that controller. methods/properties etc... just putting a fine point on goosemanjack's comment.
  • Cagatay Kalan
    Cagatay Kalan over 8 years
    To encapsulate the $scope.$apply code, you can put in inside a method on scope like $scope.externalAction = function() {...} and in this method, you can use $scope.$apply. This way, external code does not have to care about calling apply manually or checking if it is in phase or not.
  • Cotta
    Cotta over 8 years
    How good to see someone going straight to the point, without the "you should not do it" without an actual answer. The advice is still valid, but let the one that needs the answer judge if something should or not be done.
  • cesarpachon
    cesarpachon about 8 years
    how about angular 2.0? it seems to me like if there is no "angular" object in the global context..