57

I am developing a mobile application using Cordova and AngularJS. How do I restrict bootstrapping of AngluarJS before Cordova device ready. Basically I don't want to use any of AngularJS controllers before device ready.

Anwer
  • 689
  • 1
  • 7
  • 10

6 Answers6

87

Manually bootstrap your Angular app:

Remove your ng-app attribute from your HTML code, so Angular doesn't start itself.

Add something like this to you JavaScript code:

document.addEventListener("deviceready", function() {
    // retrieve the DOM element that had the ng-app attribute
    var domElement = document.getElementById(...) / document.querySelector(...);
    angular.bootstrap(domElement, ["angularAppName"]);
}, false);

Angular documentation for bootstrapping apps.

cnmuc
  • 6,025
  • 2
  • 24
  • 29
TheHippo
  • 61,720
  • 15
  • 75
  • 100
  • 1
    does the deviceready event ALWAYS fire after the documentready? if not there will be times when this wont work – olanod Sep 30 '14 at 04:20
  • 4
    This won't work when the app is running in a browser, without Cordova. My solution below deals with that issue. – Michael Oryl Dec 12 '14 at 18:53
  • See my answer below for @olanod's concern. @michaeloryl How about `window.ionic.Platform.ready()` as @wade-anderson answered? – levsa May 14 '15 at 06:32
69

I'm using the following solution, which allows AngularJS to be bootstrapped when running with Cordova as well as when running directly in a browser, which is where much of my development takes place. You have to remove the ng-app directive from your main index.html page since that's what the manual bootstrapping is replacing.

UPDATE: I've since switched to the following method, which I think is cleaner. It works for Ionic as well as vanilla Cordova/PhoneGap. It should be the last bit of JavaScript to run - perhaps inside a script tag before the /body tag.

  angular.element(document).ready(function () {
    if (window.cordova) {
      console.log("Running in Cordova, will bootstrap AngularJS once 'deviceready' event fires.");
      document.addEventListener('deviceready', function () {
        console.log("Deviceready event has fired, bootstrapping AngularJS.");
        angular.bootstrap(document.body, ['app']);
      }, false);
    } else {
      console.log("Running in browser, bootstrapping AngularJS now.");
      angular.bootstrap(document.body, ['app']);
    }
  });

Here's the older solution I used:

// This is a function that bootstraps AngularJS, which is called from later code
function bootstrapAngular() {
    console.log("Bootstrapping AngularJS");
    // This assumes your app is named "app" and is on the body tag: <body ng-app="app">
    // Change the selector from "body" to whatever you need
    var domElement = document.querySelector('body');
    // Change the application name from "app" if needed
    angular.bootstrap(domElement, ['app']);
}

// This is my preferred Cordova detection method, as it doesn't require updating.
if (document.URL.indexOf( 'http://' ) === -1 
        && document.URL.indexOf( 'https://' ) === -1) {
    console.log("URL: Running in Cordova/PhoneGap");
    document.addEventListener("deviceready", bootstrapAngular, false);
} else {
    console.log("URL: Running in browser");
    bootstrapAngular();
}

If you run into problems with the http/https detection method, due to, perhaps, loading a Cordova app into the phone from the web, you could use the following method instead:

function bootstrapAngular() {
    console.log("Bootstrapping AngularJS");
    // This assumes your app is named "app" and is on the body tag: <body ng-app="app">
    // Change the selector from "body" to whatever you need
    var domElement = document.querySelector('body');
    // Change the application name from "app" if needed
    angular.bootstrap(domElement, ['app']);
}

// This method of user agent detection also works, though it means you might have to maintain this UA list
if (navigator.userAgent.match(/(iOS|iPhone|iPod|iPad|Android|BlackBerry)/)) {
    console.log("UA: Running in Cordova/PhoneGap");
    document.addEventListener("deviceready", bootstrapAngular, false);
} else {
    console.log("UA: Running in browser");
    bootstrapAngular();
}

Note that you still need the same bootstrapAngular function from the first example.

Why manually bootstrap AngularJS with Cordova/PhoneGap/Ionic?

Some people getting here might not know why you would want to do this in the first place. The issue is that you could have AngularJS code that relies on Cordova/PhoneGap/Ionic plugins, and those plugins won't be ready until after AngularJS has started because Cordova takes longer to get up and running on a device than the plain old Javascript code for AngularJS does.

So in those cases we have to wait until Cordova/PhoneGap/Ionic is ready before starting up (bootstrapping) AngularJS so that Angular will have everything it needs to run.

For example, say you are using the NG-Persist Angular module, which makes use of local storage for saving data on a browser, iOS Keychain plugin when running on iOS, and the cordova-plugin-file when running on Android. If your Angular app tries to load/save something right off the bat, NG-Persist's check on window.device.platform (from the device plugin) will fail because the mobile code hasn't completed startup yet, and you'll get nothing but a white page instead of your pretty app.

Michael Oryl
  • 20,856
  • 14
  • 77
  • 117
  • 1
    To be able to test in the browser I usually create a fake cordova.js file that contains some code, eg automatically calling at once any "deviceready" registered event. – user276648 Jan 30 '15 at 01:29
  • @user276648 could you share your fake cordova.js (e.g. as github gist)? – hgoebl Jul 01 '15 at 09:26
  • 1
    If I do this, I get "Uncaught Error: [$injector:modulerr] Failed to instantiate module app due to: Error: [$injector:nomod] Module 'app' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument." What can I do? – Ken Vernaillen Dec 22 '15 at 13:12
  • 3
    @KenVernaillen My guess is that your main app module is not called `app` as it is in my example. Look at both `angular.bootstrap(document.body, ['app']);` lines and change it to what the main module in your app is called. Don't forget to vote it up if it works for you.... – Michael Oryl Dec 22 '15 at 14:38
  • @MichaelOryl Sir does this mean hat i no longer need to wrap device ready on my individual plugin calls? – konzo Mar 08 '16 at 02:46
