Modular AngularJS and Ionic architecture: a first step towards AngularJS 2

This post describes the architecture that you can use RIGHT NOW on your AngularJS 1.x or Ionic applications and that will be compatible with AngularJS 2. When the time comes and according to this article: Angular 1 and 2 running together, you will be able to migrate easily your application, module by module.

Forget what you know

A large majority of the Ionic, AngularJS 1.x projects are using Bower, Gulp and a folder-by-type architecture. If you want to build large scale applications with several developers, you will definitely have problems if you follow that kind of architecture.

AngularJS is often used to create prototypes and MVP really quickly so why bother? Well, we all know what happen to prototypes and MVPs that somehow work… they go directly into production. If you have reached that point, it is not too late to restructure your code and think about it before your application turns into FrankenstApp!


Bower

Here are few reasons not to use Bower:

  1. It does not support CommonJS out of the box.
  2. Using npm and Bower at the same time is a non sense (they both fulfill the same need).
  3. npm has become the most popular package manager for JavaScript.
  4. Requires closures to avoid scope leaking.

If you want to know more reasons I recommend you to read this article: Why my team uses npm instead of bower

What is CommonJS?

The CommonJS group defined a module format to solve JavaScript scope issues by making sure each module is executed in its own namespace.

CommonJS handle modules outside the browser and works great with ECMAScript 5 or 6.

// Import with ECMAScript 5
var angular = require('angular');

// Export with ECMAScript 5
module.exports = function(){}

// Import with ECMAScript 6
import angular from 'angular';

// Export with ECMAScript 6
export default function(){}

Gulp

Few reasons not to use Gulp:

  1. Compiling, concatenating and minifying your application require to install a lot of dependencies (gulp-concat, gulp-minify-css, express, livereload etc.).
  2. Basic needs (web server, livereload, concat etc) require to create many scripts that YOU will need to maintain yourself.

In 2015 it should not be that complicated to fulfill your application’s basic needs. The solution is to use Webpack.


Folder-by-type structure

The folder-by-type structure is basically grouping JavaScript files by types (controllers, configs, views, directives etc.) as followed:

app/
    app.js
    controllers/
        aaa.js
        bbb.js
        ccc.js
    directives/
        ddd.directive.js
        eee.directive.js
        fff.directive.js
    services/
        xxx.js
        yyy.js
        zzz.js
    views/
        aaa.html
        bbb.html
        ccc.html
        ddd.directive.html
        eee.directive.html
        fff.directive.html

This folder architecture is a terrible idea for large scale applications. I have experienced it before and it is not a good memory, especially if you are several developers to work at the same time. Here are some reasons not to use it:

  1. The number of files in your folders can become really large.
  2. Finding all the files you need to modify for a specific feature can be tricky.
  3. Working on a feature will lead to open many folders.

If you do not trust me, at least trust John Papa’s styleguide 🙂

A better solution is to Create components/modules with a Folders-by-Feature structure.


Use Webpack

Webpack is a module bundler, it takes modules with dependencies and generates static assets.

Webpack and Webpack-dev-server easily replace Gulp for basic application needs (web server, livereload, concatenation, minification, compliling JS or Sass etc.). Instead of having to maintain several Gulp scripts, what you only need is a configuration file webpack.config.js… that’s ALL!

For instance having an autoprefixed CSS injected into your application from several Sass files is as simple as that with Webpack:

{
    test: /.scss$/,
    loader: "style!css!autoprefixer!sass"
}

No need to show you the equivalent using Gulp or Grunt, I think you got my point! If you want to go further with Webpack you can read this article I created some time ago: Introduction to Webpack with practical examples


Create components/modules

The solution to large scale applications is to create loosely coupled modules. I have been doing AngularJS applications for a long time now and after a lot of experimenting I have settled to the following architecture:

  • Every file that can be edited, live in the src/ or /lib folder.
  • Every AngularJS module needs a proper folder.
  • Every module file *.module.js must define a unique namespace (and must be the only place where this namespace appears).
  • Every module file *.module.js must declare all its dependencies (even if dependencies are already injected in the app).
  • Every module file *.module.js must declare all its configs, controllers, services, filters etc.
  • Every config, controller, service, filter etc. must export a function or a Class.
  • If a module needs some specific style, the .scss file must live within the module as well.

