Angular 1 Style Guide

Angular 1 Style Guide

Angular Team Endorsed

Special thanks to Igor Minar, lead on the Angular team, for reviewing, contributing feedback, and entrusting me to shepherd this guide.

Purpose

Opinionated Angular style guide for teams by @john_papa

If you are looking for an opinionated style guide for syntax, conventions, and structuring Angular applications, then step right in. These styles are based on my development experience with Angular, presentations, Pluralsight training courses and working in teams.

The purpose of this style guide is to provide guidance on building Angular applications by showing the conventions I use and, more importantly, why I choose them.

If you like this guide, check out my Angular Patterns: Clean Code course at Pluralsight which is a companion to this guide.

Angular Patterns: Clean Code

Community Awesomeness and Credit

Never work in a vacuum. I find that the Angular community is an incredible group who are passionate about sharing experiences. As such, Angular expert Todd Motto and I have collaborated on many styles and conventions. We agree on most, and some we diverge. I encourage you to check out Todd’s guidelines to get a sense for his approach and how it compares.

Many of my styles have been from the many pair programming sessions Ward Bell and I have had. My friend Ward has certainly helped influence the ultimate evolution of this guide.

See the Styles in a Sample App

While this guide explains the what, why and how, I find it helpful to see them in practice. This guide is accompanied by a sample application that follows these styles and patterns. You can find the sample application (named modular) here in the modular folder. Feel free to grab it, clone it, or fork it. Instructions on running it are in its readme.

##Translations
Translations of this Angular style guide are maintained by the community and can be found here.

Table of Contents

  1. Single Responsibility
  2. IIFE
  3. Modules
  4. Controllers
  5. Services
  6. Factories
  7. Data Services
  8. Directives
  9. Resolving Promises
  10. Manual Annotating for Dependency Injection
  11. Minification and Annotation
  12. Exception Handling
  13. Naming
  14. Application Structure LIFT Principle
  15. Application Structure
  16. Modularity
  17. Startup Logic
  18. Angular $ Wrapper Services
  19. Testing
  20. Animations
  21. Comments
  22. JSHint
  23. JSCS
  24. Constants
  25. File Templates and Snippets
  26. Yeoman Generator
  27. Routing
  28. Task Automation
  29. Filters
  30. Angular Docs

Single Responsibility

Rule of 1

[Style Y001]
  • Define 1 component per file, recommended to be less than 400 lines of code.

Why?: One component per file promotes easier unit testing and mocking.

Why?: One component per file makes it far easier to read, maintain, and avoid collisions with teams in source control.

Why?: One component per file avoids hidden bugs that often arise when combining components in a file where they may share variables, create unwanted closures, or unwanted coupling with dependencies.

The following example defines the app module and its dependencies, defines a controller, and defines a factory all in the same file.

/* avoid */
angular
    .module('app', ['ngRoute'])
    .controller('SomeController', SomeController)
    .factory('someFactory', someFactory);

function SomeController() {
    }

function someFactory() {
    }

The same components are now separated into their own files.

/* recommended */

// app.module.js
angular
    .module('app', ['ngRoute']);
/* recommended */

// some.controller.js
angular
    .module('app')
    .controller('SomeController', SomeController);

function SomeController() {
    }
/* recommended */

// some.factory.js
angular
    .module('app')
    .factory('someFactory', someFactory);

function someFactory() {
    }

Back to top

Small Functions

[Style Y002]
  • Define small functions, no more than 75 LOC (less is better).

Why?: Small functions are easier to test, especially when they do one thing and serve one purpose.

Why?: Small functions promote reuse.

Why?: Small functions are easier to read.

Why?: Small functions are easier to maintain.

Why?: Small functions help avoid hidden bugs that come with large functions that share variables with external scope, create unwanted closures, or unwanted coupling with dependencies.

Back to top

IIFE

JavaScript Scopes

[Style Y010]
  • Wrap Angular components in an Immediately Invoked Function Expression (IIFE).

