Some initial thoughts on building desktop apps with Ionic and Electron

This post is more than 2 years old.

Earlier this week I was working with a desktop app (which I can't talk about... yet) that had an Ionic-look to it. On a whim, I opened up the package contents and discovered it was an Electron app. If you've never heard of it, Electron is an open source project that lets you build desktop apps (for Mac, Windows, and Linux) using web technologies. The last time I did anything in this space was with Adobe AIR, which was years ago. I've played with Electron a tiny bit, but I had not tried to use Ionic with it so I thought I'd give it a shot. Before digging in, I want to bring up two very important points.

First, I've spent maybe three hours looking into this subject, and obviously have not built a production application yet. This post focuses on some of the things I ran into while testing things out, but it is safe to assume more issues probably exist. As time goes on I'll probably blog other things to consider and I hope my readers will share their own discoveries.

Second, while Electron makes it easy to build a desktop app, I cannot stress enough that you need to remember that app development is not the same as web development. When I talk about Cordova and how it makes it easy to use the web to build mobile apps, I try very hard to remind people that building a mobile app is nothing like building a web page. Repeat that after me: "Building a mobile app is nothing like building a web page."

Ok, so with that out of the way, let's talk shop.

The Basics

The first thing I did was to see what would happen if I took a generic Ionic app and just plain ran it under Electron. To do that, I created a new Ionic app and used the -no-cordova flag. If you aren't aware, you can tell the Ionic CLI to create a new project and skip including all the Cordova bits. You still get a bunch of extra stuff like the bower file and gulp script, so I simply copied out the www folder.

shot1

Then, following the Electron quick start, I added a package.json file and main.js. For my main.js, I copied their code exactly, but removed the bits to open dev tools. (More on that in a bit.)

var app = require('app');  // Module to control application life.
var BrowserWindow = require('browser-window');  // Module to create native browser window.

// Report crashes to our server.
require('crash-reporter').start();

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is GCed.
var mainWindow = null;

// Quit when all windows are closed.
app.on('window-all-closed', function() {
  // On OS X it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform != 'darwin') {
    app.quit();
  }
});

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
app.on('ready', function() {
  // Create the browser window.
  mainWindow = new BrowserWindow({width: 800, height: 600});

  // and load the index.html of the app.
  mainWindow.loadUrl('file://' + __dirname + '/index.html');

  // Emitted when the window is closed.
  mainWindow.on('closed', function() {
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    mainWindow = null;
  });
});

Here's how my directory structure looked. And again, this is the result of taking the www contents from Ionic's default template (tabs) and adding in the files Electron requires.

shot2

At this point I went and just tried running it with the Electron CLI. Surprisingly, it just plain worked. In fact, even the $ionicPlatform.ready fired. As far as I can tell, it noticed that cordova.js didn't load and assumed I was running the app on a desktop browser.

So as I said, it just worked, which was cool, but then I began digging in and figuring out bits and pieces of code I needed to rip out and modify.

The first thing I did was remove cordova.js since - obviously - this was no longer a Cordova application. Here's the index.html for my app with that modification. I also added a title value. This is going to come into play in a bit.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
    <title>My App</title>

    <link href="lib/ionic/css/ionic.css" rel="stylesheet">
    <link href="css/style.css" rel="stylesheet">

    <script src="lib/ionic/js/ionic.bundle.js"></script>

    <script src="js/app.js"></script>
    <script src="js/controllers.js"></script>
    <script src="js/services.js"></script>
  </head>
  
  <body ng-app="starter">
    <ion-nav-bar class="bar-stable">
      <ion-nav-back-button>
      </ion-nav-back-button>
    </ion-nav-bar>

    <ion-nav-view></ion-nav-view>
  </body>
</html>

Next, inside app.js I completely emptied out the run block. We don't need to test for the keyboard plugins, and ionicPlatform.ready does not make sense in this content. Electron lets you do 'Desktop stuff', but doesn't make you wait for an event in your code.

I then noticed something odd. Here's the app running:

shot3

And here it is with another tab selected:

shot4