All of this is very powerful, it assures you to have modules that can be shared by several applications without getting any error (except for missing dependencies).

Here an example of a home module structure:

lib/
    index.js
    home/
        home.module.js
        home.config.js
        home.service.js
        home.controller.js
        home.html
        home.scss

Now here is the home.module.js file using ECMAScript 6:

import modConfig from './home.config';
import modController from './home.controller';

let mod = angular.module('prototype.home', [
    'ionic',
    'ui.router'
]);

mod.config(modConfig);
mod.controller('HomeController', modController);

export default mod = mod.name

Use Folders-by-Feature structure

Reasons to use Folders-by-Feature structure:

  1. The number of files in your folders are limited to few.
  2. Finding all the files you need to modify for a specific feature is easy (they are in the same folder!).
  3. You can work independently on a feature.
  4. Knowing what the module represents is easy (the folder name is sufficient).
lib/
    index.js
    home/
        home.module.js
        home.config.js
        home.service.js
        home.controller.js
        home.html
        home.scss
    parameters/
        parameters.module.js
        parameters.config.js
        parameters.service.js
        parameters.controller.js
        parameters.html
        parameters.scss

Recommended boilerplates

If you are ready to start making some changes in your application with the architecture I suggested in this post you can clone those repositories, they are a good start:

  1. [Angular Material]: shprink/angular1.4-ES6-material-webpack-boilerplate. A simple AngularJS 1.4 boilerplate using ES6, material design and Webpack.
  2. [Ionic]: shprink/ios-android-wordpress-ionic-webpack-ES6. Repo created for the TutsPlus article: Creating iOS/Android mobile applications for WordPress using Ionic SDK, Webpack, ES6 and WP-API.

Creating an application with AngularJS 1.4, ECMAScript 6, Material Design and Webpack

AngularJS 1.4 has now been released.

It introduces a new Router, a translation system similar to angular-translate created by Pascal Precht, some performance enhancement and a better Webpack, Browserify support (CommonJS) without any “hacks”. You can for example directly import AngularJS like this

import 'angular/angular.js';

instead of this

require('expose?angular!exports?window.angular!angular/angular.js')

Demo

Source available on Github : https://github.com/shprink/angular1.4-ES6-material-webpack-boilerplate


Basic Needs

Dependencies (package.json)

For this tutorial we will need AngularJS, Angular Material, the UI router (The new router is not ready for production yet) and a icon library.

"dependencies": {
    "angular": "~1.4.0",
    "angular-animate": "~1.4.0",
    "angular-aria": "~1.4.0",
    "angular-material": "^0.10.1",
    "angular-ui-router": "^0.2.14",
    "font-awesome": "^4.3.0"
}

Webpack

If you need an explanation on what is Webpack for, I suggest you go check out a previous post of mine: http://julienrenaux.fr/2015/03/30/introduction-to-webpack-with-practical-examples/

We will need the following Webpack loaders to be able to compile ECMAScript 6 code and to process CSS, HTML files.

"devDependencies": {
    "babel-loader": "^5.0.0",
    "css-loader": "^0.12.0",
    "file-loader": "^0.8.1",
    "html-loader": "^0.3.0",
    "html-webpack-plugin": "^1.3.0",
    "style-loader": "^0.12.1"
}

The webpack.config.js cannot be easier:

module.exports = {
    entry: './lib/index.js',
    output: {
        path: './www',
        filename: 'bundle-[hash:6].js'
    },
    module: {
        loaders: [{
            test: /.html$/,
            loader: 'file?name=templates/[name]-[hash:6].html'
        }, {
            test: /.css$/,
            loader: "style!css"
        }, {
            test: /.js$/,
            exclude: /(node_modules)/,
            loader: "ng-annotate?add=true!babel"
        }, {
            test: [/fontawesome-webfont.svg/, /fontawesome-webfont.eot/],
            loader: 'file?name=fonts/[name].[ext]'
        }]
    },
    plugins: [
        new HtmlWebpackPlugin({
            filename: 'index.html',
            template: './lib/index.html'
        })
    ]
};

