There are many good styleguides already available for Angular: here and here So, why do we need another one?
This styleguide is different in that it is focused on teaching & learning Angular. The syntax and structures advocated by other styleguides are great if you're already an Angular guru with plenty of experience, but can be extremely difficult for students new to Angular, with varying levels of JS knowledge to understand. When we teach we generally increase complexity over time. This guide aims to set guidelines to ensure that syntax across lessons is the same, while slowly introducing additional syntactical complexity.
tldr; This style-guide is aimed at students new to Angular, not angular professionals.
These guidelines are suggested for all code given to students.
Use Controller as syntax, which promotes the demystifying use of dot syntax in HTML to indicate where variables come from. This also means it is possible to safely use scalars on the controller. You must also avoid the use of $scope
and use this
instead in your controllers.
Avoid:
<div ng-controller="CustomerController">
{{ name }}
</div>
Recommended:
<div ng-controller="CustomerController as customer">
{{ customer.name }}
</div>
Declare named functions for controllers and other components.
Avoid:
angular
.module('app', [])
.controller('MainController', function MainController () {
})
.service('SomeFactory', function SomeFactory () {
});
Recommended:
angular
.module('app', [])
.controller('MainController', MainController)
.factory('SomeFactory', SomeFactory);
function MainController () {
}
function SomeFactory () {
}
In general multi-line dot-chaining is more prone to error, but since we're also avoiding in-line call-backs, the issue should be greatly reduced and overall readability should remain high. This also follows what other style-guides suggest.
Avoid:
var app = angular.module('app', []);
app.controller('MainController', function() {
});
app.factory('SomeFactory', function() {
});
Recommended:
angular
.module('app', [])
.controller('MainController', MainController)
.factory('SomeFactory', SomeFactory);
The context of this
could be changed in a function within a controller. Capturing in another name avoids this issue. Note how this rule works with controller-as.
While many students will never run into an issue using just this
in their controllers; this rule is trivial and might help them on projects.
Avoid:
function PostsController() {
this.title = 'Some Title';
}
Prefer:
function PostsController() {
var vm = this;
vm.title = 'Some Title';
}
Use mapController
rather than map
. Use phoneService
rather than phone
Some style guides use UpperCamelCase such as DoAwesomeController
. This is primarily in preparation for ES6 and classes. While not absolutely necessary, being consistent about this keeps it clear.
Avoid:
function libraryController() { }
function authorDirective() { }
Prefer:
function LibraryController() { }
function AuthorDirective() { }
Use mapController
not mapCtrl
.
The syntax should be {feature}.{component}.js
or logger.service.js
, library.controller.js
.
Use these guidelines when introducing and working with directives.
In general DOM manipulations should be done in directives not controllers or services. This excludes built-ins such as ngShow
, ngHide
, angular animations and templates. CSS and animations can also be used independently.
Remember to specify controllerAs
in directives.
It's also extremely common to use vm
as the controllerAs
name; using another name may be disambiguating for students.
Avoid:
function dragUploadDirective () {
var directive = {
controller: uploadController,
scope: {
city: '@'
},
template: '<div>{{prop}}</div>'
};
return directive;
}
Prefer:
function dragUploadDirective () {
var directive = {
controllerAs: 'uploads', // note: often called 'vm' in the wild
controller: uploadController,
scope: {
city: '@'
},
template: '<div>{{uploads.prop}}</div>'
};
return directive;
}
Controllers should always be outside the directive. One-liners may be OK, but in general separate functions.
Avoid:
function flowDirective() {
var directive = {
controller: ['$http', function($http) {
// ...
}],
controllerAs: 'vm',
....
}
return directive;
}
}
Prefer:
function flowDirective() {
var directive = {
controller: flowSourceController,
controllerAs: 'vm',
....
}
return directive;
}
}
flowSourceController.$inject = ['$http']
function flowSourceController($http) { }
This could conflict with newer versions.
Avoid:
<div ng-upload>
Prefer:
<div drag-upload>
Custom elements prefixed with data-
won't pass validators. (data- only applies to attributes.)
Note: All custom elements must contain a dash.
Avoid:
<data-fancy-calendar>
Prefer:
<fancy-calendar>
Use these guidelines when introducing factories and services.
Use Services. Services and Factories are exceedingly similar and the differences are very tricky. We don't need to teach both. Services are closer to the way we teach controllers and should therefore be easier for students. They also look similar to the way we teach constructors. 🌻
This does not conform with John Papa's styleguide. which recommends Factories over Services, but Services are more similar to our controller style.
Factory Pattern - Avoid:
// factory
function someFactory(){
var dataObj = {};
dataObj.doSomething = function(){
//...
}
return dataObj;
}
Service Pattern - Prefer:
// service
function someService(){
this.doSomething = function(){
//…
}
}
Calling a Factory a Service or naming it someService
is confusing. The difference between the two is already quite confusing without mixing up the terminology.
Use these rules when working with MEAN or Rails+Angular stacks or anywhere else where you might encounter minification. They can be introduced after students have gained some familiarity with Angular.
Use $inject
vs. inline annotation.
This has better readability and lower likelihood of syntax errors. Try to keep the $inject
call directly above the function it refers to. You can also align the functions parameters with the injected strings to make this more obvious.
- This rule is a good one to follow from the beginning. Students can get used to seeing it every time.
Avoid:
angular
.module('app')
.controller('PhoneController', ['$location', '$routeParams', PhoneController]);
function PhoneController($location, $routeParams) {...}
Prefer:
angular
.module('app')
.controller('PhoneController', PhoneController);
PhoneController.$inject = ['$location', '$routeParams'];
function PhoneController($location, $routeParams) {...}
Also preferred:
// aligned
PhoneController.$inject = ['$location', '$routeParams'];
function PhoneController( $location, $routeParams ) {...}
For a comparison see the official angular tutorial
If you're following the above use of $inject
ng-annotate is unnecessary. However, you should consider using ng-strict-di
to alert you to missing annotations. Reference
Prefer:
<div ng-app="ngAppStrictDemo" ng-strict-di>
These rules should be mentioned at some point, but not right away.
Mention that data-
can be pre-pended to ng-
attributes. These pass HTML validators and may help students to realize that ng-
is not magic.
This:
<div ng-controller="MainController as mainCtrl">
Can just as easily be:
<div data-ng-controller="MainController as mainCtrl">
Since we're using controller-as and this
in our controllers, $scope
will only rarely be used. However, students are going to come across blogs, older code and stackoverflow posts that use $scope
frequently. We should mention how this works, at least in writing, but not right away.
Note: when using $scope one should always pass objects, not scalars. Use
$scope.obj = {}
rather than$scope.foo = 'adsf'