Cleaning up Ionic 1 Default Code to Match the Style Guide Feels Good

The Style Guide preps your project for Angular 2 and scalability.

TL;DR

The app with all changes can be found on github.

Author’s Ramblings

When you start a new Ionic app you get a fairly standard Angular layout with 3 JS files — app, controller, and services. Inside each file, commands are chained, turning each file into the equivalent of one giant line of code. If you use this layout you’ll eventually run into problems with massive, unmanageable files. We’re going to break each component out into its own file and clean up how they’re attached to the app’s module.

Using The Style Guide will make your Angular 1 code work a lot more like Angular 2 code. In many cases you’ll even be able to reuse whole services because you’ll be using pure JS. Using pure JS will also make your code easier to test. All this will make the transition to V2 much easier when it becomes less of an adventure in a month or 2.

The Style Guide has grown to be quite a beast but it’s well worth the read. Even if you just use the aspects that I lay out here you’ll be off to a great start. And to be honest, I’m not following everything recommended by the style guide myself, just the low hanging fruit.

Actual Useful Information

We’ll start by assuming you have NPM and Ionic installed. Let’s start by making a new app.

ionic start my_app
cd my_app
ionic serve

We’re going to be breaking and unbreaking the the app a lot during this process so leave the server running and check that everything is OK often.

Single Responsibility and Naming Conventions

We should only have one, single application component, per file. Each of our components will need to be moved to its own file.

Each file name should use the format feature.type.js and should match the component name. So avengers.controller.js would hold the AvengersController.

Let’s open up our 3 files and see what components need to be turned into new files.

app.js
This file contains the module declaration, a run function and a config function used for routing and state maintenance. We’ll create 2 new files and a directory. Then we’ll move app.js into the new directory.

www/js/app/app.js
www/js/app/app.run.js
www/js/app/routing.config.js

Make sure that www/index.html reflects your changes:

<script src="js/app/app.js"></script>
<script src="js/app/app.run.js"></script>
<script src="js/app/routing.config.js"></script>
Remembering to keep index.html up to date is your job going forward.

controllers.js
We shouldn’t have a single folder for controllers either. Folders should be created to logically organize your app, not to locate files based on component type. That means we won’t be creating a new folder for the controllers, just dropping them in the JS folder to be organized later as the app grows.

www/js/dash.controller.js
www/js/chats.controller.js
www/js/chat-detail.controller.js
www/js/account.controller.js

services.js
Create:

www/js/chats.factory.js

Modules

Modules should use a getter / setter system. They should also be decoupled from their components, but we’ll handle that later. Set with:

angular.module('app', ['requirement1', 'requirement2']);

And get with:

angular.module('app');

Move the run function from app.js to app.run.js.

Separate the .run() chained function into its own function:

function appRun($ionicPlatform) {
$ionicPlatform.ready(function() {
// Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
// for form inputs)
if (window.cordova && window.cordova.plugins && window.cordova.plugins.Keyboard) {
cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
cordova.plugins.Keyboard.disableScroll(true);
}
if (window.StatusBar) {
// org.apache.cordova.statusbar required
StatusBar.styleDefault();
}
});
}

Then at the bottom of the file, add your module getter.

angular
.module('starter')
.run(appRun);

Do the same for routing.config.js:

function routingConfig($stateProvider, $urlRouterProvider) {
...
}
angular
.module('starter')
.config(routingConfig);

The only thing left in your app.js should be:

angular.module(‘starter’, [‘ionic’, ‘starter.controllers’, ‘starter.services’]);

Controllers and Controller Scope

With overlapping controllers, it’s easy to mix up variables so we’ll be using the controller as an object rather than $scope. So instead of just {{variable}} we’d have {{controllerName.variable}}.

First, do the same thing with controllers.js that you did with app.js. Separate each chained function into its own file and convert it into a regular function. The only difference is that you have to name your controller. (Don’t forget to update your index.html.)

This is what dash.controller.js should look like:

function dashController() {
}
angular
.module('starter')
.controller('DashController', dashController);

That will break your app because we changed the name from DashCtrl to DashController. Open routing.config.js and find the ‘tab.dash’ state declaration. Change the controller attribute like so:

controller: ‘DashController’,
controllerAs: ‘dash'

Now do the same thing for the chats controller remembering to update routing.config.js:

function chatsController(Chats) {
var controller = this;
  //changed variable to avoid chats.chats
controller.chatList = Chats.all();
controller.remove = function(chat) {
Chats.remove(chat);
};
}
angular
.module('starter')
.controller('ChatsController', chatsController);
IMPORTANT! We use ‘var controller = this;’ because ‘this’ will be overridden from inside an anonymous inner function. Assigning it to a variable guarantees it will be available from anywhere inside the controller.

If you navigate to the chats page the data won’t be displayed. This is because we eliminated scope in favor of a named controller. Going forward, anywhere we would normally have a scoped variable, we’ll need to use the controller name as declared in controllerAs. Open tab-chats.html and change the ion-item to use the controller name.

<ion-item class="item-remove-animate item-avatar item-icon-right" ng-repeat="chat in chats.chatList" type="item-text-wrap" href="#/tab/chats/{{chat.id}}">

Hopefully you’ve got the hang of it now. Go ahead and fix up the rest of your controllers.

Services

Nothing special about the service. If you’ve followed along this far you should be able to figure it out yourself. But here’s how chats.factory.js looks anyway:

function chatsFactory() {
...
}
angular
.module('starter')
.factory('ChatsFactory', chatsFactory);

The one gotcha is that we’ve renamed Chats to ChatsFactory, so you’ll need to update the chats.controller.js dependency.

Decoupling and Cleanup

We decoupled the module when we made each component reference the module rather than the other way around. This will allow us to move Components to other projects and only need to change the module name. Let’s reflect that change in app.js by removing the dependencies on our own modules. Your app.js should now look like this:

angular.module(‘starter’, [‘ionic’]);

Feels Good!

You can now safely remove services.js and controllers.js from the directory and your index file.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.