Entry point (lib/index.js)

The following entry point gathers all our application basic needs: Angular, Material Design, the router and the icons. Here we use import which is the ECMAScript 6 way to require CommonJS modules, inject angularMaterial and angularUIRouter as our module dependencies and to finish we export our module as default (You can export several modules within a single file, default is the export you will get if you do not specify a specific import).

// Import angular
import 'angular/angular.js';
// Material design css
import 'angular-material/angular-material.css';
// Icons
import 'font-awesome/css/font-awesome.css';
// Materail Design lib
import angularMaterial from 'angular-material';
// Router
import angularUIRouter from 'angular-ui-router';

// Create our demo module
let demoModule = angular.module('demo', [
    angularMaterial,
    angularUIRouter
])

export default demoModule;

Index.html

The index.html needs two things. A way to bootstrap the application (here ng-app="demo") and a way to include the JavaScript file generated by Webpack (here src="{%=o.htmlWebpackPlugin.assets[chunk]%}").

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
    </head>
    <body ng-strict-di ng-app="demo">
        {% for (var chunk in o.htmlWebpackPlugin.assets) { %}
        <script src="{%=o.htmlWebpackPlugin.assets[chunk]%}"></script>
        {% } %}
    </body>
</html>

Now you should have your demo module running. To make sure it works add the following lines to your entry point:

demoModule.run(($log) => {
    $log.info('demo running');
})

and check the console!


Going further

Create your own module

Now that our application is running let’s create a home module with ES6!

// Create a new module
let homeModule = angular.module('demo.home', []);
// Named export is needed to inject modules directly as Angular dependencies
export default homeModule = homeModule.name

Then we can import it the same way as other modules:

import home from './home/home.module.js';

// Create our demo module
let demoModule = angular.module('demo', [
    angularMaterial,
    angularUIRouter,
    home
])

Now let’s create a controller function './home.controller' that will be imported by our new module:

export default function($scope) {
    'ngInject';
}

'ngInject'; is an annotation of ng-annotate that allow us to use AngularJS in strict mode. If you want to know more about Ng-annotate here is an explanation: http://julienrenaux.fr/2015/01/18/angularjs-1-x-open-source-projects-to-follow-in-2015/#Ng-Annotate

We can then import the function that way:

let homeModule = angular.module('demo.home', []);

import HomeController from './home.controller';

homeModule.controller('HomeController', HomeController);

export default homeModule = homeModule.name

Full demo

A working example is available on Github, feel free to fork: https://github.com/shprink/angular1.4-ES6-material-webpack-boilerplate

Install

git clone git@github.com:shprink/angular1.4-ES6-material-webpack-boilerplate.git
cd angular1.4-ES6-material-webpack-boilerplate.git
npm install

Dev (liverelaod server)

npm run-script devserver

Build

npm run-script build

AngularJs 1.x Open Source projects to follow in 2015

Ionic Framework

ionic

Ionic needs no introduction, it is a HTML5 Mobile Framework for building, cross-platform hybrid native apps with HTML, JavaScript, and CSS.

In 2014 Ionic became one of the top 50 most popular open source projects in the world, with over 12,000 stars on GitHub and over 50,000 new apps were created using Ionic Framework.

2015 will be an important year for Ionic as they will slowly move from a simple HTML5 hybrid framework to a whole galaxy of tools to create hybrid apps. The npm ionic package is already available with features such as building cordova apps or creating splashscreens and icons for IOS and Android.

Developers to follow:


Angular Material Design

Material Design is a Google specification for a unified system of visual, motion, and interaction design that adapts across different devices.

angular/material is a lean, lightweight set of AngularJS-native UI elements that implement the material design specification for use in Angular single-page applications (SPAs). This is the official AngularJs Material Design library.

