mgechev

Introduction

? Community-driven set of best practices for AngularJS application development
Under MIT License
By mgechev

angularjs best-practices


Introduction

The goal of this style guide is to present a set of best practices and style guidelines for one AngularJS application.
These best practices are collected from:



  1. AngularJS source code

  2. Source code or articles I've read

  3. My own experience


Note 1: this is still a draft of the style guide, its main goal is to be community-driven so filling the gaps will be greatly appreciated by the whole community.


Note 2: before following any of the guidelines in the translations of the English document, make sure they are up-to date. The latest version of the AngularJS style guide is in the current document.


In this style guide you won't find common guidelines for JavaScript development. Such can be found at:



  1. Google's JavaScript style guide

  2. Mozilla's JavaScript style guide

  3. Douglas Crockford's JavaScript style guide

  4. Airbnb JavaScript style guide

  5. Idiomatic JavaScript style guide


For AngularJS development recommended is the Google's JavaScript style guide.


In AngularJS's GitHub wiki there is a similar section by ProLoser, you can check it here.


Translations

Table of content

General
Directory structure

Since a large AngularJS application has many components it's best to structure it in a directory hierarchy.
There are two main approaches:



In this way the directory structure will look like:


.
├── app
│   ├── app.js
│   ├── controllers
│   │   ├── home
│   │   │   ├── FirstCtrl.js
│   │   │   └── FirstCtrl.spec.js
│   │   │   └── SecondCtrl.js
│   │   │   └── SecondCtrl.spec.js
│   │   └── about
│   │   └── ThirdCtrl.js
│   │   └── ThirdCtrl.spec.js
│   ├── directives
│   │   ├── home
│   │   │   └── directive1.js
│   │   │   └── directive1.spec.js
│   │   └── about
│   │   ├── directive2.js
│   │   ├── directive2.spec.js
│   │   └── directive3.js
│   │   └── directive3.spec.js
│   ├── filters
│   │   ├── home
│   │   └── about
│   └── services
│   ├── CommonService.js
│   ├── CommonService.spec.js
│   ├── cache
│   │   ├── Cache1.js
│   │   ├── Cache1.spec.js
│   │   └── Cache2.js
│   │   └── Cache2.spec.js
│   └── models
│   ├── Model1.spec.js
│   ├── Model1.js
│   └── Model2.spec.js
│   └── Model2.js
├── partials
├── lib
└── e2e-tests



Here is its layout:


.
├── app
│   ├── app.js
│   ├── common
│   │   ├── controllers
│   │   ├── directives
│   │   ├── filters
│   │   └── services
│   ├── home
│   │   ├── controllers
│   │   │   ├── FirstCtrl.js
│   │   │   ├── FirstCtrl.spec.js
│   │   │   └── SecondCtrl.js
│   │   │   └── SecondCtrl.spec.js
│   │   ├── directives
│   │   │   └── directive1.js
│   │   │   └── directive1.spec.js
│   │   ├── filters
│   │   │   ├── filter1.js
│   │   │   ├── filter1.spec.js
│   │   │   └── filter2.js
│   │   │   └── filter2.spec.js
│   │   └── services
│   │   ├── service1.js
│   │   ├── service1.spec.js
│   │   └── service2.js
│   │   └── service2.spec.js
│   └── about
│   ├── controllers
│   │   └── ThirdCtrl.js
│   │   └── ThirdCtrl.spec.js
│   ├── directives
│   │   ├── directive2.js
│   │   ├── directive2.spec.js
│   │   └── directive3.js
│   │   └── directive3.spec.js
│   ├── filters
│   │   └── filter3.js
│   │   └── filter3.spec.js
│   └── services
│   └── service3.js
│   └── service3.spec.js
├── partials
├── lib
└── e2e-tests



app
├── app.js
└── my-complex-module
   ├── controllers
   ├── directives
   ├── filters
   └── services



app
└── directives
├── directive1
│   ├── directive1.html
│   ├── directive1.js
│   ├── directive1.spec.js
│   └── directive1.sass
└── directive2
├── directive2.html
├── directive2.js
├── directive2.spec.js
└── directive2.sass


This approach can be combined with both directory structures above.
* The unit tests for a given component (*.spec.js) should be located in the directory where the component is. This way when you make changes to a given component finding its test is easy. The tests also act as documentation and show use cases.


services
├── cache
│   ├── cache1.js
│   └── cache1.spec.js
└── models
├── model1.js
└── model1.spec.js



Conventions about component naming can be found in each component section.


Markup

TLDR; Put the scripts at the bottom.


```html


MyApp






```


Keep things simple and put AngularJS specific directives after standard attributes. This will make it easier to skim your code and will make it easier to maintain because your attributes are consistently grouped and positioned.


```html



```


Other HTML attributes should follow the Code Guide's recommendation


Naming conventions

The following table is shown the naming conventions for every element:


Element | Naming style | Example | usage
----|------|----|--------
Modules | lowerCamelCase | angularApp |
Controllers | Functionality + 'Ctrl' | AdminCtrl |
Directives | lowerCamelCase | userInfo |
Filters | lowerCamelCase | userFilter |
Services | UpperCamelCase | User | constructor
Factories | lowerCamelCase | dataFactory | others


