1

I'm using the cloud enpoints demo with AngularJS and I'm running into an infinite loop with their suggested method of running the authorization after the client.js is loaded. Here is the suggested method.

First, after all other script tags (for Angular and other JS files, I'm doing this):

<script>
    function init() {
        window.init();
    }
</script>
<script src="https://apis.google.com/js/client.js?onload=init"></script>

Then, in a controller, I handle the window init like:

    $window.init = function () {
        // Loads the OAuth and helloworld APIs asynchronously, and triggers login
        // when they have completed.
        var apisToLoad;
        var callback = function () {
            if (--apisToLoad == 0) {
                googleAPI.signin(true,
                    googleAPI.userAuthed);
                appContext.appReady = true;
                alert('loaded');
            }
        }

        apisToLoad = 2; // must match number of calls to gapi.client.load()
        gapi.client.load('helloworld', 'v1', callback, googleAPI.apiRoot);
        gapi.client.load('oauth2', 'v2', callback);
    };

What I think I'm finding is that there is a race condition here where the $window.init is not set up early enough so I end up with the message:

Uncaught RangeError: Maximum call stack size exceeded

This is due to the fact that the "window.init()" just calls back to the init() function and exceeds the stack.

Any suggestions on how I can better handle this? Thanks.

Michael Witt
  • 1,582
  • 4
  • 22
  • 43

4 Answers4

2

Looks like your angular controllers are not loading/executing in time, can't tell why but you could wait for document ready, in true jQuery fashion:

function init() {
    angular.element(document).ready(function() {
        window.init();
    });
}

Angular should've finished loading by then.

Jaime Gómez
  • 6,961
  • 3
  • 40
  • 41
  • Didn't help actually. Just went into another infinite loop as I think the window.init() was still causing the "ready" to catch again. – Michael Witt Feb 26 '15 at 21:47
  • And you're sure your angular code is loading and executing correctly? Change the function name in the controller and see what happens, it'll either work or throw undefined. – Jaime Gómez Feb 26 '15 at 22:05
  • If I just rename my function to "init1", nothing happens, it just doesn't get called. However, if I rename the onload assignment to "init1" as well, then I get undefined on the "window.init();" call. What it looks like to me is that the "$window.init = function()" assignment is not really assigned. Finally, I noticed something I was doing wrong. See my answer below. – Michael Witt Feb 27 '15 at 00:41
  • it's not a solutiuon, really ! – mpgn Jun 03 '15 at 14:24
2

The first line is creating an infinite loop there because you are calling window.init inside the actual window.init.

<script>
    /**
     * Initializes the Google API JavaScript client. Bootstrap the angular module after loading the Google libraries
     * so that Google JavaScript library ready in the angular modules.
     */
    function init() {
        gapi.client.load('conference', 'v1', null, '//' + window.location.host + '/_ah/api');
        gapi.client.load('oauth2', 'v2', callback);
    };
</script>
<script src="//apis.google.com/js/client:plusone.js?onload=init"></script>

You can try this code to see if makes more sense for you

Albertoimpl
  • 321
  • 3
  • 8
  • Yeah I can see how this would work, but the idea is to get inside the Angular controller logic to handle the authentication and this would pull that out. I know it would be workable, but I want to store the session information in an Angular factory. – Michael Witt Feb 26 '15 at 21:49
  • take a look to this code: https://github.com/udacity/ud859/tree/master/ConferenceCentral_Complete/src/main/webapp/js It might give you a better approach to handle it. – Albertoimpl Feb 27 '15 at 16:02
  • Dude you rock! That did it. – Michael Witt Feb 27 '15 at 21:40
1

By doing this, you're telling window.init to call itself, creating an infinite loop.

<script>
    function init() {
        window.init();
    }
    init===window.init; // => true
</script>
<script src="https://apis.google.com/js/client.js?onload=init"></script>

If you look at my code more closely, you'll see that I name the functions differently, like so:

<script>
    function init() {
        window.initGapi();
    }
    init===window.initGapi; // => false
</script>
<script src="https://apis.google.com/js/client.js?onload=init"></script>

Then simply define initGapi in your controller instead:

$window.initGapi = function () {}

The code in the comments to the accepted answer waits until the api is loaded to bootstrap the app, which takes longer.

Community
  • 1
  • 1
willlma
  • 7,353
  • 2
  • 30
  • 45
0

Put this in the "I missed something really basic" bin:

I noticed that I forgot something in my controller definition:

topNavBar.$inject = ['$location', 'appContext', 'logger'];
function topNavBar($location, $window, appContext, logger) {

Notice, no '$window' in the inject, so it got the definition for appContext and doing a "$window.init = " had absolutely no effect.

Michael Witt
  • 1,582
  • 4
  • 22
  • 43
  • One other little note. This method, which I first saw [here](https://cloud.google.com/developers/articles/angularjs-cloud-endpoints-recipe-for-building-modern-web-applications/), works better if it is in global scope (no [iffe](http://en.wikipedia.org/wiki/Immediately-invoked_function_expression)). – Michael Witt Feb 27 '15 at 00:56
  • Ugh! Seems like there's still a race condition here. Maybe 1 out of 10 tries, I get the stack exceeded message. – Michael Witt Feb 27 '15 at 01:44