mgechev/angularjs-style-guide

Created: 2015-11-23 22:22 Updated: 2015-11-23 22:22 Source: https://github.com/mgechev/angularjs-style-guide Notebook: All Tech/Frontend Development

Skip to content
Community-driven set of best practices for AngularJS application development https://mgechev.github.io/angularjs-style-guide/
LICENSE Fix #125 4 months ago
README-de-de.md Namensgebung als Tabelle hinzugefügt 2 months ago
README-es-es.md fix typos 2 years ago
README-fr-fr.md [FR] Typos 3 months ago
README-id-id.md Fix markdown in EN, RU, ID and IT version 10 months ago
README-it-it.md [IT] Translate Filter section 20 days ago
README-ja-jp.md Update README-ja-jp.md 5 months ago
README-ko-kr.md [KR] remove english block (2) 10 months ago
README-pl-pl.md Updates for polish translation 2 years ago
README-pt-br.md Fixed typo 10 months ago
README-ru-ru.md Update README-ru-ru.md 6 months ago
README-sr-lat.md fix typos 2 years ago
README-sr.md fix typos 2 years ago
README-tr-tr.md Turkish lang added. 7 months ago
README-zh-cn.md Translate un-translated string. 3 months ago
README.md Merge pull request #140 from VladimirKazan/VladimirKazan-patch-1 12 hours ago

README.md

68747470733a2f2f6261646765732e6769747465722e696d2f4a6f696e253230436861742e737667.bin

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. GitHub's JavaScript style guide
  4. Douglas Crockford's JavaScript style guide
  5. Airbnb 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:

  • Creating high-level divisions by component types and lower-level divisions by functionality.

In this way the directory structure will look like:

normal.
├── app
│   ├── app.js
│   ├── controllers
│   │   ├── home
│   │   │   ├── FirstCtrl.js
│   │   │   └── SecondCtrl.js
│   │   └── about
│   │       └── ThirdCtrl.js
│   ├── directives
│   │   ├── home
│   │   │   └── directive1.js
│   │   └── about
│   │       ├── directive2.js
│   │       └── directive3.js
│   ├── filters
│   │   ├── home
│   │   └── about
│   └── services
│       ├── CommonService.js
│       ├── cache
│       │   ├── Cache1.js
│       │   └── Cache2.js
│       └── models
│           ├── Model1.js
│           └── Model2.js
├── partials
├── lib
└── test
normal
  • Creating high-level divisions by functionality and lower-level divisions by component types.

Here is its layout:

normal.
├── app
│   ├── app.js
│   ├── common
│   │   ├── controllers
│   │   ├── directives
│   │   ├── filters
│   │   └── services
│   ├── home
│   │   ├── controllers
│   │   │   ├── FirstCtrl.js
│   │   │   └── SecondCtrl.js
│   │   ├── directives
│   │   │   └── directive1.js
│   │   ├── filters
│   │   │   ├── filter1.js
│   │   │   └── filter2.js
│   │   └── services
│   │       ├── service1.js
│   │       └── service2.js
│   └── about
│       ├── controllers
│       │   └── ThirdCtrl.js
│       ├── directives
│       │   ├── directive2.js
│       │   └── directive3.js
│       ├── filters
│       │   └── filter3.js
│       └── services
│           └── service3.js
├── partials
├── lib
└── test
normal
  • In case the directory name contains multiple words, use lisp-case syntax:
normalapp
 ├── app.js
 └── my-complex-module
     ├── controllers
     ├── directives
     ├── filters
     └── services
normal
  • Put all the files associated with the given directive (i.e. templates, CSS/SASS files, JavaScript) in a single folder. If you choose to use this style be consistent and use it everywhere along your project.
normalapp
└── directives
    ├── directive1
    │   ├── directive1.html
    │   ├── directive1.js
    │   └── directive1.sass
    └── directive2
        ├── directive2.html
        ├── directive2.js
        └── directive2.sass
normal

This approach can be combined with both directory structures above.

  • The unit tests for a given component 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.
normalservices
├── cache
│   ├── cache1.js
│   └── cache1.spec.js
└── models
    ├── model1.js
    └── model1.spec.js
