ABOUT Mobile: Under the hood Part 1/3.A robust application structure for ionic apps

INSIGHTS ABOUT Mobile: Under the hood Part 1/3.A robust application structure for ionic apps

ABOUT YOU TECH

ABOUT YOU TECH

8 min read

Autor: Robert Merten

V1

The first version of the mobile app was basically a fork of our existing site, which was developed with ionic. We added some features exclusive to the mobile app and were able to ship it with a very small team to gather first insights on how a mobile app for our shop would be used (if you want to know more about what we learned head here).

While this approach helped us to ship the app relatively fast and without the use of many resources it came with a cost for the user experience and performance of the app. Performance was janky and some things just weren’t thought through.

V2

When we first discussed a new version of our app that would have a new and dedicated look and feel we soon realized that we would have to scrap the old code and try a new approach.

With React Native still in active development and at the time not available for Android, we decided to once again use ionic as the framework for our app. Other than that, the inner workings of the app would get a massive overhaul.

With more developers working on the new code and a focus on a better user experience, we needed an easy to learn and robust structure.

After studying the angular styleguide we were very keen on the proposed application structure. Utilizing this proposed coding style, we would be able to better address some of the pitfalls that angular throws in your way. To supplement this approach we used the new javascript standard ES6 which gives us, above a lot of smaller features and niceties, modules and classes which made it a lot easier to separate our code in contained components.

As this approach helped us a lot to develop a more stable app with a significantly better codebase we want to share our learnings in a small series.

In this series I will cover a better application structure for ionic / angular apps, how to implement it in a clean way with the help of ES6 and how to generally increase performance in hybrid apps developed with ionic and angular.

Application structure

In the first post of this series I’m going to talk about the most important part of our new approach, the application structure. A lot of times this is overlooked and best practices are ignored thinking that you will yield results faster by just jumping right into it. Especially with angular projects this can lead to various problems that slow you down and make the maintenance of your application a nightmare.

With relatively small changes to, for example, the folder structure, you can force yourself and other developers to think harder about separation of concerns and increase the code quality a lot through a very small amount of boilerplate code.

Folder Structure

Folder by type

In a lot of angular projects you will find the following structure:

app/
controllers/
feed.js
services/
stories.js
factories/
filters/
time-ago.js
directives/
story-share.js
templates/
feed.html
scss/
app.js
...

While this structure is sufficient for most smaller projects it gets unwieldy quickly as your project grows. For developing a new feature you’ll have to move to different folders and at times it’s hard to locate files belonging together without checking the source code.

Another problem you will be having with larger projects is, that the files that are bootstrapping the application will be very cluttered, especially the app.js.

Often you’ll see the main js file doing way too much work:

app.js

angular.module( 'app', [
'ionic',
... // long list of dependencies
] )
.config( function( $stateProvider ) {

$stateProvider.state( 'home', {
url: '/home',
templateUrl: 'home.html',
controller: 'HomeController',
} )
... // Long list of routes

... // Additional config
})
.run( function() {

// run block

});

You can make it a little less verbose if you have different files for config and run blocks. But still there is a lot of searching going on if you just want to make small adjustments and it increases the cognitive load for the developer.

Folder by feature

The folder by feature structure on the other hand decreases the cognitive load for the developer and makes it easy to think about your application structure. Every feature of your application has it’s own directory and always follows the same boilerplate. Additionally it makes it very easy to separate your features into different angular modules that are easy to swap out, deactivate or rewrite without interfering with other parts of the code.

The folder structure could look something like this:

app/
components/
featureA/
feature.js
feature.states.js
feature.service.js
feature.controller.js
feature.html
featureB/
comments.js
...
services/
api.service.js
currency-format.filter.js
services.js
shared/
shared-directive
shared-directive.html
shared-directive.directive.js

app.module.js
app.run.js
app.config.js

The _app.*_ files are for bootstrapping the application. They are separated so that it’s easier to identify the run and config block of your application and to check in app.module

Like mentioned earlier, we don’t want a huge file that wires everything together and does all the configuration of our application. To separate concerns we will have one file creating our main module and adding dependencies that our application needs to run:

app.module.js