No doubt that 2015 will be the year of Material Design, it is already everywhere from Android to Polymer. I have a lot of expectations for this library that I already use in my current company. Unfortunately a lot of features are missing (menu, dropdown menu, select, multi-select etc.) and the development pace is not high enough to reach the planning explained at the end of this video:

Go Google go!

Developers to follow:


lumX Material Design

lumx

Material Design is a Google specification for a unified system of visual, motion, and interaction design that adapts across different devices.

LumX will help you to design beautiful applications, faster and easier, respecting Material Design specification in a pixel perfect way.

In a lot of aspects this set of directives is more advanced than the official Angular Material Design repository. I really hope this project will have the attention it deserves. A downside of it though is that it depends on jQuery..

Developers to follow:


Foundation for Apps

foundation_apps

Foundation for Apps is an Angular-powered framework for building powerful responsive web apps. With this project the Zurb foundation is clearly trying to compete with Ionic Framework. There is still a lot of work to do but it sounds really promissing!

Developers to follow:


Angular Hint – Runtime hinting for AngularJS

Whether you start a new project or want to have feedbacks on an existing Angular app, this project is for you. It basically tells you if you follow AngularJs’s standards or not.

If you do not want to add this project to your development dependencies you can use a Chrome plugin that includes it.

If you also wants to follow AngularJs’s best practice I suggest you to read John Papa’s angularjs-styleguide. There are a lot of great advices in there and I personally always go through it before starting a new project. Some are explained in this video:

Developers to follow:


Angular-google-maps

angular-google-map

Angular-google-maps is a set of directives for the Google Maps JavaScript API. It is part of the great Angular UI organization. This repository changed a lot in Q4 2014 and released 2.x versions that is really popular lately.

Developers to follow:


Ng-Annotate

If you are familiar with AngularJs and it’s concept of Dependency Injection (DI), you know that it can be really annoying to use when minifying your code:

Minification KO

angular.module('ngAppStrictDemo', [])
// BadController will fail to instantiate, due to relying on automatic function annotation,
// rather than an explicit annotation
.controller('BadController', function($scope) {
  $scope.a = 1;
  $scope.b = 2;
})

Minification OK

angular.module('ngAppStrictDemo', [])
// Unlike BadController, GoodController will not fail to be instantiated,
// due to using explicit annotations using the array style.
.controller('GoodController', ['$scope', function($scope) {
  $scope.a = 1;
  $scope.b = 2;
}])

angular.module('ngAppStrictDemo', [])
// Unlike BadController, GoodController2 will not fail to be instantiated,
// due to using the $inject property.
.controller('GoodController2', GoodController2);
function GoodController2($scope) {
  $scope.name = "World";
}
GoodController2.$inject = ['$scope'];

Before AngularJs 1.3 it was difficult to know if you code was production ready (minifiable) or not. To tackle this very problematic AngularJs 1.3 introduced the ngStrictDi directive which tells you if your code is production ready or not.

Ng-Annotate basically makes the “Minification KO” example a production ready code! You do not have to worry about the $inject property or the array style anymore. This project literally changed my life, with it I was able to remove all the duplicate service injection ['$scope', function($scope) and the $inject property GoodController2.$inject that I used before.

Developers to follow:

AngularJs, Browserify Polymer and Sass boilerplate

Source available on Github : https://github.com/shprink/angularjs-browserify-polymer-boilerplate

Two way data biding

Using Polymer and AngularJs at the same time is a bit problematic. The two way data biding (AngularJs feature) cannot natively work with Polimer web components. Some project are emerging though:

Those projects are not mature enought to use, so I decided to directly use AngularJs’s resources within Polymer elements.

Using AngularJs’s services within Polymer

var userService = angular.injector(['boilerplate']).get('UserService');

Polymer('user-list', {
    ready: function() {
        this.list = userService.getList();
    },
    tapAction: function(e) {
        console.log('tap', e);
    }
});

Using AngularJs’s scope within Polymer

Polymer('boilerplate-login', {
    ready: function() {
        this.angularScope = angular.element(this).scope();
    },
    sendForm: function(){
        this.angularScope.goToHome();
    }
});