normal
  • The  app.js  file should contain route definitions, configuration and/or manual bootstrap (if required).
  • Each JavaScript file should only hold a single component. The file should be named with the component's name.
  • Use AngularJS project structure template like Yeoman, ng-boilerplate.

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

Markup

TLDR; Put the scripts at the bottom.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>MyApp</title>
</head>
<body>
  <div ng-app="myApp">
    <div ng-view></div>
  </div>
  <script src="angular.js"></script>
  <script src="app.js"></script>
</body>
</html>

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.

<form class="frm" ng-submit="login.authenticate()">
  <div>
    <input class="ipt" type="text" placeholder="name" require ng-model="user.name">
  </div>
</form>

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
Services lowerCamelCase dataFactory others

Others

  • Use:
    •  $timeout  instead of  setTimeout 
    •  $interval  instead of  setInterval 
    •  $window  instead of  window 
    •  $document  instead of  document 
    •  $http  instead of  $.ajax 

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

  • Automate your workflow using tools like:

  • Use promises ( $q ) instead of callbacks. It will make your code look more elegant and clean, and save you from callback hell.

  • Use  $resource  instead of  $http  when possible. The higher level of abstraction will save you from redundancy.
  • Use an AngularJS pre-minifier (ng-annotate) for preventing problems after minification.
  • Don't use globals. Resolve all dependencies using Dependency Injection, this will prevent bugs and monkey patching when testing.
  • Avoid globals by using Grunt/Gulp to wrap your code in Immediately Invoked Function Expression (IIFE). You can use plugins like grunt-wrap or gulp-wrap for this purpose. Example (using Gulp)

    gulp.src("./src/*.js")
    .pipe(wrap('(function(){\n"use strict";\n<%= contents %>\n})();'))
    .pipe(gulp.dest("./dist"));
  • Do not pollute your  $scope . Only add functions and variables that are being used in the templates.
  • Prefer the usage of controllers instead of  ngInit . The only appropriate use of  ngInit  is for aliasing special properties of  ngRepeat . Besides this case, you should use controllers rather than  ngInit  to initialize values on a scope. The expression passed to  ngInit  should go through lexing, parsing and evaluation by the Angular interpreter implemented inside the  $parse  service. This leads to:
    • Performance impact, because the interpreter is implemented in JavaScript
    • The caching of the parsed expressions inside the  $parse  service doesn't make a lot of sense in most cases, since  ngInit  expressions are often evaluated only once
    • Is error-prone, since you're writing strings inside your templates, there's no syntax highlighting and further support by your editor
    • No run-time errors are thrown
  • Do not use  $  prefix for the names of variables, properties and methods. This prefix is reserved for AngularJS usage.
  • When resolving dependencies through the DI mechanism of AngularJS, sort the dependencies by their type - the built-in AngularJS dependencies should be first, followed by your custom ones:
module.factory('Service', function ($rootScope, $timeout, MyCustomDependency1, MyCustomDependency2) {
  return {
    //Something
  };
});

Modules

  • Modules should be named with lowerCamelCase. For indicating that module  b  is submodule of module  a  you can nest them by using namespacing like:  a.b .

    There are two common ways for structuring the modules:

    1. By functionality
    2. By component type

    Currently there's not a big difference, but the first way looks cleaner. Also, if lazy-loading modules is implemented (currently not in the AngularJS roadmap), it will improve the app's performance.

