djna
djna

Reputation: 55907

Triggering angular from third party initialisation

I'm new to angular, no problems with a simple angular apps, but now I want to integrate with a third party component. I can think of a few kluges to achieve what I'm trying to do, but I'd prefer to use a clean solution to the general requirement: how to call angular code from "outside" angular?

I want my angular application to initialise a splash view, then wait for the third-party software to initialise and then have angular show the main view. The third-party software initialises via various asynchronous calls, and might take a second or two, or in extreme cases longer. It provides a call-back function that will be called when it's ready.

So right now to simulate the effect I have got:

.controller('MySplash', ['$scope', '$location', '$q', 
                             function($scope, $location, $q) {   

      $scope.waitForInit = $q.defer();

      $scope.waitForInit.promise.then(function() {
          $location.path('/tab/MyMainView');
      });

      $scope.waitForInit.resolve();  // fake the post-init triggering
    }])

So this shows the splash screen and when the deferred is satisfied shows the main screen, in this case of course it happens immediately.

I also have a function provided by the third-party software

 CalledWhenThirdPartyReady() {
      // put some code here
 }

And in concept all I want to do is move that resolve call into this callback

 CalledWhenThirdPartyReady() {
       $scope.waitForInit.resolve();
 }

Fundamentally, my puzzle is how to get some code that is effectively stand-alone have access to DI. In concept all I want to do is call an angular service, or access an angular variable. I've tried this kind of thing

 CalledWhenThirdPartyReady() {

      angular.module('myApp')

   .run(["$rootScope", "$location",
              function ($rootScope, $location) {
                // just to show access to DI-ed variables
                console.log("Run in module", $rootScope, ",", $location);

            }] );
 }

Thinking that the run method would be called and I'd have access to the injected variables, but run never gets triggered I guess because I'm registering the run() too late.

Upvotes: 0

Views: 525

Answers (2)

djna
djna

Reputation: 55907

The link given by tasseKATT above gives the general principle for external interactions, in particular the looking up of angular entry points via the DOM.

In the end I took the approach of adding an attribute to a module, effectively using the module as scratchpad for communicating between parts of my app. (I guess slightly better than using a global.)

in my controller module:

angular.module('starter.controllers', [])


.controller('PetSplashCtrl', ['$scope', '$location', '$q', 
                         function($scope, $location, $q) {   

  var initDeferred = $q.defer();
  angular.module('starter.controllers').initDeferred = initDeferred;

  initDeferred.promise.then(function() {
      $location.path('/tab/pets');
  });

}]);

and in the third-party callback added

angular.module('starter.controllers').initDeferred.resolve();

There is a race condition between angular and the thirdPartyCode, so I needed to check that angular was ready and the Promise was set up before calling resolve():

function thirdPartyCallback(){

var initWaiter = function(){

    if ( typeof angular !== 'undefined' 
              && angular.module('starter.controllers') 
              && angular.module('starter.controllers').initDeferred
       ){
        angular.module('starter.controllers').initDeferred.resolve();
        console.log("init done");

    } else {
        console.log("init wait");
        setTimeout(initWaiter, 1000);
    }               
};

initWaiter();

}

I'm not sure whether looking-up a service via the DOM would be better than this.

Upvotes: 1

tasseKATT
tasseKATT

Reputation: 38490

Try using $apply:

$apply() is used to execute an expression in angular from outside of the angular framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). Because we are calling into the angular framework we need to perform proper scope life cycle of exception handling, executing watches.

For example:

CalledWhenThirdPartyReady() {
    $scope.$apply($scope.waitForInit.resolve());  
}

This requires that CalledWhenThirdPartyReady has access to the $scope, for example if it's defined inside your controller. If you need CalledWhenThirdPartyReady to be defined outside of your Angular application you need to handle it in some other way.

Example to call a function in a controller from the outside:

JS:

var app = angular.module('myApp', []);

app.controller('MySplash', ['$scope', '$location', '$q',
  function($scope, $location, $q) {

    $scope.waitForInit = $q.defer();

    $scope.waitForInit.promise.then(function() {
      console.log('Resolved.');
    });

    $scope.resolve = function () {
       $scope.waitForInit.resolve();
    }
  }
]);

Html:

<body ng-controller="MySplash">
</body>

<script type="text/javascript">
  window.onload = function() {

    console.log('Loading.');
    window.setTimeout(function () {
      var controllerScope = angular.element(document.querySelector('body')).scope();
      controllerScope.resolve();
    }, 1000);
  }
</script>

Upvotes: 1

Related Questions