Why?: An IIFE removes variables from the global scope. This helps prevent variables and function declarations from living longer than expected in the global scope, which also helps avoid variable collisions.

Why?: When your code is minified and bundled into a single file for deployment to a production server, you could have collisions of variables and many global variables. An IIFE protects you against both of these by providing variable scope for each file.

/* avoid */
// logger.js
angular
    .module('app')
    .factory('logger', logger);

// logger function is added as a global variable
function logger() {
    }

// storage.js
angular
    .module('app')
    .factory('storage', storage);

// storage function is added as a global variable
function storage() {
    }
/**
 * recommended
 *
 * no globals are left behind
 */

// logger.js
(function() {
   
    'use strict';

    angular
        .module('app')
        .factory('logger', logger);

    function logger() {
    }
})();

// storage.js
(function() {
   
    'use strict';

    angular
        .module('app')
        .factory('storage', storage);

    function storage() {
    }
})();
  • Note: For brevity only, the rest of the examples in this guide may omit the IIFE syntax.

  • Note: IIFE’s prevent test code from reaching private members like regular expressions or helper functions which are often good to unit test directly on their own. However you can test these through accessible members or by exposing them through their own component. For example placing helper functions, regular expressions or constants in their own factory or constant.

Back to top

Modules

Avoid Naming Collisions

[Style Y020]
  • Use unique naming conventions with separators for sub-modules.

Why?: Unique names help avoid module name collisions. Separators help define modules and their submodule hierarchy. For example app may be your root module while app.dashboard and app.users may be modules that are used as dependencies of app.

Definitions (aka Setters)

[Style Y021]
  • Declare modules without a variable using the setter syntax.

Why?: With 1 component per file, there is rarely a need to introduce a variable for the module.

/* avoid */
var app = angular.module('app', [
    'ngAnimate',
    'ngRoute',
    'app.shared',
    'app.dashboard'
]);

Instead use the simple setter syntax.

/* recommended */
angular
    .module('app', [
        'ngAnimate',
        'ngRoute',
        'app.shared',
        'app.dashboard'
    ]);

Getters

[Style Y022]
  • When using a module, avoid using a variable and instead use chaining with the getter syntax.

Why?: This produces more readable code and avoids variable collisions or leaks.

/* avoid */
var app = angular.module('app');
app.controller('SomeController', SomeController);

function SomeController() {
    }
/* recommended */
angular
    .module('app')
    .controller('SomeController', SomeController);

function SomeController() {
    }

Setting vs Getting

[Style Y023]
  • Only set once and get for all other instances.

Why?: A module should only be created once, then retrieved from that point and after.

/* recommended */

// to set a module
angular.module('app', []);

// to get a module
angular.module('app');

Named vs Anonymous Functions

[Style Y024]
  • Use named functions instead of passing an anonymous function in as a callback.

Why?: This produces more readable code, is much easier to debug, and reduces the amount of nested callback code.

/* avoid */
angular
    .module('app')
    .controller('DashboardController', function() {
    })
    .factory('logger', function() {
    });
/* recommended */

// dashboard.js
angular
    .module('app')
    .controller('DashboardController', DashboardController);

function DashboardController() {
    }
// logger.js
angular
    .module('app')
    .factory('logger', logger);

function logger() {
    }

Back to top

Controllers

controllerAs View Syntax

[Style Y030]
  • Use the controllerAs syntax over the classic controller with $scope syntax.

Why?: Controllers are constructed, “newed” up, and provide a single new instance, and the controllerAs syntax is closer to that of a JavaScript constructor than the classic $scope syntax.

Why?: It promotes the use of binding to a “dotted” object in the View (e.g. customer.name instead of name), which is more contextual, easier to read, and avoids any reference issues that may occur without “dotting”.

Why?: Helps avoid using $parent calls in Views with nested controllers.

<!-- avoid -->
<div ng-controller="CustomerController">
    {
  { name }}
</div>
<!-- recommended -->
<div ng-controller="CustomerController as customer">
    {
  { customer.name }}
