Yesterday I described how to make use of the Ionic framework and IBM's MobileFirst platform. Today I'm going to build on that first post and talk about bootstrapping. Specifically - how can you coordinate the use of your application with the Cordova deviceReady and MobileFirst initialization routines.
To give some context, I first talked about this issue last August in a blog post, Ionic and Cordova’s DeviceReady – My Solution. In that post, I described how I wanted to coordinate the first view of my Ionic app with Cordova's deviceReady event. The solution I used there was to create a view that acted a bit like a splash screen. When the deviceReady event fired, I then pushed the user to the real initial page of the application and ensured they never went back to that initial temporary page.
At the time, that felt like a bit of a hack, but it also seemed sensible. However, other people suggested another approach. You can simply tell Angular to not bootstrap until deviceReady is fired. For whatever reason, that didn't seem as sensible as my approach back then, but now... yeah... now that makes sense. So how would we do this in a MobileFirst application?
The first thing to note is that within MobileFirst and hybrid applications, running WL.Client.init
and using the success handler, wlCommonInit
, gives you the same behavior as listening for deviceReady in a "regular" Cordova application. So basically - wlCommonInit is deviceReady. With that in mind, I began by removing the automatic bootstrap from my code. I changed:
<body ng-app="starter">
to
<body>
Then I modified my wlCommonInit to do a manual bootstrap:
var wlInitOptions = {
// # To disable automatic hiding of the splash screen uncomment this property and use WL.App.hideSplashScreen() API
//autoHideSplash: false
};
if (window.addEventListener) {
window.addEventListener('load', function() { WL.Client.init(wlInitOptions); }, false);
} else if (window.attachEvent) {
window.attachEvent('onload', function() { WL.Client.init(wlInitOptions); });
}
function wlCommonInit(){
// Common initialization code goes here
var env = WL.Client.getEnvironment();
if(env === WL.Environment.IPHONE || env === WL.Environment.IPAD){
document.body.classList.add('platform-ios');
} else if(env === WL.Environment.ANDROID){
document.body.classList.add('platform-android');
}
angular.element(document).ready(function() {
angular.bootstrap(document, ['starter']);
});
}
This works perfectly. But there's a twist. By default, an application doesn't connect to your MobileFirst server until you tell it to. This lets you delay or even skip actually connecting unless you need too. I definitely needed too, so I added that code in and moved my bootstrap code to there:
var wlInitOptions = {
// # To disable automatic hiding of the splash screen uncomment this property and use WL.App.hideSplashScreen() API
//autoHideSplash: false
};
if (window.addEventListener) {
window.addEventListener('load', function() { WL.Client.init(wlInitOptions); }, false);
} else if (window.attachEvent) {
window.attachEvent('onload', function() { WL.Client.init(wlInitOptions); });
}
function wlCommonInit(){
// Common initialization code goes here
var env = WL.Client.getEnvironment();
if(env === WL.Environment.IPHONE || env === WL.Environment.IPAD){
document.body.classList.add('platform-ios');
} else if(env === WL.Environment.ANDROID){
document.body.classList.add('platform-android');
}
WL.Client.connect({
onSuccess:function() {
console.log("Connected to MFP");
angular.element(document).ready(function() {
angular.bootstrap(document, ['starter']);
});
},
onFailure:function(f) {
console.log("Failed to connect to MFP, not sure what to do now.", f);
}
});
}
The WL.Client.connect method does exactly what you expect - connect to the MobileFirst server. Seems like a small mod, right? But when I did this, I noticed something bad.
Can you see it? It is a FOUC (Flash of Unstyled Content). There's a simple fix for this though. We can simply use the splash screen to hide the site until things are ready. (And credit for suggesting this approach goes to Carlos again!) One of the initialization options you can provide is to not hide the splash screen. You can then use the JavaScript API to hide it. Here is that version.
var wlInitOptions = {
// # To disable automatic hiding of the splash screen uncomment this property and use WL.App.hideSplashScreen() API
autoHideSplash: false
};
if (window.addEventListener) {
window.addEventListener('load', function() { WL.Client.init(wlInitOptions); }, false);
} else if (window.attachEvent) {
window.attachEvent('onload', function() { WL.Client.init(wlInitOptions); });
}
function wlCommonInit(){
// Common initialization code goes here
var env = WL.Client.getEnvironment();
if(env === WL.Environment.IPHONE || env === WL.Environment.IPAD){
document.body.classList.add('platform-ios');
} else if(env === WL.Environment.ANDROID){
document.body.classList.add('platform-android');
}
WL.Client.connect({
onSuccess:function() {
console.log("Connected to MFP");
angular.element(document).ready(function() {
WL.App.hideSplashScreen();
angular.bootstrap(document, ['starter']);
});
},
onFailure:function(f) {
console.log("Failed to connect to MFP, not sure what to do now.", f);
}
});
}
In the code snippet above, you can see the new option added to wlInitOptions
and then the call to WL.App.hideSplashScreen
to hide the screen. This worked too - but don't forget that the mobile browser simulator does not have a splash screen! At first I thought this workaround wasn't working, but then I tested in the iPhone Simulator and it worked perfectly.
So - thoughts on this approach? Does it make sense? I've recorded a video of this process that you can watch below. I'm also attaching a copy of my common folder here: Download
Archived Comments
Well done! I like this approach much better than your previously similar approach:
http://www.raymondcamden.co...
I honestly do not know why I didn't like the delayed bootstrapping approach earlier. I mean, I know people change their minds, but jeeze. I must have been smoking something then. :)
I'm wondering if I should start taking the same approach for base Apache Cordova and Ionic.
I am. I know some people have said that if you move all your calls (well calls related to device stuff) to services you can just listen for deviceready in there instead. To me that just seems weird.
What boggles my mind though is how no one seems to talk about this issue. When I first started learning Ionic, I immediately ran into it, and it feels like something you would see often in the forums, but that doesn't seem to be the case.
If you look at the examples on ngcordova (http://ngcordova.com/docs/p... they have event listeners in the controller, which again, seems wrong.
Great approach. I was wondering how best to get past the FOUC. Thanks!
You are most welcome.