Introduction

Ionic has been around for two years now. It is a great set of tools for developing hybrid applications based on AngularJS. Ionic is extremely popular at the moment, with more than one million applications built and a growing community of thousands of developers.

Since Ionic’s first release, time has passed, and web technologies and best practices have evolved in many ways. Therefore, it is hard to determine which path to follow when starting a new project. In these conditions, developers can make mistakes potentially impacting the quality of their applications or the productivity of their team.

By reading the following Common mistakes, you will have the keys to avoiding fundamental problems and to create performant and scalable applications with Ionic.

Ionic is extremely popular at the moment, with more than one million applications built.

Common Mistake #1: Forgetting to Enable Native Scrolling

Native Scrolling allows Ionic to listen to scrolling events on supported webviews. It makes Pull to Refresh, List Reordering and Infinite Scroll possible without JavaScript scrolling, which was created in a time when browsers lacked proper scroll events.

Native Scrolling is enabled by default on Android since Ionic 1.2 (December 2015). It is a huge performance and user experience improvement, as it ensures a smooth scroll due to asynchronous events.

Unfortunately, due to the lack of proper events on iOS the native scrolling is not enabled for that platform yet.

If you are using a version prior to 1.2, you can enable Native Scrolling for Android using $ionicConfigProvider :

// Enable Native Scrolling on Android $ionicConfigProvider.platform.android.scrolling.jsScrolling(false);

You also can enable or disable Native Scrolling on any page using overflow-scroll directive on any ion-content :

<!-- Disable Native Scrolling on this page only --> <ion-content overflow-scroll=”false”>

Please note that unfortunately collection-repeat, which allows your application to show huge lists of items, cannot be covered by native scrolling.

Common Mistake #2: Not Using the Ionic CLI to Install Platforms and Plugins

Ionic CLI adds features to the Cordova CLI. Platforms and plugins persistence is a great feature that Ionic CLI adds.

The problem with Cordova CLI is that the platforms and plugins you install are installed on your machine only. When working on a team, to avoid bugs you want to share the same environment, platforms, and plugins. With Cordova CLI, it’s harder to keep the project in sync between developers machines. Yes, you could commit the platforms and plugins folders, but it is not recommended.

When using Ionic CLI to install platforms ionic platform add ios and plugins ionic plugin add camera , the package.json file is edited appropriately.

Platforms and plugins are stored in cordovaPlatforms and cordovaPlugins properties:

It is now easy for other developers to get in sync when pulling a new code, simply by running ionic state restore when necessary (addition, deletion or version update).

Common Mistake #3: Thinking Performance Comes out of the Box

Ionic is based on AngularJS, and performance on the device is often questioned. I would like to reassure you on this point: with a little AngularJS background, you can create world-class applications with Ionic.

The perfect example is Sworkit app that is built with Ionic, has a 9M+ userbase, 7M+ downloads and an average of 4.5 stars on Google Play.

If you want to get the best out of AngularJS, here are a few things you should learn before starting your project.

$watch

Watchers are used to listening to scope changes in AngularJS. There are basically four types of $watch : $watch (normal) , $watch (deep) , $watchCollection and $watchGroup .

Every single one of them is different, and choosing the right one can make a huge difference in terms of performance.

$watch (normal)

Using the normal $watch will only check existing Object properties or Array items. Shallow changes, like adding an Object property or push a new item into an Array, will not be taken care of.

