A sensible & opinionated AngularJS style guide for teams using Service Portal.
Based off of the Angular 1 Style Guide by John Papa and up-to-date with AngularJS 1.5 components.
Note: The original version of Angular is officially called AngularJS; this includes all versions of Angular starting with 1. The current version of Angular is simply called Angular, which includes versions 2 and beyond and will be referred to as such in this guide.
- IIFE
- Modules
- Named Controllers
- Controller as Syntax
- Bindable Members at Top
- $onInit
- Components
- One-way Binding
- Defer Logic to Services
- Small Functions
Where possible, wrap components in an (IIFE) Immediately Invoked Function Expression. This removes variables from the global namespace, thus preventing variable collisions and providing an enclosed scope.
(function() {
/* */
})();
Set a module only once in its own file. Why should I do this?
John Papa answers this best in his style guide.
Separates configuration from module definition, components, and active code. Provides an identifiable place to set configuration for a module.
Do this to define a module.
angular.module('pe-timeline', []);
Get instances of the module this way.
angular.module('pe-timeline');
Read more on the topic, from John Papa.
https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md#modules-1
Use named controllers instead of anonymous functions. Why named controllers?
- easier to debug
- creates more readable code
Also, be sure to append the controller name with the suffix Controller. This is a good practice that is commonly used and is descriptive.
function PeopleCardController() {
var c = this;
}
The platform provides you with anonymous functions by default when creating widgets, but if possible try to avoid this.
function() {
var c = this;
}
The Controllers as Syntax was introduced in AngularJS 1.2. Use this syntax instead of $scope
inside your controller. Why Controller as?
- it is a common and best practice
- removes scope inheritance issues
- eliminates having to inject
$scope
as a dependency $scope
is phased out in Angular
Every time you create a new widget the platform provides this syntax for you by default, which is nice.
function() {
var c = this;
c.isVisible = true;
}
Try to avoid this, if possible.
function($scope) {
$scope.isVisible = true;
}
Place bindable members at the top of the context in alphabetical order. This makes it easy to read and at a glance you know exactly which members are bound to the view and where to find the implementation details.
function FeedbackCardController() {
var c = this;
c.closeSurvey = closeSurvey;
c.setRating = setRating;
function closeSurvey() {
/* */
}
function setRating() {
/* */
}
}
AngularJS 1.5 brings the $onInit
method. This is a good place to put initialization code for your controller. From the documentation:
Called on each controller after all the controllers on an element have been constructed and had their bindings initialized (and before the pre & post linking functions for the directives on this element).
function TabSelectionController() {
var c = this;
c.$onInit = function() {
c.selection = 1;
c.isVisible = false;
};
}
Another great use case for utilizing $onInit
, is when you need a place to fire off an immediately invoked function.
c.$onInit = function() {
activateCharts();
setDefaults();
};
AngularJS 1.5 brings the new .component()
helper method, which allows developers to write in a modern Angular style and makes upgrading to the new framework an easier task. Here are the advantages from the AngularJS documentation:
- simpler configuration than plain directives
- promote sane defaults and best practices
- optimized for component-based architecture
- writing component directives will make it easier to upgrade to Angular
(function() {
'use strict';
var hamburgerMenu = {
template: [
'<button type="button" data-target="#nav-bar">',
'<span class="sr-only">${Toggle nav}</span>',
'<span class="icon-bar"></span>',
'<span class="icon-bar"></span>',
'<span class="icon-bar"></span>',
'</button>'
].join('')
};
angular
.module('sec-ops')
.component('hamburgerMenu', hamburgerMenu);
})();
AngularJS 1.3 delivered a new performance enhancing feature called one-time binding. From the AngularJS documentation:
One-time expressions will stop recalculating once they are stable, which happens after the first digest if the expression result is a non-undefined value.
<div class="panel-heading">{{::options.title}}</div>
The syntax can easily be applied inside an ng-repeat as well:
<ul>
<li ng-repeat="user in ::c.users"></li>
</ul>
Note: Be careful using one-way bindings in areas where the data could fluctuate in the future.
Defer controller logic to services or factories. Why you say?
- keeps the controller focused and lean
- promotes reuse via dependency injection
- reduces repetitive controller logic
- ideal for unit testing and mocking
Create a service.
(function() {
'use strict';
function eventService() {
var service = {
getInitialEvents: getInitialEvents
};
return service;
function getInitialEvents() {
/* */
}
}
})();
Simply inject that service into the controller.
function TimelineController(eventService) {
var c = this;
c.$onInit = function() {
c.initialEvents = eventService.getInitialEvents();
};
}
Read more on the topic, from the man John Papa.
Write small functions; less than 20 lines of code is ideal. Abstract large functions to smaller ones. Why?
- produces more readable code
- easier to maintain
- promotes code reuse
Note: Widget Server script abstraction can easily be achieved using methods from Script includes. Read more on Script includes from the ServiceNow documentation.