Controllers

  • Do not manipulate DOM in your controllers, this will make your controllers harder for testing and will violate the Separation of Concerns principle. Use directives instead.
  • The naming of the controller is done using the controller's functionality (for example shopping cart, homepage, admin panel) and the substring  Ctrl  in the end.
  • Controllers are plain javascript constructors, so they will be named UpperCamelCase ( HomePageCtrl ,  ShoppingCartCtrl ,  AdminPanelCtrl , etc.).
  • The controllers should not be defined as globals (even though AngularJS allows this, it is a bad practice to pollute the global namespace).
  • Use the following syntax for defining controllers:

    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).

  • Prefer using  controller as  syntax:

    normal<div ng-controller="MainCtrl as main">
       {{ main.title }}
    </div>
    normal
    app.controller('MainCtrl', MainCtrl);
    
    function MainCtrl () {
      this.title = 'Some title';
    }

    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 (like  $scope.$broadcast ). 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:

    function MyCtrl(s) {
      // ...
    }
    
    module.controller('MyCtrl', ['$scope', MyCtrl]);

    which is less readable than:

    function MyCtrl($scope) {
      // ...
    }
    module.controller('MyCtrl', ['$scope', 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.

  • Make the controllers as lean as possible. Abstract commonly used functions into a service.

  • Avoid writing business logic inside controllers. Delegate business logic to a  model , using a service. For example:

    //This is a common behaviour (bad example) of using business logic inside a controller.
    angular.module('Store', [])
    .controller('OrderCtrl', function ($scope) {
    
      $scope.items = [];
    
      $scope.addToOrder = function (item) {
        $scope.items.push(item);//-->Business logic inside controller
      };
    
      $scope.removeFromOrder = function (item) {
        $scope.items.splice($scope.items.indexOf(item), 1);//-->Business logic inside controller
      };
    
      $scope.totalPrice = function () {
        return $scope.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):

    //Order is used as a 'model'
    angular.module('Store', [])
    .controller('OrderCtrl', function (Order) {
    
      $scope.items = Order.items;
    
      $scope.addToOrder = function (item) {
        Order.addToOrder(item);
      };
    
      $scope.removeFromOrder = function (item) {
        Order.removeFromOrder(item);
      };
    
      $scope.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
  • Communicate within different controllers using method invocation (possible when a child wants to communicate with its parent) or  $emit ,  $broadcast  and  $on  methods. The emitted and broadcasted messages should be kept to a minimum.

  • Make a list of all messages which are passed using  $emit ,  $broadcast  and manage it carefully because of name collisions and possible bugs.

    Example:

    // 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
    * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  • When you need to format data encapsulate the formatting logic into a filter and declare it as dependency:

    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

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

    HomeCtrl

    function HomeCtrl() {
     this.bindingValue = 42;
    }

    template.html

    normal<div ng-bind="home.bindingValue"></div>
    normal

Directives

  • Name your directives with lowerCamelCase.
  • Use  scope  instead of  $scope  in your link function. In the compile, post/pre link functions you have already defined arguments which will be passed when the function is invoked, you won't be able to change them using DI. This style is also used in AngularJS's source code.
  • Use custom prefixes for your directives to prevent name collisions with third-party libraries.
  • Do not use  ng  or  ui  prefixes since they are reserved for AngularJS and AngularJS UI usage.
  • DOM manipulations must be done only through directives.
  • Create an isolated scope when you develop reusable components.
  • Use directives as attributes or elements instead of comments or classes, this will make your code more readable.
  • Use  scope.$on('$destroy', fn)  for cleaning up. This is especially useful when you're wrapping third-party plugins as directives.
  • Do not forget to use  $sce  when you should deal with untrusted content.

Filters

  • Name your filters with lowerCamelCase.
  • Make your filters as light as possible. They are called often during the  $digest  loop so creating a slow filter will slow down your app.
  • Do a single thing in your filters, keep them coherent. More complex manipulations can be achieved by piping existing 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.

  • Use camelCase to name your services.

    • UpperCamelCase (PascalCase) for naming your services, used as constructor functions i.e.:

      function MainCtrl($scope, User) {
        $scope.user = new User('foo', 42);
      }
      
      module.controller('MainCtrl', MainCtrl);
      
      function User(name, age) {
        this.name = name;
        this.age = age;
      }
      
      module.factory('User', function () {
        return User;
      });
    • lowerCamelCase for all other services.

  • Encapsulate all the business logic in services. Prefer using it as your  model . For example:

    //Order is the 'model'
    angular.module('Store')
    .factory('Order', function () {
        var add = function (item) {
          this.items.push (item);
        };
    
        var remove = function (item) {
          if (this.items.indexOf(item) > -1) {
            this.items.splice(this.items.indexOf(item), 1);
          }
        };
    
        var total = function () {
          return this.items.reduce(function (memo, item) {
            return memo + (item.qty * item.price);
          }, 0);
        };
    
        return {
          items: [],
          addToOrder: add,
          removeFromOrder: remove,
          totalPrice: total
        };
    });

    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:

    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);
    
  • For session-level cache you can use  $cacheFactory . This should be used to cache results from requests or heavy computations.

  • If given service requires configuration define the service as provider and configure it in the  config  callback like:

    angular.module('demo', [])
    .config(function ($provide) {
      $provide.provider('sample', function () {
        var foo = 42;
        return {
          setFoo: function (f) {
            foo = f;
          },
          $get: function () {
            return {
              foo: foo
            };
          }
        };
      });
    });
    
    var demo = angular.module('demo');
    
    demo.config(function (sampleProvider) {
      sampleProvider.setFoo(41);
    });

Templates

  • Use  ng-bind  or  ng-cloak  instead of simple  {{ }}  to prevent flashing content.
  • Avoid writing complex expressions in the templates.
  • When you need to set the  src  of an image dynamically use  ng-src  instead of  src  with  {{ }}  template.
  • When you need to set the  href  of an anchor tag dynamically use  ng-href  instead of  href  with  {{ }}  template.
  • Instead of using scope variable as string and using it with  style  attribute with  {{ }} , use the directive  ng-style  with object-like parameters and scope variables as values:
<script>
...
$scope.divStyle = {
  width: 200,
  position: 'relative'
};
...
</script>

<div ng-style="divStyle">my beautifully styled div which will work in IE</div>;

Routing

  • Use  resolve  to resolve dependencies before the view is shown.
  • Do not place explicit RESTful calls inside the  resolve  callback. Isolate all the requests inside appropriate services. This way you can enable caching and follow the separation of concerns principle.

i18n

  • For newer versions of the framework (>=1.4.0) use the built-in i18n tools, when using older versions (<1.4.0) use  angular-translate .

Performance

  • Optimize the digest cycle

    • Watch only the most vital variables. When required to invoke the  $digest  loop explicitly (it should happen only in exceptional cases), invoke it only when required (for example: when using real-time communication, don't cause a  $digest  loop in each received message).
    • For content that is initialized only once and then never changed, use single-time watchers like  bindonce  for older versions of AngularJS or one-time bindings in AngularJS >=1.3.0.
    • Make the computations in  $watch  as simple as possible. Making heavy and slow computations in a single  $watch  will slow down the whole application (the  $digest  loop is done in a single thread because of the single-threaded nature of JavaScript).
    • When watching collections, do not watch them deeply when not strongly required. Better use  $watchCollection , which performs a shallow check for equality of the result of the watched expression and the previous value of the expression's evaluation.
    • Set third parameter in  $timeout  function to false to skip the  $digest  loop when no watched variables are impacted by the invocation of the  $timeout  callback function.
    • When dealing with big collections, which change rarely, use immutable data structures.
  • Consider decreasing number of network requests by bundling/caching html template files into your main javascript file, using grunt-html2js / gulp-html2js. See here and here for details. This is particularly useful when the project has a lot of small html templates that can be a part of the main (minified and gzipped) javascript file.

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 pascalockert yanivefraim ericguirbal agnislav
mgechev morizotter pascalockert yanivefraim ericguirbal agnislav
ray7551 mainyaa elfinxx Xuefeng-Zhu rubystream SullyP
ray7551 mainyaa elfinxx Xuefeng-Zhu rubystream SullyP
giacomocusinato susieyy lukaszklis cironunes cavarzan guiltry
giacomocusinato susieyy lukaszklis cironunes cavarzan guiltry
MertSKaan mingchen tornad jmblog kuzzmi mo-gr
MertSKaan mingchen tornad jmblog kuzzmi mo-gr
astalker clbn atodorov apetro valgreens kirstein
astalker clbn atodorov apetro valgreens kirstein
meetbryce dchest gsamokovarov grvcoelho bargaorobalo hermankan
meetbryce dchest gsamokovarov grvcoelho bargaorobalo hermankan
jabhishek jesselpalmer capaj johnnyghost jordanyee nacyot
jabhishek jesselpalmer capaj johnnyghost jordanyee nacyot
mariolamacchia mischkl dwmkerr dreame4 cryptojuice nikshulipa
mariolamacchia mischkl dwmkerr dreame4 cryptojuice nikshulipa
olov vorktanamobay sahat ganchiku kaneshin imaimiami
olov vorktanamobay sahat ganchiku kaneshin imaimiami
dooart thomastuts andela-abankole grapswiz coderhaoxin ntaoo
dooart thomastuts andela-abankole grapswiz coderhaoxin ntaoo
kuzmeig1
kuzmeig1

View static HTML