Global filters: Create global filters using angular.filter() only. Never use local filters inside Controllers/Services
// avoid
function SomeCtrl () {
this.startsWithLetterA = function (items) {
return items.filter(function (item) {
return /^a/i.test(item.name);
});
};
}
angular
.module('app')
.controller('SomeCtrl', SomeCtrl);
// recommended
function startsWithLetterA () {
return function (items) {
return items.filter(function (item) {
return /^a/i.test(item.name);
});
};
}
angular
.module('app')
.filter('startsWithLetterA', startsWithLetterA);
This enhances testing and reusability
Promises: Resolve Controller dependencies in the $routeProvider (or $stateProvider for ui-router), not the Controller itself
// avoid
function MainCtrl (SomeService) {
var _this = this;
// unresolved
_this.something;
// resolved asynchronously
SomeService.doSomething().then(function (response) {
_this.something = response;
});
}
angular
.module('app')
.controller('MainCtrl', MainCtrl);
// recommended
function config ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/main.html',
resolve: {
// resolve here
}
});
}
angular
.module('app')
.config(config);
Controller.resolve property: Never bind logic to the router itself. Reference a resolve property for each Controller to couple the logic
// avoid
function MainCtrl (SomeService) {
this.something = SomeService.something;
}
function config ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/main.html',
controllerAs: 'vm',
controller: 'MainCtrl'
resolve: {
doSomething: function () {
return SomeService.doSomething();
}
}
});
}
// recommended
function MainCtrl (SomeService) {
this.something = SomeService.something;
}
MainCtrl.resolve = {
doSomething: function (SomeService) {
return SomeService.doSomething();
}
};
function config ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/main.html',
controllerAs: 'vm',
controller: 'MainCtrl'
resolve: MainCtrl.resolve
});
}
This keeps resolve dependencies inside the same file as the Controller and the router free from logic
$scope: Use the $emit and $broadcast methods to trigger events to direct relationship scopes only
// up the $scope
$scope.$emit('customEvent', data);
// down the $scope
$scope.$broadcast('customEvent', data);
$rootScope: Use only $emit as an application-wide event bus and remember to unbind listeners
// all $rootScope.$on listeners
$rootScope.$emit('customEvent', data);
Hint: Because the $rootScope is never destroyed, $rootScope.$on listeners aren't either, unlike $scope.$on listeners and will always persist, so they need destroying when the relevant $scope fires the $destroy event
// call the closure
var unbind = $rootScope.$on('customEvent'[, callback]);
$scope.$on('$destroy', unbind);
For multiple $rootScope listeners, use an Object literal and loop each one on the $destroy event to unbind all automatically
var rootListeners = {
'customEvent1': $rootScope.$on('customEvent1'[, callback]),
'customEvent2': $rootScope.$on('customEvent2'[, callback]),
'customEvent3': $rootScope.$on('customEvent3'[, callback])
};
for (var unbind in rootListeners) {
$scope.$on('$destroy', rootListeners[unbind]);
}
One-time binding syntax: In newer versions of Angular (v1.3.0-beta.10+), use the one-time binding syntax {{ ::value }} where it makes sense
// avoid
<h1>{{ vm.title }}</h1>
// recommended
<h1>{{ ::vm.title }}</h1>
Why? : Binding once removes the watcher from the scope's $$watchers array after the undefined variable becomes resolved, thus improving performance in each dirty-check
Consider $scope.$digest: Use $scope.$digest over $scope.$apply where it makes sense. Only child scopes will update
$scope.$digest();
Why? : $scope.$apply will call $rootScope.$digest, which causes the entire application $$watchers to dirty-check again. Using $scope.$digest will dirty check current and child scopes from the initiated $scope
$document and $window: Use $document and $window at all times to aid testing and Angular references
// avoid
function dragUpload () {
return {
link: function ($scope, $element, $attrs) {
document.addEventListener('click', function () {
});
}
};
}
// recommended
function dragUpload () {
return {
link: function ($scope, $element, $attrs, $document) {
$document.addEventListener('click', function () {
});
}
};
}
$timeout and $interval: Use $timeout and $interval over their native counterparts to keep Angular's two-way data binding up to date
// avoid
function dragUpload () {
return {
link: function ($scope, $element, $attrs) {
setTimeout(function () {
//
}, 1000);
}
};
}
// recommended
function dragUpload ($timeout) {
return {
link: function ($scope, $element, $attrs) {
$timeout(function () {
//
}, 1000);
}
};
}
jsDoc: Use jsDoc syntax to document function names, description, params and returns
/**
* @name SomeService
* @desc Main application Controller
*/
function SomeService (SomeService) {
/**
* @name doSomething
* @desc Does something awesome
* @param {Number} x - First number to do something with
* @param {Number} y - Second number to do something with
* @returns {Number}
*/
this.doSomething = function (x, y) {
return x * y;
};
}
angular
.module('app')
.service('SomeService', SomeService);
ng-annotate: Use ng-annotate for Gulp as ng-min is deprecated, and comment functions that need automated dependency injection using /** @ngInject */
/**
* @ngInject
*/
function MainCtrl (SomeService) {
this.doSomething = SomeService.doSomething;
}
angular
.module('app')
.controller('MainCtrl', MainCtrl);
Which produces the following output with the $inject annotation
/**
* @ngInject
*/
function MainCtrl (SomeService) {
this.doSomething = SomeService.doSomething;
}
MainCtrl.$inject = ['SomeService'];
angular
.module('app')
.controller('MainCtrl', MainCtrl);