$scope.$watch('watchExpression', function(newVal, oldVal){ if(newVal){ // watchExpression has changed. } });

$watch (deep)

The deep $watch takes care of shallow changes and deep changes, like Nested Object properties. With this $watch you are sure not to miss any modification. However, using deep $watch has performance implications. I would advise to use it with caution.

$scope.$watch('watchExpression', function(newVal, oldVal){ if(newVal){ // watchExpression has changed. } }, true);

$watchCollection

$watchCollection can be considered in between the normal $watch and the deep $watch . It also works comparing object references, but with the advantage to also shallow watches the properties of your object by adding an Object property or push a new item into an Array.

$scope.$watchCollection('watchExpression', function(newVal, oldVal){ if(newVal){ // watchExpression has changed. } });

$watchGroup

Introduced in AngularJS 1.3, $watchGroup allows watching several expressions at once.

While $watchGroup might not improve your application performance compared to the normal $watch , it has the advantage to be more synthetic when watching several scope expressions.

$scope.$watchGroup([ 'watchExpression', 'watchExpression2', 'watchExpression3' ], function(newVals, oldVals) { if (newVals[0]) { // watchExpression has changed. } if (newVals[1]) { // watchExpression2 has changed. } if (newVals[2]) { // watchExpression3 has changed. } });

Track By

The track by is used to avoid useless DOM manipulation when using ng-repeat . Indeed, if the digest cycle finds that at least one element of your collection has changed, ng-repeat will re-render all the elements. DOM manipulation always has effects on the application performance, so the less you have the better.

To avoid re-rendering the complete collection and only update the elements that need to be updated, use track by with a unique identifier.

<!-- if items have a unique id --> <div ng-repeat="item in items track by item.id"></div> <!-- if not, you can use the $index that ng-repeat adds to every of its items --> <div ng-repeat="user in users track by $index"></div>

Just avoid using track by on collection-repeat .

One-Time Binding

One-time binding, or :: was introduced in Angular 1.3, and it has a real impact on your application performance.

Basically, using one-time binding :: on an expression will remove it from the $watchers list when populated. It means that the expression will not be able to update even if the data changes.

<p>{{::user.firstName}}</p>

Our advice is to go through all your application’s views and think about what could or could not be updated, and use one-time binding :: accordingly. It will be a huge relief for the digest cycle.

Please note that unfortunately one-time binding cannot be used in a collection-repeat , because the list of items displayed on screen changes on the scroll.

If you wish to know more about AngularJS and Ionic performance tips and tricks, I recommend reading the Ultimate AngularJS and Ionic performance cheat sheet .

Common Mistake #4: Having Confusions with the View Cache Logic

Single page applications do not cache pages by default. You have probably experienced it using AngularJS applications, where the scroll or the user inputs are not saved when you navigate back and forth between the pages.

With Ionic, ten pages are cached by default, and this can be changed globally or per platform.

// Globally $ionicConfigProvider.views.maxCache(5); // Per platforms $ionicConfigProvider.platform.android.views.maxCache(5); $ionicConfigProvider.platform.ios.views.maxCache(5);

This is a great feature, but sometimes it’s hard for beginners to understand how to deal with cached pages.

The problem is that when the user goes back to a cached page, the controller is not re-instantiated again, which is different from AngularJS applications, and everything is like you never left that page.

In these conditions, how should you update the data on the page?

Introducing Controller Life Cycle Events

Compared to AngularJS, Ionic offers many life cycle events:

$scope.$on('$ionicView.loaded', function(){}); $scope.$on('$ionicView.unloaded', function(){}); $scope.$on('$ionicView.enter', function(){}); $scope.$on('$ionicView.leave', function(){}); $scope.$on('$ionicView.beforeEnter', function(){}); $scope.$on('$ionicView.beforeLeave', function(){}); $scope.$on('$ionicView.afterEnter', function(){}); $scope.$on('$ionicView.afterLeave', function(){});

These events are necessary if you want to have control over the view cache.

$ionicView.loaded event, for instance, is triggered the first time a view is loaded. This event will not be triggered any longer while this view is cached, even if the user comes back to it. This is generally the event you would use to initiate variables the same way you do with $viewContentLoaded event in AngularJS.

If you want to fetch data every time you enter a view, cached or not, you can use the $ionicView.enter event.

By using the right event at the right time, you can improve the usability of the application.

Regarding performance, using the cache view only impacts the size of the DOM. When a page is cached, all its watchers are disconnected and the page is therefore just some more DOM elements lying on your page waiting to be used again.

The size of the DOM matters to have a great user experience, but caching up to ten pages seems to work fine (of course, depending on what you load in your pages).

Common Mistake #5: Not Knowing About Crosswalk for Android

Every Android version runs a different WebView (a browser that runs your application). The performance is different across devices, and it can be really bad on the old Android devices. To get the same experience with fluidity and responsiveness on every Android device, you can install Crosswalk. It basically embeds the latest Chromium browser into your application, and is adding around 20Mb per APK, both ARM and X86.

Crosswalk can be installed simply using Ionic CLI or Cordova CLI:

ionic plugin add cordova-plugin-crosswalk-webview

Common Mistake #6: Trying to Run Cordova Plugins Inside the Browser

The majority of developers using Ionic will want their app to run on iOS and Android. After adding the platforms ionic platform add ios android and some plugins ionic plugin add cordova-plugin-device-orientation cordova-plugin-contacts , a rooky mistake is to think you can test them in the browser. Well, you could, but only after you install the proper browser platform. Keep in mind, it does not work with all plugins.

Cordova’s plugins are meant to interact with the native device API through JavaScript. The contact plugin or the device orientation plugin will therefore only work on a device.

However, you can easily test your code on a device and remotely debug it through your computer.

Remote Debug on Android

Plug in your device and make sure it is correctly detected by your computer by running adb devices (Android SDK is required).

Build your app and install it on your device by running ionic run android . Once your app is launched on the device, open the console via Chrome dev tools (on your computer) chrome://inspect/#devices , and inspect your device.

Remote Debug on iOS

Plug in your device and make sure it is correctly detected by your computer. Build your app and install it on your device by running ionic run ios --device .

Once your app is launched on device, open Safari dev tools (on your computer) by clicking on Develop > Your iPhone > Your app :

Run Cordova Plugins Inside the Browser

Running Cordova plugins inside the browser is an advanced feature that you should know about. Since Ionic 1.2, the browser is officially supported, so it opens the era of cross-platform applications way beyond iOS and Android platforms.

With Cordova Browser platform, Electron and only Web technologies (JavaScript, HTML, and CSS) we can now build Ionic applications for the browser and the desktop (Windows, Linux, and OSX).

A starter kit is available on Github.

Cordova Browser Platform

With the Browser platform, you can create Cordova applications for the browser. It means that you can use Cordova’s plugins on the browser too.

It can be installed the same way you install iOS or Android platforms:

cordova platform add browser

Your application needs to be compiled before usage exactly as with iOS or Android:

cordova run browser

This command will compile your app and open your default browser.

Cross Platform Plugins

A lot of plugins such as Network, Camera and Facebook support iOS, Android, and the Browser platform at the same time - all with the same API.

To illustrate that there is a way to know if your device is online or offline on every platform (iOS, Android, Browser and Desktop) using ngCordova API:

// listen for Online event $rootScope.$on('$cordovaNetwork:online', (event, connectionType) => { this.isOnline = true; }); // listen for Offline event $rootScope.$on('$cordovaNetwork:offline', (event, connectionType) => { this.isOnline = false; });

With this in mind, you can now imagine creating products that can run anywhere with one single code base.

Common Mistake #7: Following the Starter Kit Architecture for Large Scale Applications

When using the ionic start myapp command, a starter project is created with the following folder structure:

www/ js/ app.js controllers/ aaa.js bbb.js ccc.js services/ xxx.js yyy.js zzz.js templates/ aaa.html bbb.html ccc.html

This is called a Folder-by-Type structure, where JavaScript, CSS, and HTML files are grouped by types. As it might seem easy for beginners, this kind of architecture gets out of hand pretty quickly. It simply does not scale.

Here are some reasons not to use the Folder-by-Type structure:

The number of files in your folders can become vast

Finding all the files you need to modify for a specific feature can be tricky

Working on a feature will lead to many open folders

Doesn’t scale well, the more the app grows the more difficult working on it is

I rather recommend using a Folders-by-Feature structure, where JavaScript, CSS, and HTML files are grouped by feature or AngularJS module:

myNewFeature/ index.js (AngularJS module) config.js service.js controller.js index.html style.scss

Reasons to use Folders-by-Feature structure:

The number of files in your folders is limited to few

Finding all the files you need to modify for a specific feature is easy - they are in the same folder

You can work independently on a feature

Knowing what the module represents is easy - the folder name is sufficient

Easy to create a new feature, simply copy/paste an existing one

Scales well, you can add as many new features as you want without making it difficult for your team to work on

Please note that this architecture is close to the Folders-by-Component structure that is now the default in Angular2/Ionic2 applications.

Common Mistake #8: Binding Events to onscroll , and Forgetting About requestAnimationFrame

This single pitfall is usually beginner’s mistake, but it may have probably the worst impact on performance. Consider this:

<ion-content on-scroll="getScrollPosition()"> // … </ion-content>

$scope.getScrollPosition = function () { // heavy processing, like manipulating DOM // or anything that triggers a $digest() // will be called every ~80ms, // and will impact UX }

Even though Ionic provides throttling for these actions, it can still be very slow. Basically, anything that triggers a digest loop should be deferred and not triggered together with heavy painting, which also is the effect of scrolling.

Many of the goals that developers have been trying to achieve by binding to scroll events, and especially animations, can also be achieved using a different method. Behold requestAnimationFrame .

var myElement = document.getElementById('content'); var elemOffsetFromParent = myElement.offsetTop; function onCapturedFrame() { if (window.scrollY >= elemOffsetFromParent) { customTweenFunction(myElement, options); } window.requestAnimationFrame(onCapturedFrame); } onCapturedFrame();

The code above is a very simple example, checking if the user scrolled past the top of the element. Remember to add vendor-specific alternatives for cross-browser compatibility if you intend to use the example. It will basically run at an optimal speed, depending on the browser, at 60 FPS or at the screen’s refresh rate. But it is optimized, and high-performance animation frameworks utilize that simple method.

You may also want to look into element.getBoundingClientRect() , which provides information on an HTML node’s size and position.

Common Mistake #9: Prototyping Ionic Applications Manually

Ionic has a specific design, almost a visual language. Especially with prototypes and early-stage products, a lot of time and expenses can be saved by utilizing the available components and styles. They are actually rather minimal and have a good aesthetic.

Presenting wireframes and mockups with basic functionality has become an industry standard. To see a picture and to see an actual app with dynamic components on a mobile device are two very different cups of tea. Many designers, and also UX developers, use tools like Axure or Balsamiq, which allow quickly making wireframes with minimal functionality.

Now, the creators of Ionic released a similar tool made exclusively for Ionic developers. It is called Ionic Creator. It has a drag and drop web interface, and supports close to everything core Ionic provides. What’s great about it is that it allows to export the prototype into several formats, with standard working Ionic code, and even build the application and share it. The tool is proprietary, but many of the options are free to use.

Conclusion

Ionic revolutionized the hybrid application industry in a way nobody could have imagined. However, over time the best practices and tooling lacked evolution. As a consequence, the number of potential mistakes which developers can make increased.

Expert Ionic developers have a clear way to deliver World-Class applications to multiple platforms simultaneously. The way is to leverage the available tools, keep performance as a top priority, and follow the best practices.

This post would not have been possible without the creativity of the amazing Ionic community, Michał Mikołajczyk, Mike Hartington (Ionic Core team) and Katie Ginder-Vogel (Marketing & Communications Manager, Ionic). Thank you all very much.