angular.module( 'app', [
'app.core',
'app.feature',
'app.anotherFeature'
...
]);

angular.module( 'app.core', [
'ionic',
'app.services', // Services that are used throughout the application
...
]);

app.run.js and app.config.js contain the config and run block of our application:

function config( $ionicConfigProvider ) {

$ionicConfigProvider.scroll.jsScrolling( false );

}

angular.module( 'app' ).config( config );

Developing features

Instead of attaching everything to your main app module you have a contained module that handles just one feature of your application. For now I’ll be leaving out the ES6 part so it’s easier to see the basic concept.

Structure of a feature / component

As you can see the folder contains everything that belongs to this specific feature (other than services and directives that are used by multiple features) even specific styles that need to be applied.

app/
components/
feature/
feature.js
feature.states.js
feature.service.js
feature.controller.js
feature.html
feature.scss

feature.js

Located under app/components/feature/ it acts as an index file for the specific feature. It creates the angular module and adds dependencies that are used.

angular.module( 'app.featureName', [
'dependency'
])

feature.controller.js

Adds the controller to our feature. It’s generally a good idea to keep it skinny and to contain most of the feature logic to a service. This way you can achieve a better Separation of Concerns. The single responsibility of the controller in angularjs should be the communication between the view and services acting as a viewmodel.

Notice that we’re using this to expose data to the view with the controllerAs feature of angular as it makes the code easier to read and removes the temptation to overuse $scope methods like $watch, $apply, $parent which can lead to performance issues and harder to maintain code.

function FeatureController( FeatureService ) {

var vm = this;

initialize();

function initialize() {

FeatureService.fetchData().then( function( data ) {
vm.data = data;
});

}
// ...
}

angular
.module( 'app.featureName' )
.controller( 'FeatureController', FeatureController )

feature.service.js

Adds the features service. Generally the service should handle everything that is related to receiving and sending data.

function FeatureService( $http ) {
// ...

this.fetchData() {
return $http.get( '/api/endpoint' );
}

}

angular
.module( 'app.featureName' )
.service( 'FeatureService', FeatureService )

feature.states.js

Every feature has its own config block that we’re using to define the states. This makes sense as we don’t want to jump to a state’s file and search every time we need to change part of the state for our feature. While it removes the possibility to have a complete “routes” file for your application it shouldn’t pose a huge challenge as it should be easy to identify the feature you access.

function config( $stateProvider ) {

$stateProvider.state( 'feature', {
url: '/feature',
templateUrl: 'feature.html',
controller: 'FeatureController as vm',
} )
}

angular
.module( 'app.featureName' )
.config( config );

Adding a feature to your application

To add a new feature to your application, just add the module to your main module.

app.module.js

angular.module( 'app', [
//...
'app.featureName'
]);

Other folders

services

The services directory contains services that are used throughout your application. This could be for example an APIService that is used by multiple features to communicate with your API or a filter that formats the prices in your application.

There is no distinction between the different service recipes that angular provides ( like factory ), as they are mostly syntactic sugar over the $provider ( more ).

In services.js we create our services module so we can attach every global service to it.

```

// services/services.js
angular.module( 'app.services', []);

// services/api.service.js
function ApiService() {
//...
}

angular.module( 'app.services' ).service( 'ApiService', ApiService );

shared

Features that are used by multiple parts of your application are located in the _shared_ folder. Most of the time these will be directives like, for example, share button.

// shared/shared.js
angular.module( 'app.shared', []);

// services/share-button.directive.js
function ShareButtonDirective() {
//...
}

angular.module( 'app.shared' ).service( 'ShareButtonDirective', ShareButtonDirective );

Conclusion

With some relatively small tweaks to a standard ionic project structure and with following a few best practices like controllerAs, we can achieve a much cleaner and better maintainable code base which forces the developer to think twice about the separation of parts of the application and gives them clear guidelines on how to structure their code.

Next Steps

While using a different application structure and knowing some best practices for angular projects is already helping, we can improve it even further by using the new iteration of javascript ES6 with the help of Babel. I will cover that in my next blog post.

More articles

Reducing points of friction in the sales process
ABOUT YOU TECH

ABOUT YOU TECH

3 min read