The document discusses several best practices for writing cleaner and more opinionated AngularJS code, including:
1) Defining modules and avoiding polluting the global namespace.
2) Using dependency injection properly to support minification.
3) Adding controllers to views using the "controller as" syntax for better scoping.
4) Delegating logic to services to maximize reusability.
2. var app = angular.module('app', []);
app.controller('MyCtrl', function () {
…
});
function MainCtrl () {
…
}
angular
.module('app', [])
.controller('MainCtrl', MainCtrl);
Better
(function () {
angular.module('app', []);
function MainCtrl () {
}
angular
.module('app')
.controller('MainCtrl', MainCtrl);
})();
BestDefining a module
• aids in stack traces as functions aren't anonymous • avoids polluting the global namespace
3. Better
Better #2Dependency injection
• Using the array syntax to declare dependencies works with minification
function MainCtrl(anyVariableName, $q) {
}
MainCtrl.$inject = ['$http',‘$q’];
angular
.module('app', [])
.controller('MainCtrl', MainCtrl);
var app = angular.module('app', []);
app.controller('MainCtrl', [ '$http', '$q', function($http, $q){
…
});
var app = angular.module('app', []);
app.controller('MainCtrl', function ($http, $q) {
…
});
4. <div ng-controller="MainCtrl">
{{ someObject }}
</div>
<div ng-controller="MainCtrl as main">
{{ main.someObject }}
</div>
Better
<div>
{{ main.someObject }}
</div>
!
function config ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/main.html',
controller: 'MainCtrl',
controllerAs: 'main'
});
}
angular
.module('app')
.config(config);
BestAdding controller to a view
• Controllers are classes
• aids in nested scoping and controller instance reference.
• avoids tight coupling ie., reusable view with different controllers
• avoids using $parent to access any parent controllers from a child controller
5. function MainCtrl ($scope) {
$scope.someObject = {};
$scope.doSomething = function () {
};
}
angular
.module('app')
.controller('MainCtrl', MainCtrl);
function MainCtrl ($scope) {
this.someObject = {};
this._$scope = $scope;
}
MainCtrl.prototype.doSomething = function () {
// use this._$scope
};
angular
.module('app')
.controller('MainCtrl', MainCtrl);
Slightly better
function MainCtrl () {
this.someObject = {};
this.doSomething = function () {
};
}
angular
.module('app')
.controller('MainCtrl', MainCtrl);
function config ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/main.html',
controller: 'MainCtrl',
controllerAs: 'main'
});
}
Betterthis and $scope
• Good for inheritance
• controllerAs syntax uses this keyword inside controllers instead of $scope.
• When using controllerAs, the controller is infact bound to $scope, but there is a degree of separation.
6. function SomeService () {
}
angular
.module('app')
.factory('$$SomeService', SomeService);
• $ incites they’re public and can be used.
• $scope and $rootScope
• $$ are considered private methods.
• $$listeners, which are available on the Object
function SomeService () {
}
angular
.module('app')
.factory('SomeService', SomeService);
Better
• Avoid using two different names for the Service definition, and the function name for consistency
and stack tracing.
Naming services/directives/providers/factories
7. <input ng-model=“myModel">
function MainCtrl(){
$scope.$watch('myModel', function () {
// go
});
}
<input ng-model="myModel" ng-change="callback">
function MainCtrl(){
$scope.callback = function () {
// go
};
}
Better
• Avoid $scope.$watch unless there are no others options
• less performant than binding an expression to something like ng-change
Watching data change
8. function MainCtrl () {
this.doSomething = function () {
};
}
angular
.module('app')
.controller('MainCtrl', MainCtrl);
function MainCtrl (SomeService) {
this.doSomething = SomeService.doSomething;
}
angular
.module('app')
.controller('MainCtrl', MainCtrl);
Better
• delegating logic to Factories/Services
• maximises reusability
Controller logic
9. function AnotherService () {
var someValue = '';
var someMethod = function () {
};
return {
someValue: someValue,
someMethod: someMethod
};
}
angular
.module('app')
.factory('AnotherService',AnotherService);
function AnotherService () {
var AnotherService = {};
AnotherService.someValue = ‘’;
AnotherService.someMethod = function () {
};
return AnotherService;
}
angular
.module('app')
.factory('AnotherService',AnotherService);
Better
• makes internal module namespacing a little easier
• easy to read any private methods and variables
Factory
10. function MainCtrl (SomeService) {
this.makeActive = function (elem) {
$(elem).addClass(‘test’);
};
}
angular
.module('app')
.controller('MainCtrl', MainCtrl);
function SomeDirective (SomeService) {
return {
restrict: 'EA',
template: [
'<a href="" class="myawesomebutton" ng-transclude>',
'<i class="icon-ok-sign"></i>',
'</a>'
].join(''),
link: function ($scope, $element, $attrs) {
// DOM manipulation/events here!
$element.on('click', function () {
$(this).addClass('test');
});
}
};
}
angular
.module('app')
.directive('SomeDirective', SomeDirective);
Better
• Any DOM manipulation should take place inside a directive, and only directives
DOM manipulation
11. If you are writing $(element) somewhere in your
controller, its a indication that you need a directive.
12. function ngFocus (SomeService) {
return {};
}
angular
.module('app')
.directive('ngFocus', ngFocus);
function focusFire (SomeService) {
return {};
}
angular
.module('app')
.directive('focusFire', focusFire);
Better
• Custom directives should not be ng-* prefixed to prevent future core overrides
Naming a directive
13. <!-- directive: my-directive -->
<div class="my-directive"></div>
Followed by adding restrict usage using the restrict property inside each directive's Object.
• E for element
• A for attribute
• M for comment (avoid)
• C for className (avoid this too as it's even more confusing, but plays better with IE).
• Multiple restrictions, such as restrict: 'EA'.
BetterDirective usage restriction
<my-directive></my-directive>
<div my-directive></div>
14. function MainCtrl (SomeService) {
var self = this; // unresolved
self.something;
// resolved asynchronously
SomeService.doSomething().then(function (response) {
self.something = response;
});
}
angular
.module('app')
.controller('MainCtrl', MainCtrl);
function config ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/main.html',
resolve: {
doSomething: function (SomeService) {
return SomeService.doSomething();
}
}
});
}
function MainCtrl (SomeService) {
// resolved!
this.something = SomeService.something;
}
angular..module(‘app').controller('MainCtrl', MainCtrl);
angular.module(‘app').config(config);
Better
// config with resolve pointing to relevant controller
function config ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/main.html',
controller: 'MainCtrl',
controllerAs: 'main',
resolve: MainCtrl.resolve
});
}
// controller as usual
function MainCtrl (SomeService) {
// resolved!
this.something = SomeService.something;
}
// create the resolved property
MainCtrl.resolve = {
doSomething: function (SomeService) {
return SomeService.doSomething();
}
};
angular
.module(‘app')
.controller('MainCtrl', MainCtrl)
.config(config);
BestInit data for a view
• keeping router tidy
• progress. indicator using the $routeChangeStart event
• keeping controller tidy