Did you notice the title? The title of the app changes as I change my view. Now, that could be nice, but to me, it doesn't make sense. The app should have a core title, like "My App", the one I used in html earlier, and the header could continue to be more context-driven. Unfortunately, the code that updates the title is built into Ionic itself and can't be disabled. I raised the issue in an Ionic chat room, and Leandro Zubrezki spent some time helping me out. The following solution is 100% his idea.

In order to prevent the title from updating, you can listen for the $ionicView.afterEnter event and stop it. So for example:

$scope.$on('$ionicView.afterEnter', function(ev, data) { 
      ev.stopPropagation();
});

This can be used in your controllers, but quickly gets repetitive. While talking with Leandro, he suggested using the root level state to define a controller and run it from there. Here is that top level state from the Tabs demo:

.state('tab', {
    url: '/tab',
    abstract: true,
    templateUrl: 'templates/tabs.html',
    controller:'TestCtrl'
})

The change here is the addition of TestCtrl (not the best name). I then added this to the controllers.js file:

.controller('TestCtrl', function($scope) {
  
  console.log('test ctrl');
  $scope.$on('$ionicView.afterEnter', function(ev, data) { 
      ev.stopPropagation();
  });

})

And that was it! It worked fine at that point.

One more tip. Don't forget you can enable dev tools for your app from the menu.

shot5

By default this will appear in the app. Don't forget you can 'pop it' out using the icon highlighted below.

shot7

That icon actually has three states - right, bottom, and popped out. So if it looks like this:

shot8

Clicking will just send it to the bottom. Click and hold to open a menu and select the icon I showed earlier. Using this, I was able to have my dev tools nicely separate from the app which made debugging easier. I also made use of the Reload option too so I didn't have to restart the app from the command line.

shot9

Speaking of the command line, it is worth noting that console.log messages will show up there too.

shot10

Whew - that was a lot. So if you want to see the code behind the modified Tabs demo, you can find it here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/ionic_electron_tabs

A Demo

With a basic understanding of how to make this work in place, I decided to try a desktop version of my RSS Reader. Here it is up and running:

shot11 shot12

Not the most thrilling app, but let me discuss how I changed it for Electron.

I began by making the modifications I mentioned above - removing cordova.js and ionicPlatform.ready. I then needed to do the "title fix" as I mentioned before. This was a bit weird for me as my app did not already have an abstract state on top. I ran into issues with this and got some great help from Mike Hartington. Here is my new app.js with the change to state.

(function() {
/* global angular,window,cordova,console */

	angular.module('starter', ['ionic','rssappControllers','rssappServices'])

	.constant("settings", {
		title:"Raymond Camden's Blog",
		rss:"http://feeds.feedburner.com/raymondcamdensblog"
	})

	.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {

		$stateProvider
		    .state('root', {
			    url: '/root',
			    abstract: true,
			    controller:'RootCtrl',
				template:'<ion-nav-view />'
			  })
			.state('root.Home', {
				url: '/home',
				controller: 'HomeCtrl',
				templateUrl: 'partials/home.html'
			})
			.state('root.Entries', {
				url: '/entries',
				controller: 'EntriesCtrl',
				templateUrl: 'partials/entries.html',
			})
			.state('root.Entry', {
				url: '/entry/:index',
				controller: 'EntryCtrl',
				templateUrl: 'partials/entry.html',
			})
			.state('root.Offline', {
				url: '/offline',
				templateUrl: 'partials/offline.html'
			});

		$urlRouterProvider.otherwise("/root/home");

	}])

	.run(['$ionicPlatform','$rootScope','$state', function($ionicPlatform, $rootScope, $state) {

		$rootScope.goHome = function() {
			$state.go("root.Entries");
		};

	}]);

}());

Essentially, my root state has a blank template because that's required in Angular. Well, not blank, but a template with just a nav-view. All I really wanted was my root controller. I won't show the code as it is the same event hack I described above.

Next, I had to make another modification. My RSS Reader app used the Network Information Cordova plugin (and ngCordova) to check to see if you were online. This is somewhat simpler in Electron - you just use navigator.onLine:

