After reading Google’s AngularJS guidelines, I felt they were a little too incomplete and also guided towards using the Closure library. They also state “We don’t think this makes sense for all projects that use AngularJS, and we’d love to see our community of developers come up with a more general Style that’s applicable to AngularJS projects large and small”, so here goes.
From my experience with Angular, several talks and working in teams, here’s my opinionated styleguide for syntax, building and structuring Angular applications.
Official styleguide repo now on GitHub, all future styleguide updates will be here!
Module definitions
Angular modules can be declared in various ways, either stored in a variable or using the getter syntax. Use the getter syntax at all times (angular recommended).
Bad:
Good:
From these modules we can pass in function references.
Module method functions
Angular modules have a lot of methods, such as
controller
, factory
, directive
,service
and more. There are many syntaxes for these modules when it comes to dependency injection and formatting your code. Use a named function definition and pass it into the relevant module method, this aids in stack traces as functions aren’t anonymous (this could be solved by naming the anonymous function but this method is far cleaner).Bad:
Good:
Define a module once using
angular.module('app', [])
setter, then use theangular.module('app')
getter elsewhere (such as other files).
To avoid polluting the global namespace, wrap all your functions duringcompilation/concatenation inside an IIFE which will produce something like this:
Best:
Controllers
Controllers are classes and can use a
controllerAs
syntax or generic controller
syntax. Use the controllerAs
syntax always as it aids in nested scoping and controller instance reference.controllerAs DOM bindings
Bad:
Good:
Binding these
ng-controller
attributes couples the declarations tightly with our DOM, and also means we can only use that controller for that specific view (there are rare cases we might use the same view with different controllers). Use the router to couple the controller declarations with the relevant views by telling each route
what controller to instantiate.Best:
This avoids using
$parent
to access any parent controllers from a child controller, simple hit the main
reference and you’ve got it. This could avoid things such as$parent.$parent
calls.controllerAs this keyword
The
controllerAs
syntax uses the this
keyword inside controllers instead of $scope
. When using controllerAs
, the controller is infact bound to $scope
, there is a degree of separation.Bad:
You can also use the
prototype
Object to create controller classes, but this becomes messy very quickly as each dependency injected provider needs a reference bound to theconstructor
Object.Bad and Good:
Good for inheritance, bad (verbose) for general use.
If you’re using
prototype
and don’t know why, then it’s bad. If you are using prototype
to inherit from other controllers, then that’s good. For general use, the prototype
pattern can be verbose.Good:
These just show examples of Objects/functions inside Controllers, however we don’t want to put logic in controllers…
Avoid controller logic
Avoid writing logic in Controllers, delegate to Factories/Services.
Bad:
Good:
This maximises reusability, encapsulated functionality and makes testing far easier and persistent.
Services
Services are instantiated and should be class-like also and reference the
this
keyword, keep function style consistent with everything else.Good:
Factory
Factories give us a singleton module for creating service methods (such as communicating with a server over REST endpoints). Creating and returning a bound Object keeps controller bindings up to date and avoids pitfalls of binding primitive values.
Important: A “factory” is in fact a pattern/implementation, and shouldn’t be part of the provider’s name. All factories and services should be called “services”.
Bad:
Good:
We create an Object with the same name inside the function. This can aid documentation as well for comment-generated docs.
Any bindings to primitives are kept up to date, and it makes internal module namespacing a little easier, we can easily see any private methods and variables.
Directives
Any DOM manipulation should take place inside a directive, and only directives. Any code reusability should be encapsulated (behavioural and markup related) too.
DOM manipulation
DOM manipulation should be done inside the
link
method of a directive.Bad:
Good:
Any DOM manipulation should take place inside a directive, and only directives. Any code reusability should be encapsulated (behavioural and markup related) too.
Naming conventions
Custom directives should not be
ng-*
prefixed to prevent future core overrides if your directive name happens to land in Angular (such as when ng-focus
landed, there were many custom directives called this beforehand). It also makes it more confusing to know which are core directives and which are custom.Bad:
Good:
Directives are the only providers that we have the first letter as lowercase, this is due to strict naming conventions in the way Angular translates
camelCase
to hyphenated, sofocusFire
will become <input focus-fire>
when used on an element.Usage restriction
If you need to support IE8, you’ll want to avoid using the comments syntax for declaring where a directive will sit. Really, this syntax should be avoided anyway - there are no real benefits of using it - it just adds confusion of what is a comment and what isn’t.
Bad:
These are terribly confusing.
Good:
Declarative custom elements and attributes are clearest.
You can restrict usage using the
restrict
property inside each directive’s Object. Use E
for element
, A
for attribute
, M
for comment
(avoid) and C
for className
(avoid this too as it’s even more confusing, but plays better with IE). You can have multiple restrictions, such as restrict: 'EA'
.Resolve promises in router, defer controllers
After creating services, we will likely inject them into a controller, call them and bind any new data that comes in. This becomes problematic of keeping controllers tidy and resolving the right data.
Thankfully, using
angular-route.js
(or a third party such as ui-router.js
) we can use a resolve
property to resolve the next view’s promises before the page is served to us. This means our controllers are instantiated when all data is available, which means zero function calls.Bad:
Good:
At this point, our service will internally bind the response of the promise to another Object which we can reference in our “deferred-instantiated” controller:
Good:
We can go one better, however and create a
resolve
property on our own Controllers to couple the resolves with the Controllers and avoid logic in the router.Best:
Route changes and ajax spinners
While the routes are being resolved we want to show the user something to indicate progress. Angular will fire the
$routeChangeStart
event as we navigate away from the page, which we can show some form of loading and ajax spinner, which can then be removed on the $routeChangeSuccess
event (see docs).Avoid $scope.$watch
Using
$scope.$watch
should be avoided unless there are no others options. It’s less performant than binding an expression to something like ng-change
, a list of supported events are in the Angular docs.Bad:
Good:
Project/file structure
One role, one file, rule. Separate all controllers, services/factories, directives into individual files. Don’t add all controllers in one file, you will end up with a huge file that is very difficult to navigate, keeps things encapsulated and bitesize.
Bad:
Keep naming conventions for files consistent, don’t invent fancy names for things, you’ll just forget them.
Good:
Depending on the size of your code base, a “feature-driven” approach may be better to split into functionality chunks.
Good:
Naming conventions and conflicts
Angular provides us many Objects such as
$scope
and $rootScope
that are prefixed with $
. This incites they’re public and can be used. We also get shipped with things such as $$listeners
, which are available on the Object but are considered private methods.
Avoid using
$
or $$
when creating your own services/directives/providers/factories.Bad:
Here we create
$$SomeService
as the definition, not the function name.Good:
Here we create
SomeService
as the definition, and the function name for consistency/stack traces.Minification and annotation
Annotation order
It’s considered good practice to dependency inject Angular’s providers in before our own custom ones.
Bad:
Good:
Minification methods, automate it
Use
ng-annotate
for automated dependency injection annotation, as ng-min
isdeprecated. You can find ng-annotate
here.
With our function declarations outside of the module references, we need to use the
@ngInject
comment to explicitly tell ng-annotate
where to inject our dependencies. This method uses $inject
which is faster than the Array syntax.
Manually specifying the dependency injection arrays costs too much time.
Bad:
Good:
Using the
ng-annotate
keyword @ngInject
to instruct things that need annotating:
Will produce:
That is quite surprising... I have never heard of that before reading your post!
ReplyDeleteIt makes me quite puzzled and angry too!
voyance gratuite en ligne par mail