Others

This will make your testing easier and in some cases prevent unexpected behaviour (for example, if you missed $scope.$apply in setTimeout).



javascript
module.factory('Service', function ($rootScope, $timeout, MyCustomDependency1, MyCustomDependency2) {
return {
//Something
};
});


Modules

Controllers

JavaScript
function MyCtrl(dependency1, dependency2, ..., dependencyn) {
// ...
}
module.controller('MyCtrl', MyCtrl);


In order to prevent problems with minification, you can automatically generate the array definition syntax from the standard one using tools like ng-annotate (and grunt task grunt-ng-annotate).


Another alternative will be to use $inject like:


```JavaScript
angular
.module('app')
.controller('HomepageCtrl', HomepageCtrl);


HomepageCtrl.$inject = ['$log', '$http', 'ngRoute'];


function HomepageCtrl($log, $http, ngRoute) {
// ...
}
```



```html

{{ main.things }}


```


```JavaScript
app.controller('MainCtrl', MainCtrl);
MainCtrl.$inject = ['$http'];


function MainCtrl ($http) {
var vm = this;
//a clearer visual connection on how is defined on the view
vm.title = 'Some title';
vm.description = 'Some description';


$http.get('/api/main/things').then(function (response) {
vm.things = response.data.things; // Adding 'things' as a property of the controller
});

}
```


Avoid using this keyword repeatedly inside a controller:


```JavaScript
app.controller('MainCtrl', MainCtrl);
MainCtrl.$inject = ['$http'];


// Avoid
function MainCtrl ($http) {
this.title = 'Some title';
this.description = 'Some description';

$http.get('/api/main/things').then(function (response) {
// Warning! 'this' is in a different context here.
// The property will not be added as part of the controller context
this.things = response.data.things;
});
}
```


Using a consistent and short variable name is preferred, for example vm.


The main benefits of using this syntax:
* Creates an 'isolated' component - binded properties are not part of $scope prototype chain. This is good practice since $scope prototype inheritance has some major drawbacks (this is probably the reason it was removed on Angular 2):
* It is hard to track where data is coming from.
* Scope's value changes can affect places you did not intend to affect.
* Harder to refactor.
* The 'dot rule'.
* Removes the use of $scope when no need for special operations (as mentioned above). This is a good preparation for AngularJS V2.
* Syntax is closer to that of a 'vanilla' JavaScript constructor


Digging more into controller as: digging-into-angulars-controller-as-syntax
* If using array definition syntax, use the original names of the controller's dependencies. This will help you produce more readable code:


```JavaScript
function MyCtrl(l, h) {
// ...
}


module.controller('MyCtrl', ['$log', '$http', MyCtrl]);
```


which is less readable than:


```JavaScript
function MyCtrl($log, $http) {
// ...
}


module.controller('MyCtrl', ['$log', '$http', MyCtrl]);
```


This especially applies to a file that has so much code that you'd need to scroll through. This would possibly cause you to forget which variable is tied to which dependency.



```Javascript
//This is a common behaviour (bad example) of using business logic inside a controller.
angular.module('Store', [])
.controller('OrderCtrl', function () {
var vm = this;


vm.items = [];

vm.addToOrder = function (item) {
vm.items.push(item);//-->Business logic inside controller
};

vm.removeFromOrder = function (item) {
vm.items.splice(vm.items.indexOf(item), 1);//-->Business logic inside controller
};

vm.totalPrice = function () {
return vm.items.reduce(function (memo, item) {
return memo + (item.qty * item.price);//-->Business logic inside controller
}, 0);
};


});
```


When delegating business logic into a 'model' service, controller will look like this (see 'use services as your Model' for service-model implementation):


```Javascript
// order is used as a 'model'
angular.module('Store', [])
.controller('OrderCtrl', function (order) {
var vm = this;


vm.items = order.items;

vm.addToOrder = function (item) {
order.addToOrder(item);
};

vm.removeFromOrder = function (item) {
order.removeFromOrder(item);
};

vm.totalPrice = function () {
return order.total();
};


});
```


Why business logic / app state inside controllers is bad?
* Controllers instantiated for each view and dies when the view unloads
* Controllers are not reusable - they are coupled with the view
* Controllers are not meant to be injected



Example:


JavaScript
// app.js
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Custom events:
- 'authorization-message' - description of the message
- { user, role, action } - data format
- user - a string, which contains the username
- role - an ID of the role the user has
- action - specific action the user tries to perform
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * */



```JavaScript
function myFormat() {
return function () {
// ...
};
}
module.filter('myFormat', myFormat);


function MyCtrl($scope, myFormatFilter) {
// ...
}


module.controller('MyCtrl', MyCtrl);
``
* In case of nested controllers use "nested scoping" (the
controllerAs` syntax):


app.js
javascript
module.config(function ($routeProvider) {
$routeProvider
.when('/route', {
templateUrl: 'partials/template.html',
controller: 'HomeCtrl',
controllerAs: 'home'
});
});