</div>

controllerAs Controller Syntax

[Style Y031]
  • Use the controllerAs syntax over the classic controller with $scope syntax.

  • The controllerAs syntax uses this inside controllers which gets bound to $scope

Why?: controllerAs is syntactic sugar over $scope. You can still bind to the View and still access $scope methods.

Why?: Helps avoid the temptation of using $scope methods inside a controller when it may otherwise be better to avoid them or move the method to a factory, and reference them from the controller. Consider using $scope in a controller only when needed. For example when publishing and subscribing events using $emit, $broadcast, or $on.

/* avoid */
function CustomerController($scope) {
   
    $scope.name = {
   };
    $scope.sendMessage = function() {
    };
}
/* recommended - but see next section */
function CustomerController() {
   
    this.name = {
   };
    this.sendMessage = function() {
    };
}

controllerAs with vm

[Style Y032]
  • Use a capture variable for this when using the controllerAs syntax. Choose a consistent variable name such as vm, which stands for ViewModel.

Why?: The this keyword is contextual and when used within a function inside a controller may change its context. Capturing the context of this avoids encountering this problem.

/* avoid */
function CustomerController() {
   
    this.name = {
   };
    this.sendMessage = function() {
    };
}
/* recommended */
function CustomerController() {
   
    var vm = this;
    vm.name = {
   };
    vm.sendMessage = function() {
    };
}

Note: You can avoid any jshint warnings by placing the comment above the line of code. However it is not needed when the function is named using UpperCasing, as this convention means it is a constructor function, which is what a controller is in Angular.

/* jshint validthis: true */
var vm = this;

Note: When creating watches in a controller using controller as, you can watch the vm.* member using the following syntax. (Create watches with caution as they add more load to the digest cycle.)

<input ng-model="vm.title"/>
function SomeController($scope, $log) {
   
    var vm = this;
    vm.title = 'Some Title';

    $scope.$watch('vm.title', function(current, original) {
   
        $log.info('vm.title was %s', original);
        $log.info('vm.title is now %s', current);
    });
}

Note: When working with larger codebases, using a more descriptive name can help ease cognitive overhead & searchability. Avoid overly verbose names that are cumbersome to type.

<!-- avoid -->
<input ng-model="customerProductItemVm.title">
<!-- recommended -->
<input ng-model="productVm.title">

Bindable Members Up Top

[Style Y033]
  • Place bindable members at the top of the controller, alphabetized, and not spread through the controller code.

    Why?: Placing bindable members at the top makes it easy to read and helps you instantly identify which members of the controller can be bound and used in the View.

    Why?: Setting anonymous functions in-line can be easy, but when those functions are more than 1 line of code they can reduce the readability. Defining the functions below the bindable members (the functions will be hoisted) moves the implementation details down, keeps the bindable members up top, and makes it easier to read.