32

If you are using Ionic, this solution works for browsers and devices. Credit to romgar on this thread.

window.ionic.Platform.ready(function() {
    angular.bootstrap(document, ['<your_main_app']);
});

Still need to remove ng-app from your DOM element.

Wade Anderson
  • 2,461
  • 1
  • 20
  • 22
  • 1
    This is pretty awesome – Kenny Ki Mar 28 '15 at 14:05
  • This works only if you use Ionic (which as of the time of the writing of the question didn't existed). There are still developer out there using Cordova and Angular without Ionic. – TheHippo Apr 01 '15 at 22:16
  • @TheHippo thank you sir. I missed that. I've edited my answer to include the stipulation that you are using Ionic. – Wade Anderson Apr 02 '15 at 23:22
  • Could you put a full example? I couldn't get it working. This code goes inside the angular.module.run, $ionicPlatform.ready, or outside? – Carlos Goce Sep 30 '15 at 07:57
  • 1
    @CarlosGoce the code snippet shouldn't be run inside Angular. It's execute in pure Javascript at the bottom of your HTML page. Remember to not remove the ng-app attribute if you do have it on your HTML - it's functionality is replaced by bootstrapping Angular with the above snippet – j7m Oct 21 '15 at 12:20
  • @WadeAnderson: Just saved my life! :) – Silent_Coder Nov 17 '15 at 05:04
  • while not initializing with "ng-app" although the desired intention is accomplished but when the application loads it starts with showing my raw angular just like "{{person.firstname}}{{person.lastname}}" for around 1second and then the app is properly rendered. How can I fix this? Thanking you guys! – Dynamic Remo Dec 12 '15 at 23:07
  • @WadeAnderson Are you sure this works on a device? I am not able to get the device uuid in angularjs as the device is undefined still. If I wrap your code in document.addEventListener("deviceready", function () {... then it works on a device, however, then it won't work on a browser because deviceready won't ever fire. – Thomas Jan 22 '16 at 13:12
5

This solution became more robust when I used:

angular.element(document).ready(function () {
  var domElement = document.getElementById('appElement');
  angular.bootstrap(domElement, ["angularAppName"]);
});

UPDATE

My suggestion was to put the above within the appropriate deviceready function, e.g.:

document.addEventListener("deviceready", function() {
    angular.element(document).ready(function () {
      var domElement = document.getElementById('appElement');
      angular.bootstrap(domElement, ["angularAppName"]);
    });
}, false);
levsa
  • 930
  • 9
  • 16
  • 2
    `documentReady != deviceready` If you use any cordova specific function early in your code it could be possible that these function aren't ready yet. – TheHippo Jul 03 '14 at 14:42
  • That was supposed to be within the deviceready handler and was taken from the bootstrap documentation. – levsa Jul 03 '14 at 19:20
  • Also if you include your JavaScript after the HTML elements that will hold the angular app you do not need to wait until the DOM is completely loaded. – TheHippo Jul 24 '14 at 14:18
  • 1
    I don't understand the downvote. My proposal was from the bootstrap documentation, and the complete code would be (which works fine for me): `document.addEventListener("deviceready", function() { angular.element(document).ready(function () { // retrieve the DOM element that had the ng-app attribute var domElement = document.getElementById('appElement'); angular.bootstrap(domElement, ["angularAppName"]); }); }, false);` – levsa Aug 13 '14 at 15:02
  • Could you elaborate on why you say this was more robust? Did the solution by @TheHippo sometimes fail for you? – Peter Oct 11 '14 at 15:23
  • Yes it did. There was a race condition which cause the inialization to fail, maybe because of the way ionic initializes things. – levsa Feb 06 '15 at 11:33
  • You're managing to hook the Angular/Bootstrap ready functions, but not the Cordova deviceready event, which is what the poster was asking about. – Chris Rae May 11 '15 at 18:10
  • I'm trying to do both _deviceready_ and _document ready_, since angular documentation recommends `angular.element(document).ready()` before calling `angular.bootstrap()`. But if `window.ionic.Platform.ready()` (or `document.addEventListener("deviceready", ...)`) always implies _document ready_ than it shouldn't be needed. – levsa May 14 '15 at 06:25
2

On using the solution from TheHippo:

document.addEventListener("deviceready", function() {
    // retrieve the DOM element that had the ng-app attribute
    var domElement = document.getElementById(...) / document.querySelector(...);
    angular.bootstrap(domElement, ["angularAppName"]);
}, false);

It doesn't work in the browser because "cordova.js" gets resolved by the Cordova or Phonegap building process and is not available in your localhost or emulated testing environment.

Thus the "deviceready" event is never fired. You can simply fire it manually in your browsers console.

var customDeviceReadyEvent = new Event('deviceready');
document.dispatchEvent(customDeviceReadyEvent);

Also make sure, that the bootstrap of angular gets triggered after setting all of you angular modules/controllers/factories/directives etc.

devjsp
  • 146
  • 6
0

In most cases you probably don't need to block loading your angular app until after deviceready (mind that it can take several seconds for deviceready to fire if you have a lot of plugins).

Instead you can use something like this lib (https://github.com/arnesson/angular-cordova) which solves the deviceready issues for you by automatically buffering calls and then execute them after deviceready has been fired.

ropsnou
  • 164
  • 2
  • 3