HomeCtrl
```javascript
function HomeCtrl() {
var vm = this;


 vm.bindingValue = 42;

}
**template.html**html


```


Directives

Filters

Services

This section includes information about the service component in AngularJS. It is not dependent of the way of definition (i.e. as provider, .factory, .service), except if explicitly mentioned.



See 'Avoid writing business logic inside controllers' for an example of a controller consuming this service.
* Services representing the domain preferably a service instead of a factory. In this way we can take advantage of the "klassical" inheritance easier:


```JavaScript
function Human() {
//body
}
Human.prototype.talk = function () {
return "I'm talking";
};

function Developer() {
//body
}
Developer.prototype = Object.create(Human.prototype);
Developer.prototype.code = function () {
return "I'm coding";
};

myModule.service('human', Human);
myModule.service('developer', Developer);

```



Templates

html
<div ng-controller="MainCtrl as main">
<div ng-style="main.divStyle">my beautifully styled div which will work in IE</div>;
</div>


```JavaScript
angular
.module('app')
.controller('MainCtrl', MainCtrl);


MainCtrl.$inject = [];


function MainCtrl() {
var vm = this;
vm.divStyle = {
width: 200,
position: 'relative'
};
}
```


Routing

E2E Testing

E2E tests are the next common sense step after unit tests, that will allow you to trace bugs and errors in the behaviour of your system. They are great for providing a sanity check that most common scenarios of using your application works. This way you can automate the process and run it each time before you deploy your application.


Ideally, Angular End-to-End tests are written in Jasmine. These tests are run using the Protractor E2E test runner which uses native events and has special features for Angular applications.


File structure:


.
├── app
│   ├── app.js
│   ├── home
│   │   ├── home.html
│   │   ├── controllers
│   │   │   ├── FirstCtrl.js
│   │   │   ├── FirstCtrl.spec.js
│   │   ├── directives
│   │   │   └── directive1.js
│   │   │   └── directive1.spec.js
│   │   ├── filters
│   │   │   ├── filter1.js
│   │   │   └── filter1.spec.js
│   │   └── services
│   │   ├── service1.js
│   │   └── service1.spec.js
│   └── about
│   ├── about.html
│   ├── controllers
│   │   └── ThirdCtrl.js
│   │   └── ThirdCtrl.spec.js
│   └── directives
│      ├── directive2.js
│      └── directive2.spec.js
├── partials
├── lib
└── e2e-tests
├── protractor.conf.js
└── specs
├── home.js
└── about.js


i18n

Performance

Contribution

Since the goal of this style guide is to be community-driven, contributions are greatly appreciated.
For example, you can contribute by extending the Testing section or by translating the style guide to your language.


Contributors

| | | | | |
:---: |:---: |:---: |:---: |:---: |:---: |
mgechev |morizotter |chatii2412 |pascalockert |yanivefraim |ericguirbal |


| | | | | |
:---: |:---: |:---: |:---: |:---: |:---: |
agnislav |ray7551 |mainyaa |LeonardCModoran |elfinxx |tiagobarreto |


| | | | | |
:---: |:---: |:---: |:---: |:---: |:---: |
Xuefeng-Zhu |SullyP |giacomocusinato |rubystream |lukaszklis |Spuffynism |


| | | | | |
:---: |:---: |:---: |:---: |:---: |:---: |
susieyy |cironunes |cavarzan |guiltry |MSafter |mingchen |


| | | | | |
:---: |:---: |:---: |:---: |:---: |:---: |
jmblog |luixaviles |andreasonny83 |kuzzmi |jabhishek |adambabik |


| | | | | |
:---: |:---: |:---: |:---: |:---: |:---: |
astalker |clbn |atodorov |apetro |valgreens |bitdeli-chef |


| | | | | |
:---: |:---: |:---: |:---: |:---: |:---: |
meetbryce |unseen1980 |cminhho |dwmkerr |kuzmeig1 |dominickolbe |


| | | | | |
:---: |:---: |:---: |:---: |:---: |:---: |
gsamokovarov |grvcoelho |yassirh |bargaorobalo |hermankan |jesselpalmer |


| | | | | |
:---: |:---: |:---: |:---: |:---: |:---: |
capaj |johnnyghost |jordanyee |whoan |nacyot |mariolamacchia |


| | | | | |
:---: |:---: |:---: |:---: |:---: |:---: |
mischkl |michaelmov |kirstein |mo-gr |mortonfox |cryptojuice |


| | | | | |
:---: |:---: |:---: |:---: |:---: |:---: |
nktssh |olafahn |olov |vorktanamobay |QuietHeartThinkingFar |raphaelfruneaux |


| | | | | |
:---: |:---: |:---: |:---: |:---: |:---: |
sahat |ganchiku |kaneshin |imaimiami |dooart |thomastuts |


| | | | | |
:---: |:---: |:---: |:---: |:---: |:---: |
UrielMiranda |vkarampinis |grapswiz |coderhaoxin |giantray |ntaoo |


| |
:---: |:---: |
seyyah |dchest |