if(navigator.onLine) {
	rssService.getEntries(settings.rss).then(function(entries) {
		$ionicLoading.hide();
		$rootScope.entries = entries;
		$state.go("root.Entries");
	});

} else {
	console.log("offline, push to error");
	$ionicLoading.hide();
	$state.go("root.Offline");
}

So far so good. My final change was to the "Read Entry on Site" button. In the Cordova demo, this uses the InAppBrowser. But in Electron, we can actually use a shell command to open a url. The cool thing is that this will then use the user's desired browser to open a new tab:

$scope.readEntry = function(e) {
	var shell = require('shell');
	shell.openExternal(e.link);
};

Pretty cool, right? If you want the full source code, you may find it here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/rssreader_electron

Conclusion

All in all, I think this is an interesting idea. Ionic provides a great UI, and it looks just as good on desktop as it does mobile, and obviously the power of Angular to help architect your application is just as useful here. Certainly there are issues to keep in mind when building a desktop app that won't apply to mobile, but they are things you can address. I'd love to hear what people think, and if you build something (or have built something), please share it in the comments below!

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by giorgiofellipe posted on 7/23/2015 at 6:35 PM

Great job Raymond! I've never played with Electron, although is nice to know that it fits well with Ionic.

Comment 2 by Max posted on 7/23/2015 at 8:35 PM

Does Electron fill the the same niche as node-webkit?

Comment 3 by ian posted on 7/23/2015 at 8:42 PM

well done!