/* avoid */
function SessionsController() {
   
    var vm = this;

    vm.gotoSession = function() {
   
      /* ... */
    };
    vm.refresh = function() {
   
      /* ... */
    };
    vm.search = function() {
   
      /* ... */
    };
    vm.sessions = [];
    vm.title = 'Sessions';
}
/* recommended */
function SessionsController() {
   
    var vm = this;

    vm.gotoSession = gotoSession;
    vm.refresh = refresh;
    vm.search = search;
    vm.sessions = [];
    vm.title = 'Sessions';

    

    function gotoSession() {
   
      /* */
    }

    function refresh() {
   
      /* */
    }

    function search() {
   
      /* */
    }
}
![Controller Using "Above the Fold"](https://raw.githubusercontent.com/johnpapa/angular-styleguide/master/a1/assets/above-the-fold-1.png)

Note: If the function is a 1 liner consider keeping it right up top, as long as readability is not affected.

/* avoid */
function SessionsController(data) {
   
    var vm = this;

    vm.gotoSession = gotoSession;
    vm.refresh = function() {
   
        /**
         * lines
         * of
         * code
         * affects
         * readability
         */
    };
    vm.search = search;
    vm.sessions = [];
    vm.title = 'Sessions';
}
/* recommended */
function SessionsController(sessionDataService) {
   
    var vm = this;

    vm.gotoSession = gotoSession;
    vm.refresh = sessionDataService.refresh; // 1 liner is OK
    vm.search = search;
    vm.sessions = [];
    vm.title = 'Sessions';
}

Function Declarations to Hide Implementation Details

[Style Y034]
  • Use function declarations to hide implementation details. Keep your bindable members up top. When you need to bind a function in a controller, point it to a function declaration that appears later in the file. This is tied directly to the section Bindable Members Up Top. For more details see this post.

    Why?: Placing bindable members at the top makes it easy to read and helps you instantly identify which members of the controller can be bound and used in the View. (Same as above.)

    Why?: Placing the implementation details of a function later in the file moves that complexity out of view so you can see the important stuff up top.

    Why?: Function declarations are hoisted so there are no concerns over using a function before it is defined (as there would be with function expressions).

    Why?: You never have to worry with function declarations that moving var a before var b will break your code because a depends on b.

    Why?: Order is critical with function expressions

/**
 * avoid
 * Using function expressions.
 */
function AvengersController(avengersService, logger) {
   
    var vm = this;
    vm.avengers = [];
    vm.title = 'Avengers';

    var activate = function() {
   
        return getAvengers().then(function() {
   
            logger.info('Activated Avengers View');
        });
    }

    var getAvengers = function() {
   
        return avengersService.getAvengers().then(function(data) {
   
            vm.avengers = data;
            return vm.avengers;
        });
    }

    vm.getAvengers = getAvengers;

    activate();
}

Notice that the important stuff is scattered in the preceding example. In the example below, notice that the important stuff is up top. For example, the members bound to the controller such as vm.avengers and vm.title. The implementation details are down below. This is just easier to read.

/*
 * recommend
 * Using function declarations
 * and bindable members up top.
 */
function AvengersController(avengersService, logger) {
   
    var vm = this;
    vm.avengers = [];
    vm.getAvengers = getAvengers;
    vm.title = 'Avengers';

    activate();

    function activate() {
   
        return getAvengers().then(function() {
   
            logger.info('Activated Avengers View');
        });
    }

    function getAvengers() {
   
        return avengersService.getAvengers().then(function(data) {
   
            vm.avengers = data;
            return vm.avengers;
        });
    }
}

Defer Controller Logic to Services

[Style Y035]
  • Defer logic in a controller by delegating to services and factories.

    Why?: Logic may be reused by multiple controllers when placed within a service and exposed via a function.

    Why?: Logic in a service can more easily be isolated in a unit test, while the calling logic in the controller can be easily mocked.

    Why?: Removes dependencies and hides implementation details from the controller.

    Why?: Keeps the controller slim, trim, and focused.


/* avoid */
function OrderController($http, $q, config, userInfo) {
   
    var vm = this;
    vm.checkCredit = checkCredit;
    vm.isCreditOk;
    vm.total = 0;

    function checkCredit() {
   
        var settings = {
   };
        // Get the credit service base URL from config
        // Set credit service required headers
        // Prepare URL query string or data object with request data
        // Add user-identifying info so service gets the right credit limit for this user.
        // Use JSONP for this browser if it doesn't support CORS
        return $http.get(settings)
            .then(function(data) {
   
             // Unpack JSON data in the response object
               // to find maxRemainingAmount
               vm.isCreditOk = vm.total <= maxRemainingAmount
            })
            .catch(function(error) {
   
               // Interpret error
               // Cope w/ timeout? retry? try alternate service?
               // Re-reject with appropriate error for a user to see
            });
    };
}
/* recommended */
function OrderController(creditService) {
   
    var vm = this;
    vm.checkCredit = checkCredit;
    vm.isCreditOk;
    vm.total = 0;

    function checkCredit() {
   
       return creditService.isOrderTotalOk(vm.total)
          .then(function(isOk) {
    vm
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值