Top Related Projects
Angular Style Guide: A starting point for Angular development teams to provide consistency through good practices.
AngularJS styleguide for teams
📚 Community-driven set of best practices for AngularJS application development
Principles of Writing Consistent, Idiomatic JavaScript
JavaScript Style Guide
Style guides for Google-originated open-source projects
Quick Overview
The gocardless/angularjs-style-guide is a comprehensive style guide for AngularJS development. It provides best practices, conventions, and guidelines for writing clean, maintainable, and efficient AngularJS code. This guide aims to help developers create consistent and high-quality AngularJS applications.
Pros
- Promotes code consistency across AngularJS projects
- Offers best practices for improved performance and maintainability
- Provides clear examples and explanations for each guideline
- Regularly updated to reflect current AngularJS best practices
Cons
- Focused on AngularJS, which is now considered legacy compared to Angular 2+
- May not cover some newer AngularJS features or techniques
- Some guidelines may be opinionated and not universally agreed upon
- Requires team buy-in and discipline to follow consistently
Code Examples
- Module Definition:
// Recommended
angular.module('app', []);
// Avoid
var app = angular.module('app', []);
This example shows the recommended way to define an AngularJS module, avoiding the use of a variable.
- Controller Definition:
// Recommended
angular
.module('app')
.controller('HomeController', HomeController);
function HomeController() {
var vm = this;
vm.title = 'Home';
}
// Avoid
angular
.module('app')
.controller('HomeController', function($scope) {
$scope.title = 'Home';
});
This example demonstrates the preferred way to define controllers using the controllerAs syntax and avoiding $scope.
- Service Definition:
// Recommended
angular
.module('app')
.factory('userService', userService);
function userService() {
var service = {
getUser: getUser
};
return service;
function getUser() {
// Implementation
}
}
// Avoid
angular
.module('app')
.factory('userService', function() {
return {
getUser: function() {
// Implementation
}
};
});
This example shows the recommended way to define services using the revealing module pattern.
Getting Started
To start using this style guide in your AngularJS project:
-
Clone the repository:
git clone https://github.com/gocardless/angularjs-style-guide.git
-
Read through the README.md file for an overview of the guidelines.
-
Implement the guidelines in your AngularJS project.
-
Consider using a linter like ESLint with an AngularJS plugin to enforce some of these style rules automatically.
-
Share the guide with your team and agree on which guidelines to follow.
Competitor Comparisons
Angular Style Guide: A starting point for Angular development teams to provide consistency through good practices.
Pros of angular-styleguide
- More comprehensive and detailed guidelines
- Regularly updated with newer Angular versions
- Includes explanations and reasoning behind recommendations
Cons of angular-styleguide
- Can be overwhelming for beginners due to its extensive nature
- May require more time to implement fully in a project
Code Comparison
angularjs-style-guide:
angular.module('app', [])
.controller('MainCtrl', function MainCtrl() {
// Controller logic
});
angular-styleguide:
angular
.module('app', [])
.controller('MainController', MainController);
function MainController() {
// Controller logic
}
The angular-styleguide recommends using named functions and separating the function declaration, which can improve readability and maintainability in larger projects. It also suggests using Controller suffix for clarity.
Both style guides aim to improve code quality and consistency in Angular projects. The angularjs-style-guide is more concise and focused on AngularJS (Angular 1.x), while angular-styleguide covers a broader range of topics and is applicable to both AngularJS and newer Angular versions. Developers should choose the guide that best fits their project's needs and team preferences.
AngularJS styleguide for teams
Pros of angularjs-styleguide
- More comprehensive and detailed, covering a wider range of AngularJS topics
- Regularly updated with contributions from the community
- Includes examples of both good and bad practices for clearer understanding
Cons of angularjs-styleguide
- Can be overwhelming for beginners due to its extensive content
- Some recommendations may be opinionated and not universally accepted
- Lacks a clear, concise summary of key points
Code Comparison
angularjs-styleguide:
// recommended
function MainCtrl (SomeService) {
this.doSomething = function () {
SomeService.doSomething();
};
}
angular
.module('app')
.controller('MainCtrl', MainCtrl);
angularjs-style-guide:
// avoid
myApp.controller('MainCtrl', ['$scope', function($scope) {
$scope.doSomething = function() {
// ...
};
}]);
The angularjs-styleguide example demonstrates a more modern, "controller as" syntax with dependency injection, while the angularjs-style-guide shows an older, less preferred approach using $scope.
Both guides aim to improve AngularJS code quality and maintainability, but angularjs-styleguide offers a more comprehensive and up-to-date resource for developers. However, its extensive content may be overwhelming for beginners compared to the more concise angularjs-style-guide.
📚 Community-driven set of best practices for AngularJS application development
Pros of angularjs-style-guide
- More comprehensive coverage of AngularJS best practices
- Regularly updated with contributions from the community
- Includes examples and explanations for each guideline
Cons of angularjs-style-guide
- May be overwhelming for beginners due to its extensive content
- Some guidelines might be opinionated and not universally accepted
Code Comparison
angularjs-style-guide:
// Recommended
function MainCtrl(SomeService) {
this.doSomething = function() {
SomeService.doSomething();
};
}
AngularJS-style-guide:
// Avoid
var MainCtrl = function(SomeService) {
var vm = this;
vm.doSomething = function() {
SomeService.doSomething();
};
};
The angularjs-style-guide recommends using the function
keyword for controller definitions, while the AngularJS-style-guide suggests using a variable assignment with var
. The former approach is generally considered more readable and consistent with ES6 class syntax.
Both style guides aim to improve code quality and maintainability in AngularJS projects. The angularjs-style-guide offers a more detailed and up-to-date resource, while the AngularJS-style-guide provides a simpler, more concise set of guidelines. Developers should choose the guide that best fits their team's needs and experience level.
Principles of Writing Consistent, Idiomatic JavaScript
Pros of idiomatic.js
- More comprehensive, covering a wider range of JavaScript topics
- Language-agnostic principles applicable beyond AngularJS
- Regularly updated with contributions from the community
Cons of idiomatic.js
- Less focused on AngularJS-specific best practices
- May be overwhelming for beginners due to its extensive coverage
- Lacks specific examples for AngularJS applications
Code Comparison
idiomatic.js:
var foo = [
'alpha',
'beta',
'gamma'
];
var bar = {
a: 'alpha',
b: 'beta',
c: 'gamma'
};
angularjs-style-guide:
var foo = [
'alpha',
'beta',
'gamma'
];
var bar = {
a: 'alpha',
b: 'beta',
c: 'gamma'
};
Both style guides advocate for consistent indentation and formatting of arrays and objects. However, idiomatic.js provides more detailed explanations and examples for various JavaScript concepts, while angularjs-style-guide focuses on AngularJS-specific conventions and best practices.
The angularjs-style-guide is more tailored for AngularJS developers, offering concise guidelines for structuring and organizing AngularJS applications. On the other hand, idiomatic.js serves as a more general JavaScript style guide, applicable to a broader range of projects and frameworks.
JavaScript Style Guide
Pros of javascript
- More comprehensive, covering a wide range of JavaScript topics beyond AngularJS
- Regularly updated with modern JavaScript practices and ES6+ features
- Widely adopted in the industry, making it a de facto standard for many teams
Cons of javascript
- Less focused on AngularJS-specific best practices
- May include rules that are not applicable or optimal for AngularJS projects
- Larger and more complex, potentially overwhelming for beginners
Code Comparison
angularjs-style-guide:
// Recommended
function MainCtrl (SomeService) {
this.doSomething = function () {
SomeService.doSomething();
};
}
javascript:
// Recommended
class MainCtrl {
constructor(SomeService) {
this.SomeService = SomeService;
}
doSomething() {
this.SomeService.doSomething();
}
}
The angularjs-style-guide example uses a more traditional AngularJS approach with a function-based controller, while the javascript example demonstrates a class-based approach, which is more aligned with modern JavaScript practices but may require additional setup for use with AngularJS.
Style guides for Google-originated open-source projects
Pros of styleguide
- Comprehensive coverage of multiple programming languages
- Regularly updated and maintained by Google
- Includes guidelines for documentation and readability
Cons of styleguide
- Less specific to AngularJS development
- May be overwhelming for developers focused on a single language or framework
- Some guidelines may conflict with project-specific preferences
Code Comparison
angularjs-style-guide:
// Recommended
function setName(name) {
this.name = name;
}
// Avoid
function setName(name) {
var self = this;
self.name = name;
}
styleguide (JavaScript):
// Recommended
function setName(name) {
this.name = name;
}
// Avoid
const setName = function(name) {
this.name = name;
};
Summary
The styleguide repository offers a broader scope, covering multiple languages and general coding practices. It's well-maintained and provides comprehensive guidelines for various aspects of development. However, it may be less tailored to specific frameworks like AngularJS.
The angularjs-style-guide focuses exclusively on AngularJS development, offering more targeted recommendations for that framework. It may be more suitable for teams working primarily with AngularJS but lacks the breadth of coverage found in the styleguide repository.
Both guides emphasize clean, readable code and provide similar recommendations for common patterns, as seen in the code comparison. The choice between them depends on the project's scope and the development team's needs.
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual CopilotREADME
Angular Style Guide
Table of Contents
- High-level Goals
- Third-party Dependencies
- Directory and File Structure
- Parts of Angular
- Testing
- General Patterns and Anti-patterns
High-level Goals
The principles we use to guide low-level decision making are:
- Prioritise readability.
- Be explicit, not implicit.
- Favour composability over inheritance.
- Think forward â ES6 and Web Components (Angular 2.0).
- Know when to deviate from the style guide.
Example ES6 Application Scaffold
Third-party Dependencies
- Why: SystemJS is an ES6 module loader that enables us to load assets in development and transpile ES6 to ES5 in production.
import {dialogControllerModule} from './dialog.controller';
import template from './dialog.template.html!text';
- Why: Traceur is a transpiler supported by SystemJS. It lets us use ECMAScript 6 features before they are implemented in browsers.
- Why: lodash is a utility library we use throughout our application. Our use of
_.extend
could be replaced by Angularâs built in methodangular.extend
.
- Why: ui-router replaces Angularâs ngRoute module, and is built around states instead of URL routes, enabling nested views. Our use of
$stateProvider
could be replaced by$routeProvider
.
Note: For a complete setup see our Example ES6 Application Scaffold.
Directory and File Structure
We organise our code as follows:
Folder structure
/app
/components
/alert
alert.directive.js
alert.directive.spec.js
alert.template.html
/config
main.config.js
/constants
api-url.constant.js
/routes
/customers
/index
customers-index.template.html
customers-index.route.js
customers-index.controller.js
customers-index.e2e.js
/helpers
/currency
currency-filter.js
currency-filter.spec.js
/unit
/e2e
/services
/creditors
creditors.js
creditors.spec.js
bootstrap.js
main.js
/assets
/fonts
/images
/stylesheets
404.html
index.html
Specs (Unit/E2E)
Keep spec files in the same folder as the code being tested.
Components
Components are encapsulated DOM components. Each component contains all the HTML, CSS, JavaScript, and other dependencies needed to render itself.
Routes
A view, made up of components and unique pieces of UI, that points to a URL. Like components, each route contains all the HTML, CSS, JavaScript, and other dependencies needed to render itself.
Services
Services contain Business logic. For example, $http
abstractions.
Config
Configures Providers. For example, $locationProvider.html5Mode(true);
.
Constants
Although JavaScript does not yet support constants, we run our application through Traceur, which supports const
.
The constant should be named in all uppercase if it's a global constant that will be used across many different functions.
For example, export const API_URL = 'https://api.gocardless.com'
.
If the constant is defined within a single function, it should be in regular camelCase.
Helpers
Pure functions. For example, a currencyFilter
might take in a number and format it for a certain currency. Helpers take input and return output without having any side effects.
Parts of Angular
Rules for using each of the core parts of AngularJS (routes, directives, controllers, modules, and templates).
Routes
Use resolvers to inject data.
Why: The page is rendered only when all data is available. This means views are only rendered once all the required data is available, and you avoid the user seeing any empty views whilst the data is loading.
// Recommended
$stateProvider.state('customers.show', {
url: '/customers/:id',
template: template,
controller: 'CustomersShowController',
controllerAs: 'ctrl',
resolve: {
customer: [
'Customers',
'$stateParams',
function customerResolver(Customers, $stateParams) {
return Customers.findOne({
params: { id: $stateParams.id }
});
}
]
}
});
// Avoid
// Note: Controller written inline for the example
$stateProvider.state('customers.show', {
url: '/customers/:id',
template: template,
controllerAs: 'ctrl',
controller: [
'Customers',
'$stateParams',
function CustomersShowController(Customers, $stateParams) {
const ctrl = this;
Customers.findOne({
params: { id: $stateParams.id }
}).then(function(customers) {
ctrl.customers = customers;
});
}
]
});
Use query parameters to store route state. For example, the current offset
and limit
when paginating.
Why: The current view should be accurately reflected in the URL, which means any page refresh puts the user back in the exact state they were in.
// Recommended
function nextPage() {
const currentOffset = parseInt($stateParams.offset, 10) || 0;
const limit = parseInt($stateParams.limit, 10) || 10;
const nextOffset = currentOffset + limit;
Payments.findAll({
params: { customers: $stateParams.id, limit: limit, offset: nextOffset }
});
}
// Avoid
// Keeping route state in memory only
let currentOffset = 0;
const limit = 10;
function nextPage() {
const nextOffset = currentOffset + limit;
currentOffset = nextOffset;
Payments.findAll({
params: { customers: $stateParams.id, limit: limit, offset: nextOffset }
});
}
Directives
Directive names must only contain a-z
and at least one dash (-
).
Why: Custom elements must have a dash (namespace) to differentiate them from native elements and prevent future component collisions.
<!-- Recommended -->
<dialog-box></dialog-box>
<button click-toggle="isActive"></button>
<!-- Avoid -->
<dialog></dialog>
<button toggle="isActive"></button>
Use element directives when content is injected, else use attribute directives.
Why: Separates responsibility: element directives add content; attribute directives add behaviour; class attributes add style.
<!-- Recommended -->
<alert-box message="Error"></alert-box>
<!-- Replaced with: -->
<alert-box message="Error" class="ng-isolate-scope">
<div class="alert-box">
<span class="alert-box__message">Error</span>
</div>
</alert-box>
<!-- Avoid -->
<p alert-box message="Error"></p>
<!-- Replaced with: -->
<p alert-box message="Error">
<div class="alert-box">
<span class="alert-box__message">Error</span>
</div>
</p>
<!-- Recommended -->
<button prevent-default="click">Submit</button>
<!-- Avoid -->
<prevent-default event="click">Submit</prevent-default>
Use an isolate scope for element directives. Share scope for attribute directives.
Why: Using an isolate scope forces you to expose an API by giving the component all the data it needs. This increases reusability and testability. When using a shared scope for attribute directives you should not write to it or rely on any existing data. Attribute directives should not have an isolate scope because doing so overwrites the current scope.
// Recommended
angular.module('alertListComponentModule', [])
.directive('alertList', [
function alertListDirective() {
return {
restrict: 'E',
scope: {}
};
}
]);
// Avoid
angular.module('alertListComponentModule', [])
.directive('alertList', [
function alertListDirective() {
return {
restrict: 'E'
};
}
]);
// Recommended
angular.module('alertListComponentModule', [])
.directive('alertList', [
function alertListDirective() {
return {
restrict: 'A'
};
}
]);
// Avoid
angular.module('alertListComponentModule', [])
.directive('alertList', [
function alertListDirective() {
return {
restrict: 'A',
scope: {}
};
}
]);
angular.module('alertListComponentModule', [])
.directive('alertList', [
function alertListDirective() {
return {
restrict: 'A',
scope: true
};
}
]);
When using isolate-scope properties, always bindToController
.
Why: It explicitly shows what variables are shared via the controller.
// Recommended
angular.module('alertListComponentModule', [])
.directive('alertList', [
function alertListDirective() {
return {
restrict: 'E',
controller: 'AlertListController',
controllerAs: 'ctrl',
bindToController: true,
template: template,
scope: {}
};
}
]);
// Avoid
angular.module('alertListComponentModule', [])
.directive('alertList', [
function alertListDirective() {
return {
restrict: 'E',
controller: 'AlertListController',
template: template,
scope: {}
};
}
]);
Tear down directives, subscribe to $scope.$on('$destroy', ...)
to get rid of any event listeners or DOM nodes created outside the directive element.
Why: It avoids memory leaks and duplicate event listeners being bound when the directive is re-created.
// Recommended
angular.module('adminExpandComponentModule', [])
.directive('adminExpand', [
'$window',
function adminExpand($window) {
return {
restrict: 'A',
scope: {},
link: function adminExpandLink(scope, element) {
function expand() {
element.addClass('is-expanded');
}
$window.document.addEventListener('click', expand);
scope.$on('$destroy', function onAdminExpandDestroy() {
$window.document.removeEventListener('click', expand);
});
}
};
}
]);
// Avoid
angular.module('adminExpandComponentModule', [])
.directive('adminExpand', [
'$window',
function adminExpand($window) {
return {
restrict: 'A',
scope: {},
link: function adminExpandLink(scope, element) {
function expand() {
element.addClass('is-expanded');
}
$window.document.addEventListener('click', expand);
}
};
}
]);
Anti-Patterns
- Don't rely on jQuery selectors. Use directives to target elements instead.
- Don't use jQuery to generate templates or DOM. Use directive templates instead.
- Don't prefix directive names with
x-
,polymer-
,ng-
.
Controllers
Use controllerAs
syntax.
Why: It explicitly shows what controller a variable belongs to, by writing {{ ctrl.foo }}
instead of {{ foo }}
.
// Recommended
$stateProvider.state('authRequired.customers.show', {
url: '/customers/:id',
template: template,
controller: 'CustomersShowController',
controllerAs: 'ctrl'
});
// Avoid
$stateProvider.state('authRequired.customers.show', {
url: '/customers/:id',
template: template,
controller: 'CustomersShowController'
});
// Recommended
angular.module('alertListComponentModule', [])
.directive('alertList', [
function alertListDirective() {
return {
restrict: 'E',
controller: 'AlertListController',
controllerAs: 'ctrl',
bindToController: true,
template: template,
scope: {}
};
}
]);
// Avoid
angular.module('alertListComponentModule', [])
.directive('alertList', [
function alertListDirective() {
return {
restrict: 'E',
controller: 'AlertListController',
template: template,
scope: {}
};
}
]);
Inject ready data instead of loading it in the controller.
Why:
- 2.1. Simplifies testing with mock data.
- 2.2. Separates concerns: data is resolved in the route and used in the controller.
// Recommended
angular.module('customersShowControllerModule', [])
.controller('CustomersShowController', [
'customer', 'payments', 'mandates',
function CustomersShowController(customer, payments, mandates){
const ctrl = this;
_.extend(ctrl, {
customer: customer,
payments: payments,
mandates: mandates
});
}
]);
// Avoid
angular.module('customersShowControllerModule', [])
.controller('CustomersShowController', [
'Customers', 'Payments', 'Mandates', '$stateParams',
function CustomersShowController(Customers, Payments, Mandates, $stateParams){
const ctrl = this;
Customers.findOne({
params: { id: $stateParams.id }
}).then(function(customers) {
ctrl.customers = customers;
});
Payments.findAll().then(function(payments) {
ctrl.payments = payments;
});
Mandates.findAll().then(function(mandates) {
ctrl.mandates = mandates;
});
}
]);
Extend a controllerâs properties onto the controller.
Why: What is being exported is clear and always done in one place, at the bottom of the file.
// Recommended
angular.module('organisationRolesNewControllerModule', [])
.controller('OrganisationRolesNewController', [
'permissions',
function CustomersShowController(permissions){
const ctrl = this;
function setAllPermissions(access) {
ctrl.form.permissions.forEach(function(permission) {
permission.access = access;
});
}
_.extend(ctrl, {
permissions: permissions,
setAllPermissions: setAllPermissions
});
}
]);
// Avoid
angular.module('organisationRolesNewControllerModule', [])
.controller('OrganisationRolesNewController', [
'permissions',
function CustomersShowController(permissions){
const ctrl = this;
ctrl.permissions = permissions;
ctrl.setAllPermissions = function setAllPermissions(access) {
ctrl.form.permissions.forEach(function(permission) {
permission.access = access;
});
}
}
]);
Only extend the controller with properties used in templates.
Why: Adding unused properties to the digest cycle is expensive.
// Recommended
angular.module('webhooksIndexControllerModule', [])
.controller('WebhooksIndexController', [
'TestWebhooks', 'AlertList', 'webhooks'
function WebhooksIndexController(TestWebhooks, AlertList, webhooks) {
const ctrl = this;
function success() {
AlertList.success('Your test webhook has been created and will be sent shortly');
}
function error() {
AlertList.error('Failed to send test webhook, please try again');
}
function sendTestWebhook(webhook) {
TestWebhooks.create({
data: { test_webhooks: webhook }
}).then(success, error);
}
_.extend(ctrl, {
webhooks: webhooks,
sendTestWebhook: sendTestWebhook
});
}
]);
// Avoid
angular.module('webhooksIndexControllerModule', [])
.controller('WebhooksIndexController', [
'TestWebhooks', 'AlertList', 'webhooks'
function WebhooksIndexController(TestWebhooks, AlertList, webhooks) {
const ctrl = this;
function success() {
AlertList.success('Your test webhook has been created and will be sent shortly');
}
function error() {
AlertList.error('Failed to send test webhook, please try again');
}
function sendTestWebhook(webhook) {
TestWebhooks.create({
data: { test_webhooks: webhook }
}).then(success, error);
}
_.extend(ctrl, {
webhooks: webhooks,
success: success,
error: error,
sendTestWebhook: sendTestWebhook
});
}
]);
Store presentation logic in controllers and business logic in services.
Why:
- 5.1. Simplifies testing business logic.
- 5.2. Controllers are glue code, and therefore require integration tests, not unit tests.
// Recommended
angular.module('webhooksControllerModule', [])
.controller('WebhooksController', [
'TestWebhooks',
function WebhooksController(TestWebhooks) {
const ctrl = this;
function sendTestWebhook(webhook) {
TestWebhooks.create({
data: { test_webhooks: webhook }
}).then(function() {
$state.go('authRequired.organisation.roles.index', null);
AlertList.success('Your test webhook has been created and will be sent shortly');
});
}
_.extend(ctrl, {
sendTestWebhook: sendTestWebhook
});
}
]);
// Avoid
angular.module('webhooksControllerModule', [])
.controller('WebhooksController', [
'$http',
function WebhooksController($http) {
const ctrl = this;
function sendTestWebhook(webhook) {
$http({
method: 'POST',
data: { test_webhooks: webhook },
url: '/test_webhooks'
});
}
_.extend(ctrl, {
sendTestWebhook: sendTestWebhook
});
}
]);
Only instantiate controllers through routes or directives.
Why: Allows reuse of controllers and encourages component encapsulation.
// Recommended
angular.module('alertListComponentModule', [])
.directive('alertList', [
function alertListDirective() {
return {
restrict: 'E',
controller: 'AlertListController',
controllerAs: 'ctrl',
bindToController: true,
template: template,
scope: {}
};
}
]);
<!-- Avoid -->
<div ng-controller='AlertListController as ctrl'>
<span>{{ ctrl.message }}</span>
</div>
Anti-Patterns
- Donât manipulate DOM in your controllers, this will make them harder to test. Use directives instead.
Services and Factories
Treat Service objects like a Class with static methods; don't export services as a single function
Why: easier to see at the definition site and the call site exactly what function the service provides.
// Recommend
angular.module('buildCSVModule', []).factory('BuildCSV', function buildCVS() {
function build() {
// ...
}
return {
build: build,
}
});
// Avoid
angular.module('buildCSVModule', []).factory('BuildCSV', function buildCSV() {
function build() {
// ...
}
return build;
});
Services that take in data and manipulate it should never mutate the original object and return the new object
Why: Avoiding mutation in service objects makes it possible to reason about them from their call site without knowing what they do internally.
// Recommend
let events = [...];
events = EventPresenterService.present(events);
// Avoid
const events = [...];
EventPresenterService.present(events);
// events has been mutated
Prefer ImmutableJS when creating services that manipulate data
Use fromJS
to convert a JS object into an Immutable type, and toJS
at the end to convert back.
Why: ImmutableJS is a fantastic library for taking data and manipulating it without ever mutating.
// Recommend
angular.module('eventPresenterModule', []).factory('EventPresenterService', function eventPresenterService() {
function present(events) {
return Immutable.fromJS(events).map(function(event) {
event.set('some_data', true);
}).map(function(event) {
...
}).toJS();
}
return {
present: present,
}
});
Modules
Name a module using lowerCamelCase
and append Module
.
Why: A module name should be mapped to a file and clearly differentiated from constructors and service objects.
// Recommended
angular.module('usersPasswordEditControllerModule', [])
.controller('UsersPasswordEditController', []);
// Avoid
angular.module('UsersPasswordEditControllerModule', [])
.controller('UsersPasswordEditController', []);
Create one module per file and donât alter a module other than where it is defined.
Why:
- 1.1. Prevents polluting the global scope.
- 1.2. Simplifies unit testing by declaring all dependencies needed to run each module.
- 1.3. Negates necessity to load files in a specific order.
// Recommended
angular.module('usersPasswordEditControllerModule', [])
.controller('UsersPasswordEditController', []);
// Avoid
angular.module('app')
.controller('UsersPasswordEditController', []);
Use ES6 module system and reference other modules using Angular Moduleâs name
property.
Why:
- 2.1. Encapsulates all required files, making unit testing easier and error feedback more specific.
- 2.2. Simplifies upgrading to Angular 2.0, which uses ES6 modules.
// Recommended
import {passwordResetTokensModule} from 'app/services/password-reset-tokens/password-reset-tokens';
import {sessionModule} from 'app/services/session/session';
import {alertListModule} from 'app/components/alert-list/alert-list';
export const usersPasswordEditControllerModule = angular.module('usersPasswordEditControllerModule', [
passwordResetTokensModule.name,
sessionModule.name,
alertListModule.name
]);
// Avoid
import {passwordResetTokensModule} from 'app/services/password-reset-tokens/password-reset-tokens';
import {sessionModule} from 'app/services/session/session';
import {alertListModule} from 'app/components/alert-list/alert-list';
export const usersPasswordEditControllerModule = angular.module('usersPasswordEditControllerModule', [
'passwordResetTokensModule',
'sessionModule',
'alertListModule'
]);
Use relative imports only when importing from the current directory or any of its children. Use absolute paths when referencing modules in parent directories.
Why: Makes it easier to edit directories.
// Current directory: app/services/creditors/
// Recommended
import {API_URL} from 'app/constants/api-url.constant';
import {authInterceptorModule} from 'app/services/auth-interceptor/auth-interceptor';
import {organisationIdInterceptorModule} from 'app/services/organisation-id-interceptor/organisation-id-interceptor';
// Avoid
import {API_URL} from '../../constants/api-url.constant';
import {authInterceptorModule} from '../services/auth-interceptor/auth-interceptor';
import {organisationIdInterceptorModule} from '../services/organisation-id-interceptor/organisation-id-interceptor';
Templates
Use the one-time binding syntax when data does not change after first render.
Why: Avoids unnecessary expensive $watch
ers.
<!-- Recommended -->
<p>Name: {{::ctrl.name}}</p>
<!-- Avoid -->
<p>Name: {{ctrl.name}}</p>
Anti-Patterns
- Donât use
ngInit
â use controllers instead. - Donât use
<div ng-controller="Controller">
syntax. Use directives instead.
Testing
Our applications are covered by two different types of test:
- unit tests, which test individual components by asserting that they behave as expected.
- End to End, or E2E, tests, which load up the application in a browser and interact with it as if a user would, asserting the application behaves expectedly.
To write our tests we use Jasmine BDD and ngMock.
Unit Testing
Every component should have a comprehensive set of unit tests.
Structure of Unit Tests
Tests should be grouped into logical blocks using Jasmine's describe
function. Tests for a function should all be contained within a describe
block, and describe
blocks should also be used to describe different scenarios, or contexts:
describe('#update', function() {
describe('when the data is valid', function() {
it('shows the success message', function() {â¦});
});
describe('when the data is invalid', function() {
it('shows errors', function() {â¦});
});
});
Dependencies
Each component should have its dependencies stubbed in each test.
Inject the dependencies and the components being tested in a beforeEach
function. This encapsulates each test's state, ensuring that they are independent, making them easier to reason about. Tests should never depend on being run in a specific order.
let SomeService;
beforeEach(inject(function($injector) {
SomeService = $injector.get('SomeService');
}));
Controllers
When injecting controllers for a test, use the controller as
syntax:
beforeEach(inject(function($injector, $controller) {
$controller('OrganisationController as ctrl', {â¦});
}));
Always create a new scope to pass into the controller:
let scope;
let organisation = {
name: 'GoCardless'
};
beforeEach(inject(function($injector, $controller) {
scope = $injector.get('$rootScope').$new();
$controller('OrganisationController as ctrl', {
$scope: scope,
organisation: organisation
});
}));
Fixtures
When stubbing an API request using $httpBackend
, always respond with a correctly formatted object. These responses should be saved individually as .json
files and imported using the SystemJS JSON plugin:
import updateFixture from 'app/services/roles/update.fixture.json!json';
$httpBackend.expectPUT('someurl.com').respond(201, updateFixture);
General Patterns and Anti-Patterns
Rules that pertain to our application at large, not a specific part of Angular.
Patterns
Angular abstractions
Use:
$timeout
instead ofsetTimeout
$interval
instead ofsetInterval
$window
instead ofwindow
$document
instead ofdocument
$http
instead of$.ajax
$q
(promises) instead of callbacks
Why: This makes tests easier to follow and faster to run as they can be executed synchronously.
Dependency injection annotations
Always use array annotation for dependency injection and bootstrap with strictDi
.
Why: Negates the need for additional tooling to guard against minification and strictDi
throws an
error if the array (or $inject
) syntax is not used.
// Recommended
angular.module('creditorsShowControllerModule', [])
.controller('CreditorsShowController', [
'creditor', 'payments', 'payouts',
function CreditorsShowController(creditor, payments, payouts) {
const ctrl = this;
_.extend(ctrl, {
creditor: creditor,
payments: payments,
payouts: payouts
});
}
]);
// Avoid
angular.module('creditorsShowControllerModule', [])
.controller('CreditorsShowController',
function CreditorsShowController(creditor, payments, payouts) {
const ctrl = this;
_.extend(ctrl, {
creditor: creditor,
payments: payments,
payouts: payouts
});
});
// Recommended
import {mainModule} from './main';
angular.element(document).ready(function() {
angular.bootstrap(document.querySelector('[data-main-app]'), [
mainModule.name
], {
strictDi: true
});
});
// Avoid
import {mainModule} from './main';
angular.element(document).ready(function() {
angular.bootstrap(document.querySelector('[data-main-app]'), [
mainModule.name
]);
});
Anti-patterns
Donât use the $
name space in property names (e.g. $scope.$isActive = true
).
Why: Makes clear what is an Angular internal.
Don't use globals. Resolve all dependencies using Dependency Injection.
Why: Using DI makes testing and refactoring easier.
Don't do if (!$scope.$$phase) $scope.$apply()
, it means your $scope.$apply()
isn't high enough in the call stack.
Why: You should $scope.$apply()
as close to the asynchronous event binding as possible.
Credits
We referred to lots of resources during the creation of this styleguide, including:
Top Related Projects
Angular Style Guide: A starting point for Angular development teams to provide consistency through good practices.
AngularJS styleguide for teams
📚 Community-driven set of best practices for AngularJS application development
Principles of Writing Consistent, Idiomatic JavaScript
JavaScript Style Guide
Style guides for Google-originated open-source projects
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual Copilot