Friday, 4 March 2016

AngularJS Best Practices: I’ve Been Doing It Wrong! Part 1 of 3

Part 1: Directory Layout that Scales with the Project

We need a directory structure that will make it easy to
  • add things as we go,
  • keep the size of our modules manageable,
  • and keep everything in an easy-to-navigate, logical structure.
Here’s one example of a proposed structure: angular-seed project. It is made by the AngularJS team, it includes lots of good things like tests and places for all the essential parts:
  • app/ – all of the files to be used in production
    • css/ – css files
    • img/ – image files
    • index.html – app layout file (the main html template file of the app)
    • js/ – javascript files
      • app.js – application
      • controllers.js – application controllers
      • directives.js – application directives
      • filters.js – custom angular filters
      • services.js – custom angular services
    • lib/ – angular and 3rd party javascript libraries
    • partials/ – angular view partials (partial html templates)
  • config/ – config files for running unit tests with Testacular/Karma
  • scripts/ – handy shell/js/ruby scripts (run unit tests and dev server)
  • test/ – test source files and libraries
However, as your project grows, you’ll add tons and tons of code into controllers, services and directives. Using a file for each of these areas steers you into a direction where you’ll end up with “piles on the floor” – you know your AccountListController is somewhere in the Controllers pile, but it will take you a while to find it. In other words, angular-seed packages your code by layers. Here’s a place for all controllers, here’s a place for data services, etc.
You can make it more usable by using sub-directories for controllers, and so on:
  • controllers/
    • LoginController.js
    • RegistrationController.js
    • AccountListController.js
    • SearchResultsController.js
  • directives.js
  • filters.js
  • services/
    • CartService.js
    • UserService.js
    • AccountService.js
This is much better, since you have a nice place for everything. Now I can easily find where to add new modules or where to find current code that handles Account Lists.
But what if I need to reuse my login controller in another application? I can just copy LoginController.js into the other app’s controllers directory. As soon as I do that and refresh the browser, I see an error: the system does not know what a User object is. Ah yes, I forgot to copy UserService.js into services directory. A more complex feature would also involve some custom directives and filters. And do not forget the tests! It would suck if I copied all that code into another project but forgot to copy its tests from all the subdirectories of tests/unit that correspond to controllers, services, filters and directives of this feature. So many places to remember to look into when reusing code! Yuck! My head hurts already.
Let’s try a different approach. Enter ng-boilerplate, the best AngularJS project template in the world (to my knowledge at the time of this writing). Here’s its version of module layout:
  • build/
  • src/
    • app/
    • assets/
    • components/
    • less/
  • testacular/ (or karma/)
  • vendor/
  • Grunfile.js
  • module.prefix
  • module.suffix
  • package.json
There’s a lot of stuff here, but that’s justifiable: we are talking about huge application scalability, not an introduction-level tutorial. I’ll focus on the main part, our application source, first, and then I’ll go through the essential supporting parts one by one.

Layout of src: feature-based modularization

  • src/
    • app/
      • about/
        • about.js
        • about.tpl.html
      • home/
        • home.js
        • home.less
        • home.spec.js
        • home.tpl.html
      • app.js
      • app.spec.js
    • assets/
    • components/
    • less/
    • index.html
Application-specific code lives under app/. In the boilerplate, the application has two sections, “home” and “about”. Each of the features has its own single directory, which contains everything: Angular module declaration, controllers, routes, templates, styles, and unit tests.
In a larger app, we’ll add more sub-directories for specific controllers, services and directives related to specific features. We’ll probably grow the test suite and place tests into a “tests” directory. But still, all will be contained in one place, so you know where to look for bugs related to the “home” feature, or to add more functionality to it. A feature can be copied for reuse in one operation. This will scale much better than the previous layer-based modularization. The best part of this layout is that any new developer who looks at your code will instantly understand what your application does. And you, looking at your own app a year from now, will not scratch your head and mumble obscenities about the moron who wrote this crap.
The .spec.js files are the unit tests. The build system will scan the directory structure and pick up all the tests automatically.
The .tpl.html files are HTML templates. The rest is not hard to follow if you peek into the source.
Note, again, the app-specific unit tests alongside app.js module. This is logical. This is good. I like it.
Assets contains static files – fonts, images, static style sheets (ng-boilerplate will place here an IE-specific stylesheet to enable Font Awesome for it).
Components is a place for reusable parts of the app – both third party and your own. Every component you place here should be drag-and-drop reusable in any other project; they should depend on no other components that aren’t similarly drag-and-drop reusable.
Less contains all the LESS/CSS sources. If you prefer SASS to LESS, you can replace it and use grunt-sass task to integrate SASS compilation into the Grunt build.
Finally, index.html is the page of our single-page application. In addition to being an HTML5 document and an AngularJS template, it is also a Grunt template (see the following section for details on Grunt), and variables defined in the Gruntfile.js, such as directory names based on the projec name can be used here.
Next, let’s look in more detail at all the other parts that make ng-boilerplate not just a though-through directory layout, but a real kickstarter for projects.

Grunt

Ng-boilerplate uses Grunt, “The JavaScript Task Runner” as its build system. Grunt automates gathering all assets for distribution, compiling LESS into CSS, linting and minifying JavaScript sources, maintaining the right paths in templates, and running unit tests. It also knows not to include unit tests into production package, and to combine all your JavaScript code into a single file. All these things happen automatically when Grunt is watching your project directory. This is pretty awesome.
Configuration for Grunt is in Gruntfile.js, and custom scripts for it are in build/ directory.
An important advantage that an automatic build system and automatic test suite gives is the ability to build onTravis-CI. The code is automatically tested on multiple browsers as soon as it is pushed to github. If you are working on an open-source project, it’s a sin not to take advantage of this.

Testacular (or Karma)

The creators of AngularJS made their own, truly spectacular, system for running unit tests. I’ll talk about unit tests in more details a bit later. For now, I’ll just say that this is what testacular/ (or karma/) directory is for: test configuration.

Vendor

The vendor/ folder contains libraries that are either foundational or necessary for the build processes to succeed. There is no automatic processing of anything in this directory during build process – if you wish to add a new library, you must add whatever logic is necessary to the Grunt build file. The documentation for this section contains an example of how to add jQuery to the vendor files.

Goodies: Bootstrap, Fonts, LESS

As a front-end developer, you are probably already familiar with these tools. We already use them in most projects – why not save time by making them part of the project template?
In the next part of the series, I’ll talk about automated testing with AngularJS, using the ng-boilerplate project template.

TL;DR (but really, read the whole three articles – it’s worth it)

  • Use ng-boilerplate as a template for your projects.
  • Learn to use unit-testing and end-to-end testing with Angular, and make it your religion. Use testacular/karma and Jasmine. It will save you months and years of development. Use end-to-end tests in addition to unit tests. Borrow test practices from good AngularJS applications.
  • Acquire new Angular habits, and avoid old habits that do not translate into AngularJS world.

1 comment:

Angular Tutorial (Update to Angular 7)

As Angular 7 has just been released a few days ago. This tutorial is updated to show you how to create an Angular 7 project and the new fe...