Comment 4 (In reply to #2) by Raymond Camden posted on 7/23/2015 at 9:17 PM

Yes. And see this doc for differences: https://github.com/atom/ele...

Comment 5 by Dinesh Raja posted on 7/24/2015 at 6:39 AM

Can we create database (MySQL) connectivity in an Electron application?

Comment 6 (In reply to #5) by Raymond Camden posted on 7/24/2015 at 1:04 PM

In theory - stress - in theory - you could try using the Node.js packages available for MySQL from your main.js file. Your web app can 'speak' to it, and vice versa, so it should be possible.

Comment 7 (In reply to #6) by Dinesh Raja posted on 7/29/2015 at 10:05 AM

Ok Thank you :)

Comment 8 by Naveen Karippai posted on 8/23/2015 at 9:47 AM

Hi, Thanks for the great post! Inspired from it I started a simple project - https://github.com/NaveenKa...
I noticed that the size of packaged app is too big (100mb). Do you know how this could be reduced? I assume it's because of node modules included.

Comment 9 (In reply to #8) by Raymond Camden posted on 8/23/2015 at 7:04 PM

No, I don't. To be honest, it doesn't seem like a big deal to me since we're talking desktops here. Most likely you won't have folks on dialup. I do get your concern though. Best I can suggest is asking them (and sharing with us what you find :).

Comment 10 (In reply to #9) by Naveen Karippai posted on 8/23/2015 at 7:33 PM

Sure. Thank you :)

Comment 11 by Eric Fernance posted on 10/7/2015 at 5:57 AM

Thanks Raymond. Just the answer I was looking for. I'm looking at Ionic for a current project and thought, geez. it'd be nice to just pick this up and port to a desktop app! Thanks for sharing!

Comment 12 by Young Park posted on 3/29/2016 at 7:34 PM

Just wow! Super amazed.

Comment 13 (In reply to #12) by Raymond Camden posted on 3/29/2016 at 8:58 PM

Glad you liked it.

Comment 14 by Brock Ellis posted on 8/25/2016 at 8:28 PM

Any chance we'll see a revisit to this article with Ionic 2 someday? :-)

Comment 15 (In reply to #14) by Raymond Camden posted on 8/25/2016 at 8:55 PM

Honestly had not thought of it - but maybe so. Certainly not till 2 is final. I've added it to my list of things to write about.

Comment 16 (In reply to #15) by Krishna Karki posted on 8/26/2016 at 7:55 AM

Which Ionic version do you suggest for new project. Ionic 1 or 2?

Comment 17 (In reply to #16) by Raymond Camden posted on 8/26/2016 at 1:03 PM

I feel weird answering this because I do *very* little real work. As a dev advocate, most of my work is blog posts, demos, presentations, and *not* client work. Therefore, my needs/requirements/etc do not necessarily match what most folks do.

In general, I'd feel iffy doing Ionic 2 for a production client since v2 isn't 100% done yet. It works darn well and I *love* coding with it. I'm trying to do all my blog posts in v2, but I'm not sure how safe I'd feel doing production work in something that isn't released yet. I have supreme confidence in the Ionic folks and their work, but if they themselves haven't marked it as final, then it isn't final. :)

The flip side is - if you are looking at a multi-month project timeline, I'd probably do v2 as my gut says v2 is *very* close to being done.

Comment 18 (In reply to #17) by Krishna Karki posted on 8/26/2016 at 1:08 PM

Thank you Raymond Camden for detail answer. This is the biggest dilemma currently almost ionic developer are having like me. I hope Ionic 2 will be stable soon.

Comment 19 (In reply to #18) by Mike Hartington posted on 8/26/2016 at 1:30 PM

Just to add a bit to this, I don't see any reason why you should not use Ionic 2. We have one big batch of changes coming in the next release that we hope to be our last, then we hope to be "done". Kind of echoing what Ray has already said, but if you have a project that needs to be done 3-4months from now, use Ionic 2. It's everything you love about ionic 1, but reworked to be faster, smoother, and more performant.

That being said, if you need to ship something by the end of next week, take the time and do and ionic 1 app.

Comment 20 (In reply to #19) by Krishna Karki posted on 8/26/2016 at 1:46 PM

Thank you Mike for answering the topic. You guys are doing great thing at Ionic. My next project is bit long term project so i will go with Ionic 2.

Comment 21 by Yuri Di Carlo posted on 9/14/2016 at 3:29 PM

Hi Raymond,
your article is very detailed and explaining your points of view for development with Ionic and Electron. I got a question for your phrase you mentioned in the introduction, "Building a mobile app is nothing like building a web page."

I'm so sorry if I can't comprehend on what you intend to say about it, but I know that there are so many framework here today that allow to code responsive web apps adaptable for any device with the functionality (like Cordova) to build for browser, Android, iOS and so on... So, why don't make a "unique" coding adaptable for differente devices?

I repeat, maybe I don't knw what everyone of you know, so for you that phrase has on obvious meaning... But for me not. :-|

Thanks for your patience on reading this.

Bye,
Yuri

Comment 22 (In reply to #21) by Raymond Camden posted on 9/14/2016 at 3:42 PM

My point was more about how a content-driven site, like a blog, is very different from a web app, like your bank's web site for example. This blog, for ex, doesn't really have a functional UI outside of simple navigation, and that's fine because your just here reading, or writing comments, while your bank has a multiple buttons/UI elements etc to enable you to work with your accounts.

That's rough, but hopefully it gives you an idea of what I'm talking about.

Comment 23 by Sabarinathan posted on 11/1/2016 at 3:19 AM

Great Tutorial.It's help lot

Comment 24 by Aneudy Amparo posted on 3/3/2017 at 2:28 AM

Hey, I started an Ionic-Electron app myself and i searched to see if i'm the only one curious here! hahaha Nice Tut BTW!

Comment 25 (In reply to #24) by Raymond Camden posted on 3/4/2017 at 3:36 AM

Thanks!

Comment 26 by Zubair Ven posted on 3/10/2017 at 5:55 PM

Hi Raymond Camden thank you for the tutorial which I just read. One question I was having in mind is that how to hide the code from the folder? I mean is there a way to create just an exe file so that I can share it with my users as well rather than giving them the whole folder? Thanks in advance.

Comment 27 (In reply to #26) by Raymond Camden posted on 3/10/2017 at 7:43 PM

That I don't know. I'd check the Electron docs. Your only option may be to obfuscate the code.

Comment 28 (In reply to #27) by Zubair Ven posted on 3/11/2017 at 7:29 AM

Hi thanks for your prompt response. Please let me know if you find any solution to this. Thanks

Comment 29 by Charles Muzonzini posted on 4/30/2017 at 4:31 PM

Awesome blog post! Thank you very much!