0% found this document useful (0 votes)
288 views154 pages

File Structure and Best Practices For AngularJS

The document discusses best practices for structuring AngularJS applications. It recommends a modular structure with components separated into folders for core functionality, shared directives, and individual sections. This improves code maintainability, scalability, debugging, and testing. Key aspects include separating files by component, abstracting reusable pieces into shared directives, and organizing routes and assets logically.

Uploaded by

pruebablack
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
Download as pdf or txt
0% found this document useful (0 votes)
288 views154 pages

File Structure and Best Practices For AngularJS

The document discusses best practices for structuring AngularJS applications. It recommends a modular structure with components separated into folders for core functionality, shared directives, and individual sections. This improves code maintainability, scalability, debugging, and testing. Key aspects include separating files by component, abstracting reusable pieces into shared directives, and organizing routes and assets logically.

Uploaded by

pruebablack
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
Download as pdf or txt
Download as pdf or txt
You are on page 1/ 154

#ABetter Structure and

Foundation
Lets get to best practices and what you should be doing to build
scalable and maintainable AngularJS apps that your coworkers will
love you for. An ideal AngularJS app structure should be
modularized into very specific functions. We also want to take
advantage of the wonderful AngularJS directives to further
compartmentalize our apps. Take a look at a sample directory
structure below:
index.html

This directory structure is much harder to read and understand from


the get go. A newcomer to Angular may be completely turned off by
this complex approach, and that is why you see tutorials and
examples in Angular following the simpler directory structure found
in examples earlier. Lets dive into the directory structure above and
see whats going on here.

INDEX.HTML
The index.html lives at the root of front-end structure.
The index.html file will primarily handle loading in all the libraries and
Angular elements.

ASSETS FOLDER
The assets folder is also pretty standard. It will contain all the assets
needed for your app that are not related your AngularJS code. There
are many great ways to organize this directory but they are out of
scope for this article. The example above is good enough for most
apps.

APP FOLDER
This is where the meat of your AngularJS app will live. We have two
subfolders in here and a couple JavaScript files at the root of the
folder. The app.module.js file will handle the setup of your app, load in
AngularJS dependencies and so on. The app.route.js file will handle
all the routes and the route configuration. After that we have two
subfolders componentsand shared. Lets dive into those next.

COMPONENTS FOLDER
The components folder will contain the actual sections for your Angular
app. These will be the static views ,directives and services for that
specific section of the site (think an admin users section, gallery
creation section, etc). Each page should have its own subfolder with
its own controller, services, and HTML files.

Each component here will resemble a mini-MVC application by


having a view, controller and potentially services file(s). If the
component has multiple related views, it may be a good idea to
further separate these files into views, controllers, services
subfolders.

This can be seen as the simpler folder structure shown earlier in this
article, just broken down into sections. So you could essentially
think of this as multiple mini Angular applications inside of
your giant Angular application.

SHARED FOLDER
The shared folder will contain the individual features that your app will
have. These features will ideally be directives that you will want to
reuse on multiple pages.

Features such as article posts, user comments, sliders, and others


should be crafted as AngularJS Directives. Each component here
should have its own subfolder that contains the directive JavaScript
file and the template HTML file.

In some instances, a directive may have its own services JavaScript


file, and in the case that it does it should also go into this subfolder.

This allows us to have definitive components for our site so that a


slider will be a slider across the site. You would probably want to
build it so that you could pass in options to extend it. For example,
you could have:

<!-- user a slider directive to loop over something -->

<slider id="article-slider" ng-repeat="picture in pictures" size="large"


type="square">
</slider>

Now this slider is accessible from any part of our site so were not
reinventing the wheel. We also just have to change it in one place,
the shared folder and it will update sitewide.

#BestPractices (For Huuuuge


Apps)
If you are developing a really large application in AngularJS, you will
want to go even further and modularize your app. Here are some
additional tips on how to accomplish this.

MODULARIZE THE HEADER AND FOOTER


A good practice here would be to create a Core subfolder under
components, and then a subfolder for the Header and Footer and
any additional components that will be shared across many pages.

MODULARIZE THE ROUTES


In the structure above we didnt do this, but another good practice for
very large apps is to separate the routes into separate files. For
example you might add a blogRoutes.js file in
the /views/blog/ subfolder and there include only the routes relevant
to the blog such as /blog/:slug, /blog/:slug/edit, blog/tags:/tags, etc.

DONT FORGET TO MINIFY


If you do decide to opt in and build your AngularJS apps in a
modularized fashion, be sure to concatenate and minify your code
before going into production. There are many great extensions for
both Grunt and Gulp that will help with this so dont be afraid to
split code up as much as you need.

You may not want to necessarily have just one giant .js file for your
entire app, but concatenating your app into a few logical files like:

app.js (for app initialization, config and routing)

services.js (for all the services)


This will be greatly beneficial for reducing initial load times of your
app.

If you need some more tips on minifying, check out our


guide: Declaring AngularJS Modules For Minification

KEEP THE NAMES CONSISTENT


This is more of a general tip, but this will save you a headache in the
future, when writing components and you need multiple files for the
component, try to name them in a consistent pattern. For
example, blogView.html, blogServices.js, blogController.js.
#Benefits
of the Modularized
Approach
The example above shows a modularized approach to building
AngularJS. The benefits of this approach include:

CODE MAINTAINABILITY
Follow the approach above will logically compartmentalize your apps
and you will easily be able to locate and edit code.

SCALABLE
Your code will be much easier to scale. Adding new directives and
pages will not add bloat to existing folders. Onboarding new
developers should also be much easier once the structure is
explained. Additionally, with this approach, you will be able to drop
features in and out of your app with relative ease so testing new
functionality or removing it should be a breeze.

DEBUGGING
Debugging your code will be much easier with this modularized
approach to app development. It will be easier to find the offending
pieces of code and fix them.

TESTING
Writing test scripts and testing modernized apps is a whole lot easier
then non-modularized ones.

#In Conclusion
To conclude, this article covered some of the best practices in
regards to structuring an AngularJS app. It is easy to ignore good
practices in order to save time upfront. We all have a tendency to
just want to start writing code. Sometimes this passion can hurt us in
the long run when our awesome apps grow and become popular and
then were stuck rewriting or even worse maintaining badly thought
out code. I hope this article had some helpful tips.

I plan on building a barebones AngularJS application structure that


should follow the best practices outlined in this article that will help
you get started building Angular apps quickly and efficiently. Keep a
lookout for that in the coming weeks. Stay tuned for Part 2 where we
put these concepts into practice!

In the meantime, be sure to check out John Papas AngularJS Style


Guidefor additional tips on AngularJS best practices and while youre
add it give Todd Mottos AngularJS Guide a look too
Table of Contents
1. Single Responsibility
2. IIFE
3. Modules
4. Controllers
5. Services
6. Factories
7. Data Services
8. Directives
9. Resolving Promises
10. Manual Annotating for Dependency Injection
11. Minification and Annotation
12. Exception Handling
13. Naming
14. Application Structure LIFT Principle
15. Application Structure
16. Modularity
17. Startup Logic
18. Angular $ Wrapper Services
19. Testing
20. Animations
21. Comments
22. JSHint
23. JSCS
24. Constants
25. File Templates and Snippets
26. Yeoman Generator
27. Routing
28. Task Automation
29. Filters
30. Angular Docs

Single Responsibility
Rule of 1

[Style Y001]

Define 1 component per file, recommended to be less than 400 lines of code.

Why?: One component per file promotes easier unit testing and mocking.

Why?: One component per file makes it far easier to read, maintain, and
avoid collisions with teams in source control.

Why?: One component per file avoids hidden bugs that often arise when
combining components in a file where they may share variables, create
unwanted closures, or unwanted coupling with dependencies.

The following example defines the app module and its dependencies, defines
a controller, and defines a factory all in the same file.
/* avoid */
angular
.module('app', ['ngRoute'])
.controller('SomeController', SomeController)
.factory('someFactory', someFactory);

function SomeController() { }

function someFactory() { }

The same components are now separated into their own files.

/* recommended */

// app.module.js
angular
.module('app', ['ngRoute']);
/* recommended */

// some.controller.js
angular
.module('app')
.controller('SomeController', SomeController);

function SomeController() { }
/* recommended */

// some.factory.js
angular
.module('app')
.factory('someFactory', someFactory);

function someFactory() { }
Back to top

Small Functions

[Style Y002]

Define small functions, no more than 75 LOC (less is better).

Why?: Small functions are easier to test, especially when they do one thing
and serve one purpose.

Why?: Small functions promote reuse.

Why?: Small functions are easier to read.

Why?: Small functions are easier to maintain.

Why?: Small functions help avoid hidden bugs that come with large
functions that share variables with external scope, create unwanted closures,
or unwanted coupling with dependencies.

Back to top

IIFE

JavaScript Scopes

[Style Y010]

Wrap Angular components in an Immediately Invoked Function Expression


(IIFE).

Why?: An IIFE removes variables from the global scope. This helps prevent
variables and function declarations from living longer than expected in the
global scope, which also helps avoid variable collisions.

Why?: When your code is minified and bundled into a single file for
deployment to a production server, you could have collisions of variables
and many global variables. An IIFE protects you against both of these by
providing variable scope for each file.
/* avoid */
// logger.js
angular
.module('app')
.factory('logger', logger);

// logger function is added as a global variable


function logger() { }

// storage.js
angular
.module('app')
.factory('storage', storage);

// storage function is added as a global variable


function storage() { }
/**
* recommended
*
* no globals are left behind
*/

// logger.js
(function() {
'use strict';

angular
.module('app')
.factory('logger', logger);

function logger() { }
})();

// storage.js
(function() {
'use strict';

angular
.module('app')
.factory('storage', storage);

function storage() { }
})();

Note: For brevity only, the rest of the examples in this guide may omit the
IIFE syntax.

Note: IIFE's prevent test code from reaching private members like regular
expressions or helper functions which are often good to unit test directly on
their own. However you can test these through accessible members or by
exposing them through their own component. For example placing helper
functions, regular expressions or constants in their own factory or constant.
Back to top

Modules

Avoid Naming Collisions

[Style Y020]

Use unique naming conventions with separators for sub-modules.

Why?: Unique names help avoid module name collisions. Separators help
define modules and their submodule hierarchy. For example app may be
your root module while app.dashboard and app.users may be modules that
are used as dependencies of app.

Definitions (aka Setters)

[Style Y021]

Declare modules without a variable using the setter syntax.

Why?: With 1 component per file, there is rarely a need to introduce a


variable for the module.

/* avoid */
var app = angular.module('app', [
'ngAnimate',
'ngRoute',
'app.shared',
'app.dashboard'
]);

Instead use the simple setter syntax.

/* recommended */
angular
.module('app', [
'ngAnimate',
'ngRoute',
'app.shared',
'app.dashboard'
]);

Getters
[Style Y022]

When using a module, avoid using a variable and instead use chaining with
the getter syntax.

Why?: This produces more readable code and avoids variable collisions or
leaks.

/* avoid */
var app = angular.module('app');
app.controller('SomeController', SomeController);

function SomeController() { }
/* recommended */
angular
.module('app')
.controller('SomeController', SomeController);

function SomeController() { }

Setting vs Getting

[Style Y023]

Only set once and get for all other instances.

Why?: A module should only be created once, then retrieved from that point
and after.

/* recommended */

// to set a module
angular.module('app', []);

// to get a module
angular.module('app');

Named vs Anonymous Functions

[Style Y024]

Use named functions instead of passing an anonymous function in as a


callback.

Why?: This produces more readable code, is much easier to debug, and
reduces the amount of nested callback code.
/* avoid */
angular
.module('app')
.controller('DashboardController', function() { })
.factory('logger', function() { });
/* recommended */

// dashboard.js
angular
.module('app')
.controller('DashboardController', DashboardController);

function DashboardController() { }
// logger.js
angular
.module('app')
.factory('logger', logger);

function logger() { }
Back to top

Controllers

controllerAs View Syntax

[Style Y030]

Use the controllerAs syntax over the classic controller with $scope syntax.
Why?: Controllers are constructed, "newed" up, and provide a single new
instance, and the controllerAs syntax is closer to that of a JavaScript
constructor than the classic $scope syntax.
Why?: It promotes the use of binding to a "dotted" object in the View
(e.g. customer.name instead of name), which is more contextual, easier to read,
and avoids any reference issues that may occur without "dotting".
Why?: Helps avoid using $parent calls in Views with nested controllers.
<!-- avoid -->
<div ng-controller="CustomerController">
{{ name }}
</div>
<!-- recommended -->
<div ng-controller="CustomerController as customer">
{{ customer.name }}
</div>

controllerAs Controller Syntax


[Style Y031]

Use the controllerAs syntax over the classic controller with $scope syntax.
The controllerAs syntax uses this inside controllers which gets bound
to $scope
Why?: controllerAs is syntactic sugar over $scope. You can still bind to the
View and still access $scope methods.
Why?: Helps avoid the temptation of using $scope methods inside a
controller when it may otherwise be better to avoid them or move the
method to a factory, and reference them from the controller. Consider
using $scope in a controller only when needed. For example when publishing
and subscribing events using $emit, $broadcast, or $on.
/* avoid */
function CustomerController($scope) {
$scope.name = {};
$scope.sendMessage = function() { };
}
/* recommended - but see next section */
function CustomerController() {
this.name = {};
this.sendMessage = function() { };
}

controllerAs with vm

[Style Y032]

Use a capture variable for this when using the controllerAs syntax. Choose a
consistent variable name such as vm, which stands for ViewModel.
Why?: The this keyword is contextual and when used within a function
inside a controller may change its context. Capturing the context
of this avoids encountering this problem.
/* avoid */
function CustomerController() {
this.name = {};
this.sendMessage = function() { };
}
/* recommended */
function CustomerController() {
var vm = this;
vm.name = {};
vm.sendMessage = function() { };
}

Note: You can avoid any jshint warnings by placing the comment above the
line of code. However it is not needed when the function is named using
UpperCasing, as this convention means it is a constructor function, which is
what a controller is in Angular.

/* jshint validthis: true */


var vm = this;
Note: When creating watches in a controller using controller as, you can
watch the vm.* member using the following syntax. (Create watches with
caution as they add more load to the digest cycle.)
<input ng-model="vm.title"/>
function SomeController($scope, $log) {
var vm = this;
vm.title = 'Some Title';

$scope.$watch('vm.title', function(current, original) {


$log.info('vm.title was %s', original);
$log.info('vm.title is now %s', current);
});
}

Note: When working with larger codebases, using a more descriptive name
can help ease cognitive overhead & searchability. Avoid overly verbose
names that are cumbersome to type.

<!-- avoid -->


<input ng-model="customerProductItemVm.title">
<!-- recommended -->
<input ng-model="productVm.title">

Bindable Members Up Top

[Style Y033]

Place bindable members at the top of the controller, alphabetized, and not
spread through the controller code.

Why?: Placing bindable members at the top makes it easy to read and helps
you instantly identify which members of the controller can be bound and
used in the View.

Why?: Setting anonymous functions in-line can be easy, but when those
functions are more than 1 line of code they can reduce the readability.
Defining the functions below the bindable members (the functions will be
hoisted) moves the implementation details down, keeps the bindable
members up top, and makes it easier to read.

/* avoid */
function SessionsController() {
var vm = this;

vm.gotoSession = function() {
/* ... */
};
vm.refresh = function() {
/* ... */
};
vm.search = function() {
/* ... */
};
vm.sessions = [];
vm.title = 'Sessions';
}
/* recommended */
function SessionsController() {
var vm = this;

vm.gotoSession = gotoSession;
vm.refresh = refresh;
vm.search = search;
vm.sessions = [];
vm.title = 'Sessions';

////////////

function gotoSession() {
/* */
}

function refresh() {
/* */
}

function search() {
/* */
}
}
Note: If the function is a 1 liner consider keeping it right up top, as long as
readability is not affected.

/* avoid */
function SessionsController(data) {
var vm = this;

vm.gotoSession = gotoSession;
vm.refresh = function() {
/**
* lines
* of
* code
* affects
* readability
*/
};
vm.search = search;
vm.sessions = [];
vm.title = 'Sessions';
}
/* recommended */
function SessionsController(sessionDataService) {
var vm = this;

vm.gotoSession = gotoSession;
vm.refresh = sessionDataService.refresh; // 1 liner is OK
vm.search = search;
vm.sessions = [];
vm.title = 'Sessions';
}

Function Declarations to Hide Implementation Details

[Style Y034]

Use function declarations to hide implementation details. Keep your


bindable members up top. When you need to bind a function in a controller,
point it to a function declaration that appears later in the file. This is tied
directly to the section Bindable Members Up Top. For more details see this
post.

Why?: Placing bindable members at the top makes it easy to read and helps
you instantly identify which members of the controller can be bound and
used in the View. (Same as above.)

Why?: Placing the implementation details of a function later in the file moves
that complexity out of view so you can see the important stuff up top.

Why?: Function declarations are hoisted so there are no concerns over using
a function before it is defined (as there would be with function expressions).

Why?: You never have to worry with function declarations that moving var
a before var b will break your code because a depends on b.

Why?: Order is critical with function expressions

/**
* avoid
* Using function expressions.
*/
function AvengersController(avengersService, logger) {
var vm = this;
vm.avengers = [];
vm.title = 'Avengers';

var activate = function() {


return getAvengers().then(function() {
logger.info('Activated Avengers View');
});
}

var getAvengers = function() {


return avengersService.getAvengers().then(function(data) {
vm.avengers = data;
return vm.avengers;
});
}

vm.getAvengers = getAvengers;

activate();
}
Notice that the important stuff is scattered in the preceding example. In the
example below, notice that the important stuff is up top. For example, the
members bound to the controller such as vm.avengers and vm.title. The
implementation details are down below. This is just easier to read.
/*
* recommend
* Using function declarations
* and bindable members up top.
*/
function AvengersController(avengersService, logger) {
var vm = this;
vm.avengers = [];
vm.getAvengers = getAvengers;
vm.title = 'Avengers';

activate();

function activate() {
return getAvengers().then(function() {
logger.info('Activated Avengers View');
});
}

function getAvengers() {
return avengersService.getAvengers().then(function(data) {
vm.avengers = data;
return vm.avengers;
});
}
}

Defer Controller Logic to Services

[Style Y035]

Defer logic in a controller by delegating to services and factories.


Why?: Logic may be reused by multiple controllers when placed within a
service and exposed via a function.

Why?: Logic in a service can more easily be isolated in a unit test, while the
calling logic in the controller can be easily mocked.

Why?: Removes dependencies and hides implementation details from the


controller.

Why?: Keeps the controller slim, trim, and focused.

/* avoid */
function OrderController($http, $q, config, userInfo) {
var vm = this;
vm.checkCredit = checkCredit;
vm.isCreditOk;
vm.total = 0;

function checkCredit() {
var settings = {};
// Get the credit service base URL from config
// Set credit service required headers
// Prepare URL query string or data object with request data
// Add user-identifying info so service gets the right credit
limit for this user.
// Use JSONP for this browser if it doesn't support CORS
return $http.get(settings)
.then(function(data) {
// Unpack JSON data in the response object
// to find maxRemainingAmount
vm.isCreditOk = vm.total <= maxRemainingAmount
})
.catch(function(error) {
// Interpret error
// Cope w/ timeout? retry? try alternate service?
// Re-reject with appropriate error for a user to see
});
};
}
/* recommended */
function OrderController(creditService) {
var vm = this;
vm.checkCredit = checkCredit;
vm.isCreditOk;
vm.total = 0;

function checkCredit() {
return creditService.isOrderTotalOk(vm.total)
.then(function(isOk) { vm.isCreditOk = isOk; })
.catch(showError);
};
}
Keep Controllers Focused

[Style Y037]

Define a controller for a view, and try not to reuse the controller for other
views. Instead, move reusable logic to factories and keep the controller
simple and focused on its view.

Why?: Reusing controllers with several views is brittle and good end-to-end
(e2e) test coverage is required to ensure stability across large applications.

Assigning Controllers

[Style Y038]

When a controller must be paired with a view and either component may be
re-used by other controllers or views, define controllers along with their
routes.

Note: If a View is loaded via another means besides a route, then use the ng-
controller="Avengers as vm" syntax.
Why?: Pairing the controller in the route allows different routes to invoke
different pairs of controllers and views. When controllers are assigned in the
view using ng-controller, that view is always associated with the same
controller.
/* avoid - when using with a route and dynamic pairing is desired */

// route-config.js
angular
.module('app')
.config(config);

function config($routeProvider) {
$routeProvider
.when('/avengers', {
templateUrl: 'avengers.html'
});
}
<!-- avengers.html -->
<div ng-controller="AvengersController as vm">
</div>
/* recommended */

// route-config.js
angular
.module('app')
.config(config);

function config($routeProvider) {
$routeProvider
.when('/avengers', {
templateUrl: 'avengers.html',
controller: 'Avengers',
controllerAs: 'vm'
});
}
<!-- avengers.html -->
<div>
</div>
Back to top

Services

Singletons

[Style Y040]

Services are instantiated with the new keyword, use this for public methods
and variables. Since these are so similar to factories, use a factory instead for
consistency.

Note: All Angular services are singletons. This means that there is only one
instance of a given service per injector.

// service
angular
.module('app')
.service('logger', logger);

function logger() {
this.logError = function(msg) {
/* */
};
}
// factory
angular
.module('app')
.factory('logger', logger);

function logger() {
return {
logError: function(msg) {
/* */
}
};
}
Back to top

Factories

Single Responsibility

[Style Y050]

Factories should have a single responsibility, that is encapsulated by its context.


Once a factory begins to exceed that singular purpose, a new factory should be
created.

Singletons

[Style Y051]

Factories are singletons and return an object that contains the members of
the service.

Note: All Angular services are singletons.

Accessible Members Up Top

[Style Y052]

Expose the callable members of the service (its interface) at the top, using a
technique derived from the Revealing Module Pattern.

Why?: Placing the callable members at the top makes it easy to read and
helps you instantly identify which members of the service can be called and
must be unit tested (and/or mocked).

Why?: This is especially helpful when the file gets longer as it helps avoid the
need to scroll to see what is exposed.

Why?: Setting functions as you go can be easy, but when those functions are
more than 1 line of code they can reduce the readability and cause more
scrolling. Defining the callable interface via the returned service moves the
implementation details down, keeps the callable interface up top, and makes
it easier to read.

/* avoid */
function dataService() {
var someValue = '';
function save() {
/* */
};
function validate() {
/* */
};

return {
save: save,
someValue: someValue,
validate: validate
};
}
/* recommended */
function dataService() {
var someValue = '';
var service = {
save: save,
someValue: someValue,
validate: validate
};
return service;

////////////

function save() {
/* */
};

function validate() {
/* */
};
}

This way bindings are mirrored across the host object, primitive values
cannot update alone using the revealing module pattern.
Function Declarations to Hide Implementation Details

[Style Y053]

Use function declarations to hide implementation details. Keep your


accessible members of the factory up top. Point those to function
declarations that appears later in the file. For more details see this post.
Why?: Placing accessible members at the top makes it easy to read and
helps you instantly identify which functions of the factory you can access
externally.

Why?: Placing the implementation details of a function later in the file moves
that complexity out of view so you can see the important stuff up top.

Why?: Function declarations are hoisted so there are no concerns over using
a function before it is defined (as there would be with function expressions).

Why?: You never have to worry with function declarations that moving var
a before var b will break your code because a depends on b.

Why?: Order is critical with function expressions

/**
* avoid
* Using function expressions
*/
function dataservice($http, $location, $q, exception, logger) {
var isPrimed = false;
var primePromise;

var getAvengers = function() {


// implementation details go here
};

var getAvengerCount = function() {


// implementation details go here
};

var getAvengersCast = function() {


// implementation details go here
};

var prime = function() {


// implementation details go here
};

var ready = function(nextPromises) {


// implementation details go here
};

var service = {
getAvengersCast: getAvengersCast,
getAvengerCount: getAvengerCount,
getAvengers: getAvengers,
ready: ready
};

return service;
}
/**
* recommended
* Using function declarations
* and accessible members up top.
*/
function dataservice($http, $location, $q, exception, logger) {
var isPrimed = false;
var primePromise;

var service = {
getAvengersCast: getAvengersCast,
getAvengerCount: getAvengerCount,
getAvengers: getAvengers,
ready: ready
};

return service;

////////////

function getAvengers() {
// implementation details go here
}

function getAvengerCount() {
// implementation details go here
}

function getAvengersCast() {
// implementation details go here
}

function prime() {
// implementation details go here
}

function ready(nextPromises) {
// implementation details go here
}
}
Back to top

Data Services

Separate Data Calls


[Style Y060]

Refactor logic for making data operations and interacting with data to a
factory. Make data services responsible for XHR calls, local storage, stashing
in memory, or any other data operations.

Why?: The controller's responsibility is for the presentation and gathering of


information for the view. It should not care how it gets the data, just that it
knows who to ask for it. Separating the data services moves the logic on
how to get it to the data service, and lets the controller be simpler and more
focused on the view.

Why?: This makes it easier to test (mock or real) the data calls when testing a
controller that uses a data service.

Why?: Data service implementation may have very specific code to handle
the data repository. This may include headers, how to talk to the data, or
other services such as $http. Separating the logic into a data service
encapsulates this logic in a single place hiding the implementation from the
outside consumers (perhaps a controller), also making it easier to change
the implementation.
/* recommended */

// dataservice factory
angular
.module('app.core')
.factory('dataservice', dataservice);

dataservice.$inject = ['$http', 'logger'];

function dataservice($http, logger) {


return {
getAvengers: getAvengers
};

function getAvengers() {
return $http.get('/api/maa')
.then(getAvengersComplete)
.catch(getAvengersFailed);

function getAvengersComplete(response) {
return response.data.results;
}

function getAvengersFailed(error) {
logger.error('XHR Failed for getAvengers.' + error.data);
}
}
}
Note: The data service is called from consumers, such as a controller, hiding
the implementation from the consumers, as shown below.

/* recommended */

// controller calling the dataservice factory


angular
.module('app.avengers')
.controller('AvengersController', AvengersController);

AvengersController.$inject = ['dataservice', 'logger'];

function AvengersController(dataservice, logger) {


var vm = this;
vm.avengers = [];

activate();

function activate() {
return getAvengers().then(function() {
logger.info('Activated Avengers View');
});
}

function getAvengers() {
return dataservice.getAvengers()
.then(function(data) {
vm.avengers = data;
return vm.avengers;
});
}
}

Return a Promise from Data Calls

[Style Y061]

When calling a data service that returns a promise such as $http, return a
promise in your calling function too.

Why?: You can chain the promises together and take further action after the
data call completes and resolves or rejects the promise.

/* recommended */

activate();

function activate() {
/**
* Step 1
* Ask the getAvengers function for the
* avenger data and wait for the promise
*/
return getAvengers().then(function() {
/**
* Step 4
* Perform an action on resolve of final promise
*/
logger.info('Activated Avengers View');
});
}

function getAvengers() {
/**
* Step 2
* Ask the data service for the data and wait
* for the promise
*/
return dataservice.getAvengers()
.then(function(data) {
/**
* Step 3
* set the data and resolve the promise
*/
vm.avengers = data;
return vm.avengers;
});
}
Back to top

Directives

Limit 1 Per File

[Style Y070]

Create one directive per file. Name the file for the directive.

Why?: It is easy to mash all the directives in one file, but difficult to then
break those out so some are shared across apps, some across modules,
some just for one module.

Why?: One directive per file is easy to maintain.

Note: "Best Practice: Directives should clean up after themselves. You can
use element.on('$destroy', ...) or scope.$on('$destroy', ...) to run a
clean-up function when the directive is removed" ... from the Angular
documentation.
/* avoid */
/* directives.js */

angular
.module('app.widgets')

/* order directive that is specific to the order module */


.directive('orderCalendarRange', orderCalendarRange)

/* sales directive that can be used anywhere across the sales app */
.directive('salesCustomerInfo', salesCustomerInfo)

/* spinner directive that can be used anywhere across apps */


.directive('sharedSpinner', sharedSpinner);

function orderCalendarRange() {
/* implementation details */
}

function salesCustomerInfo() {
/* implementation details */
}

function sharedSpinner() {
/* implementation details */
}
/* recommended */
/* calendar-range.directive.js */

/**
* @desc order directive that is specific to the order module at a company
named Acme
* @example <div acme-order-calendar-range></div>
*/
angular
.module('sales.order')
.directive('acmeOrderCalendarRange', orderCalendarRange);

function orderCalendarRange() {
/* implementation details */
}
/* recommended */
/* customer-info.directive.js */

/**
* @desc sales directive that can be used anywhere across the sales app at
a company named Acme
* @example <div acme-sales-customer-info></div>
*/
angular
.module('sales.widgets')
.directive('acmeSalesCustomerInfo', salesCustomerInfo);

function salesCustomerInfo() {
/* implementation details */
}
/* recommended */
/* spinner.directive.js */

/**
* @desc spinner directive that can be used anywhere across apps at a
company named Acme
* @example <div acme-shared-spinner></div>
*/
angular
.module('shared.widgets')
.directive('acmeSharedSpinner', sharedSpinner);

function sharedSpinner() {
/* implementation details */
}

Note: There are many naming options for directives, especially since they
can be used in narrow or wide scopes. Choose one that makes the directive
and its file name distinct and clear. Some examples are below, but see
the Naming section for more recommendations.

Manipulate DOM in a Directive

[Style Y072]

When manipulating the DOM directly, use a directive. If alternative ways can
be used such as using CSS to set styles or the animation services, Angular
templating, ngShow or ngHide, then use those instead. For example, if the
directive simply hides and shows, use ngHide/ngShow.

Why?: DOM manipulation can be difficult to test, debug, and there are often
better ways (e.g. CSS, animations, templates)

Provide a Unique Directive Prefix

[Style Y073]

Provide a short, unique and descriptive directive prefix such


as acmeSalesCustomerInfo which would be declared in HTML as acme-sales-
customer-info.
Why?: The unique short prefix identifies the directive's context and origin.
For example a prefix of cc- may indicate that the directive is part of a
CodeCamper app while acme- may indicate a directive for the Acme
company.
Note: Avoid ng- as these are reserved for Angular directives. Research widely
used directives to avoid naming conflicts, such as ion- for the Ionic
Framework.

Restrict to Elements and Attributes

[Style Y074]

When creating a directive that makes sense as a stand-alone element, allow


restrict E (custom element) and optionally restrict A (custom attribute).
Generally, if it could be its own control, E is appropriate. General guideline is
allow EA but lean towards implementing as an element when it's stand-alone
and as an attribute when it enhances its existing DOM element.

Why?: It makes sense.

Why?: While we can allow the directive to be used as a class, if the directive
is truly acting as an element it makes more sense as an element or at least
as an attribute.

Note: EA is the default for Angular 1.3 +

<!-- avoid -->


<div class="my-calendar-range"></div>
/* avoid */
angular
.module('app.widgets')
.directive('myCalendarRange', myCalendarRange);

function myCalendarRange() {
var directive = {
link: link,
templateUrl: '/template/is/located/here.html',
restrict: 'C'
};
return directive;

function link(scope, element, attrs) {


/* */
}
}
<!-- recommended -->
<my-calendar-range></my-calendar-range>
<div my-calendar-range></div>
/* recommended */
angular
.module('app.widgets')
.directive('myCalendarRange', myCalendarRange);
function myCalendarRange() {
var directive = {
link: link,
templateUrl: '/template/is/located/here.html',
restrict: 'EA'
};
return directive;

function link(scope, element, attrs) {


/* */
}
}

Directives and ControllerAs

[Style Y075]

Use controller as syntax with a directive to be consistent with


using controller as with view and controller pairings.

Why?: It makes sense and it's not difficult.

Note: The directive below demonstrates some of the ways you can use
scope inside of link and directive controllers, using controllerAs. I in-lined
the template just to keep it all in one place.

Note: Regarding dependency injection, see Manually Identify Dependencies.

Note: Note that the directive's controller is outside the directive's closure.
This style eliminates issues where the injection gets created as unreachable
code after a return.
<div my-example max="77"></div>
angular
.module('app')
.directive('myExample', myExample);

function myExample() {
var directive = {
restrict: 'EA',
templateUrl: 'app/feature/example.directive.html',
scope: {
max: '='
},
link: linkFunc,
controller: ExampleController,
// note: This would be 'ExampleController' (the exported
controller name, as string)
// if referring to a defined controller in its separate file.
controllerAs: 'vm',
bindToController: true // because the scope is isolated
};

return directive;

function linkFunc(scope, el, attr, ctrl) {


console.log('LINK: scope.min = %s *** should be undefined',
scope.min);
console.log('LINK: scope.max = %s *** should be undefined',
scope.max);
console.log('LINK: scope.vm.min = %s', scope.vm.min);
console.log('LINK: scope.vm.max = %s', scope.vm.max);
}
}

ExampleController.$inject = ['$scope'];

function ExampleController($scope) {
// Injecting $scope just for comparison
var vm = this;

vm.min = 3;

console.log('CTRL: $scope.vm.min = %s', $scope.vm.min);


console.log('CTRL: $scope.vm.max = %s', $scope.vm.max);
console.log('CTRL: vm.min = %s', vm.min);
console.log('CTRL: vm.max = %s', vm.max);
}
<!-- example.directive.html -->
<div>hello world</div>
<div>max={{vm.max}}<input ng-model="vm.max"/></div>
<div>min={{vm.min}}<input ng-model="vm.min"/></div>

Note: You can also name the controller when you inject it into the link
function and access directive attributes as properties of the controller.

// Alternative to above example


function linkFunc(scope, el, attr, vm) {
console.log('LINK: scope.min = %s *** should be undefined',
scope.min);
console.log('LINK: scope.max = %s *** should be undefined',
scope.max);
console.log('LINK: vm.min = %s', vm.min);
console.log('LINK: vm.max = %s', vm.max);
}

[Style Y076]

Use bindToController = true when using controller as syntax with a


directive when you want to bind the outer scope to the directive's
controller's scope.
Why?: It makes it easy to bind outer scope to the directive's controller scope.

Note: bindToController was introduced in Angular 1.3.0.


<div my-example max="77"></div>
angular
.module('app')
.directive('myExample', myExample);

function myExample() {
var directive = {
restrict: 'EA',
templateUrl: 'app/feature/example.directive.html',
scope: {
max: '='
},
controller: ExampleController,
controllerAs: 'vm',
bindToController: true
};

return directive;
}

function ExampleController() {
var vm = this;
vm.min = 3;
console.log('CTRL: vm.min = %s', vm.min);
console.log('CTRL: vm.max = %s', vm.max);
}
<!-- example.directive.html -->
<div>hello world</div>
<div>max={{vm.max}}<input ng-model="vm.max"/></div>
<div>min={{vm.min}}<input ng-model="vm.min"/></div>
Back to top

Resolving Promises

Controller Activation Promises

[Style Y080]

Resolve start-up logic for a controller in an activate function.

Why?: Placing start-up logic in a consistent place in the controller makes it


easier to locate, more consistent to test, and helps avoid spreading out the
activation logic across the controller.
Why?: The controller activate makes it convenient to re-use the logic for a
refresh for the controller/View, keeps the logic together, gets the user to the
View faster, makes animations easy on the ng-view or ui-view, and feels
snappier to the user.

Note: If you need to conditionally cancel the route before you start using the
controller, use a route resolve instead.

/* avoid */
function AvengersController(dataservice) {
var vm = this;
vm.avengers = [];
vm.title = 'Avengers';

dataservice.getAvengers().then(function(data) {
vm.avengers = data;
return vm.avengers;
});
}
/* recommended */
function AvengersController(dataservice) {
var vm = this;
vm.avengers = [];
vm.title = 'Avengers';

activate();

////////////

function activate() {
return dataservice.getAvengers().then(function(data) {
vm.avengers = data;
return vm.avengers;
});
}
}

Route Resolve Promises

[Style Y081]

When a controller depends on a promise to be resolved before the


controller is activated, resolve those dependencies in
the $routeProvider before the controller logic is executed. If you need to
conditionally cancel a route before the controller is activated, use a route
resolver.
Use a route resolve when you want to decide to cancel the route before ever
transitioning to the View.

Why?: A controller may require data before it loads. That data may come
from a promise via a custom factory or $http. Using a route resolve allows
the promise to resolve before the controller logic executes, so it might take
action based on that data from the promise.

Why?: The code executes after the route and in the controllers activate
function. The View starts to load right away. Data binding kicks in when the
activate promise resolves. A busy animation can be shown during the view
transition (via ng-view or ui-view)
Note: The code executes before the route via a promise. Rejecting the
promise cancels the route. Resolve makes the new view wait for the route to
resolve. A busy animation can be shown before the resolve and through
the view transition. If you want to get to the View faster and do not require a
checkpoint to decide if you can get to the View, consider
the controller activate technique instead.
/* avoid */
angular
.module('app')
.controller('AvengersController', AvengersController);

function AvengersController(movieService) {
var vm = this;
// unresolved
vm.movies;
// resolved asynchronously
movieService.getMovies().then(function(response) {
vm.movies = response.movies;
});
}
/* better */

// route-config.js
angular
.module('app')
.config(config);

function config($routeProvider) {
$routeProvider
.when('/avengers', {
templateUrl: 'avengers.html',
controller: 'AvengersController',
controllerAs: 'vm',
resolve: {
moviesPrepService: function(movieService) {
return movieService.getMovies();
}
}
});
}

// avengers.js
angular
.module('app')
.controller('AvengersController', AvengersController);

AvengersController.$inject = ['moviesPrepService'];
function AvengersController(moviesPrepService) {
var vm = this;
vm.movies = moviesPrepService.movies;
}

Note: The example below shows the route resolve points to a named
function, which is easier to debug and easier to handle dependency injection.

/* even better */

// route-config.js
angular
.module('app')
.config(config);

function config($routeProvider) {
$routeProvider
.when('/avengers', {
templateUrl: 'avengers.html',
controller: 'AvengersController',
controllerAs: 'vm',
resolve: {
moviesPrepService: moviesPrepService
}
});
}

function moviesPrepService(movieService) {
return movieService.getMovies();
}

// avengers.js
angular
.module('app')
.controller('AvengersController', AvengersController);

AvengersController.$inject = ['moviesPrepService'];
function AvengersController(moviesPrepService) {
var vm = this;
vm.movies = moviesPrepService.movies;
}
Note: The code example's dependency on movieService is not minification
safe on its own. For details on how to make this code minification safe, see
the sections on dependency injection and on minification and annotation.
Back to top

Handling Exceptions with Promises

[Style Y082]

The catch block of a promise must return a rejected promise to maintain the
exception in the promise chain.

Always handle exceptions in services/factories.

Why?: If the catch block does not return a rejected promise, the caller of the
promise will not know an exception occurred. The caller's then will execute.
Thus, the user may never know what happened.

Why?: To avoid swallowing errors and misinforming the user.

Note: Consider putting any exception handling in a function in a shared


module and service.

/* avoid */

function getCustomer(id) {
return $http.get('/api/customer/' + id)
.then(getCustomerComplete)
.catch(getCustomerFailed);

function getCustomerComplete(data, status, headers, config) {


return data.data;
}

function getCustomerFailed(e) {
var newMessage = 'XHR Failed for getCustomer'
if (e.data && e.data.description) {
newMessage = newMessage + '\n' + e.data.description;
}
e.data.description = newMessage;
logger.error(newMessage);
// ***
// Notice there is no return of the rejected promise
// ***
}
}

/* recommended */
function getCustomer(id) {
return $http.get('/api/customer/' + id)
.then(getCustomerComplete)
.catch(getCustomerFailed);

function getCustomerComplete(data, status, headers, config) {


return data.data;
}

function getCustomerFailed(e) {
var newMessage = 'XHR Failed for getCustomer'
if (e.data && e.data.description) {
newMessage = newMessage + '\n' + e.data.description;
}
e.data.description = newMessage;
logger.error(newMessage);
return $q.reject(e);
}
}
Back to top

Manual Annotating for Dependency Injection

UnSafe from Minification

[Style Y090]

Avoid using the shortcut syntax of declaring dependencies without using a


minification-safe approach.

Why?: The parameters to the component (e.g. controller, factory, etc) will be
converted to mangled variables. For example, common and dataservice may
become a or b and not be found by Angular.
/* avoid - not minification-safe*/
angular
.module('app')
.controller('DashboardController', DashboardController);

function DashboardController(common, dataservice) {


}

This code may produce mangled variables when minified and thus cause
runtime errors.

/* avoid - not minification-safe*/


angular.module('app').controller('DashboardController', d);function d(a,
b) { }
Manually Identify Dependencies

[Style Y091]

Use $inject to manually identify your dependencies for Angular components.


Why?: This technique mirrors the technique used by ng-annotate, which I
recommend for automating the creation of minification safe dependencies.
If ng-annotate detects injection has already been made, it will not duplicate it.
Why?: This safeguards your dependencies from being vulnerable to
minification issues when parameters may be mangled. For
example, common and dataservice may become a or b and not be found by
Angular.

Why?: Avoid creating in-line dependencies as long lists can be difficult to


read in the array. Also it can be confusing that the array is a series of strings
while the last item is the component's function.

/* avoid */
angular
.module('app')
.controller('DashboardController',
['$location', '$routeParams', 'common', 'dataservice',
function Dashboard($location, $routeParams, common,
dataservice) {}
]);
/* avoid */
angular
.module('app')
.controller('DashboardController',
['$location', '$routeParams', 'common', 'dataservice', Dashboard]);

function Dashboard($location, $routeParams, common, dataservice) {


}
/* recommended */
angular
.module('app')
.controller('DashboardController', DashboardController);

DashboardController.$inject = ['$location', '$routeParams', 'common',


'dataservice'];

function DashboardController($location, $routeParams, common,


dataservice) {
}
Note: When your function is below a return statement the $inject may be
unreachable (this may happen in a directive). You can solve this by moving
the Controller outside of the directive.
/* avoid */
// inside a directive definition
function outer() {
var ddo = {
controller: DashboardPanelController,
controllerAs: 'vm'
};
return ddo;

DashboardPanelController.$inject = ['logger']; // Unreachable


function DashboardPanelController(logger) {
}
}
/* recommended */
// outside a directive definition
function outer() {
var ddo = {
controller: DashboardPanelController,
controllerAs: 'vm'
};
return ddo;
}

DashboardPanelController.$inject = ['logger'];
function DashboardPanelController(logger) {
}

Manually Identify Route Resolver Dependencies

[Style Y092]

Use $inject to manually identify your route resolver dependencies for


Angular components.

Why?: This technique breaks out the anonymous function for the route
resolver, making it easier to read.

Why?: An $inject statement can easily precede the resolver to handle


making any dependencies minification safe.
/* recommended */
function config($routeProvider) {
$routeProvider
.when('/avengers', {
templateUrl: 'avengers.html',
controller: 'AvengersController',
controllerAs: 'vm',
resolve: {
moviesPrepService: moviesPrepService
}
});
}
moviesPrepService.$inject = ['movieService'];
function moviesPrepService(movieService) {
return movieService.getMovies();
}
Back to top

Minification and Annotation

ng-annotate

[Style Y100]

Use ng-annotate for Gulp or Grunt and comment functions that need
automated dependency injection using /* @ngInject */

Why?: This safeguards your code from any dependencies that may not be
using minification-safe practices.

Why?: ng-min is deprecated


I prefer Gulp as I feel it is easier to write, to read, and to debug.

The following code is not using minification safe dependencies.

angular
.module('app')
.controller('AvengersController', AvengersController);

/* @ngInject */
function AvengersController(storage, avengerService) {
var vm = this;
vm.heroSearch = '';
vm.storeHero = storeHero;

function storeHero() {
var hero = avengerService.find(vm.heroSearch);
storage.save(hero.name, hero);
}
}
When the above code is run through ng-annotate it will produce the
following output with the $inject annotation and become minification-safe.
angular
.module('app')
.controller('AvengersController', AvengersController);

/* @ngInject */
function AvengersController(storage, avengerService) {
var vm = this;
vm.heroSearch = '';
vm.storeHero = storeHero;

function storeHero() {
var hero = avengerService.find(vm.heroSearch);
storage.save(hero.name, hero);
}
}

AvengersController.$inject = ['storage', 'avengerService'];


Note: If ng-annotate detects injection has already been made
(e.g. @ngInject was detected), it will not duplicate the $inject code.
Note: When using a route resolver you can prefix the resolver's function
with /* @ngInject */ and it will produce properly annotated code, keeping
any injected dependencies minification safe.
// Using @ngInject annotations
function config($routeProvider) {
$routeProvider
.when('/avengers', {
templateUrl: 'avengers.html',
controller: 'AvengersController',
controllerAs: 'vm',
resolve: { /* @ngInject */
moviesPrepService: function(movieService) {
return movieService.getMovies();
}
}
});
}
Note: Starting from Angular 1.3 you can use
the ngApp directive's ngStrictDi parameter to detect any potentially missing
minification safe dependencies. When present the injector will be created in
"strict-di" mode causing the application to fail to invoke functions which do
not use explicit function annotation (these may not be minification safe).
Debugging info will be logged to the console to help track down the
offending code. I prefer to only use ng-strict-di for debugging purposes
only. <body ng-app="APP" ng-strict-di>

Use Gulp or Grunt for ng-annotate

[Style Y101]

Use gulp-ng-annotate or grunt-ng-annotate in an automated build task.


Inject /* @ngInject */ prior to any function that has dependencies.
Why?: ng-annotate will catch most dependencies, but it sometimes requires
hints using the /* @ngInject */ syntax.
The following code is an example of a gulp task using ngAnnotate

gulp.task('js', ['jshint'], function() {


var source = pkg.paths.js;

return gulp.src(source)
.pipe(sourcemaps.init())
.pipe(concat('all.min.js', {newLine: ';'}))
// Annotate before uglify so the code get's min'd properly.
.pipe(ngAnnotate({
// true helps add where @ngInject is not used. It infers.
// Doesn't work with resolve, so we must be explicit there
add: true
}))
.pipe(bytediff.start())
.pipe(uglify({mangle: true}))
.pipe(bytediff.stop())
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest(pkg.paths.dev));
});
Back to top

Exception Handling

decorators

[Style Y110]

Use a decorator, at config time using the $provide service, on


the $exceptionHandler service to perform custom actions when exceptions
occur.

Why?: Provides a consistent way to handle uncaught Angular exceptions for


development-time or run-time.

Note: Another option is to override the service instead of using a decorator.


This is a fine option, but if you want to keep the default behavior and extend
it a decorator is recommended.

/* recommended */
angular
.module('blocks.exception')
.config(exceptionConfig);

exceptionConfig.$inject = ['$provide'];

function exceptionConfig($provide) {
$provide.decorator('$exceptionHandler', extendExceptionHandler);
}

extendExceptionHandler.$inject = ['$delegate', 'toastr'];

function extendExceptionHandler($delegate, toastr) {


return function(exception, cause) {
$delegate(exception, cause);
var errorData = {
exception: exception,
cause: cause
};
/**
* Could add the error to a service's collection,
* add errors to $rootScope, log errors to remote web server,
* or log locally. Or throw hard. It is entirely up to you.
* throw exception;
*/
toastr.error(exception.msg, errorData);
};
}

Exception Catchers

[Style Y111]

Create a factory that exposes an interface to catch and gracefully handle


exceptions.

Why?: Provides a consistent way to catch exceptions that may be thrown in


your code (e.g. during XHR calls or promise failures).

Note: The exception catcher is good for catching and reacting to specific
exceptions from calls that you know may throw one. For example, when
making an XHR call to retrieve data from a remote web service and you want
to catch any exceptions from that service and react uniquely.

/* recommended */
angular
.module('blocks.exception')
.factory('exception', exception);

exception.$inject = ['logger'];

function exception(logger) {
var service = {
catcher: catcher
};
return service;
function catcher(message) {
return function(reason) {
logger.error(message, reason);
};
}
}

Route Errors

[Style Y112]

Handle and log all routing errors using $routeChangeError.

Why?: Provides a consistent way to handle all routing errors.

Why?: Potentially provides a better user experience if a routing error occurs


and you route them to a friendly screen with more details or recovery
options.

/* recommended */
var handlingRouteChangeError = false;

function handleRoutingErrors() {
/**
* Route cancellation:
* On routing error, go to the dashboard.
* Provide an exit clause if it tries to do it twice.
*/
$rootScope.$on('$routeChangeError',
function(event, current, previous, rejection) {
if (handlingRouteChangeError) { return; }
handlingRouteChangeError = true;
var destination = (current && (current.title ||
current.name || current.loadedTemplateUrl)) ||
'unknown target';
var msg = 'Error routing to ' + destination + '. ' +
(rejection.msg || '');

/**
* Optionally log using a custom service or $log.
* (Don't forget to inject custom service)
*/
logger.warning(msg, [current]);

/**
* On routing error, go to another route/state.
*/
$location.path('/');

}
);
}
Back to top

Naming

Naming Guidelines

[Style Y120]

Use consistent names for all components following a pattern that describes
the component's feature then (optionally) its type. My recommended
pattern is feature.type.js. There are 2 names for most assets:
o the file name (avengers.controller.js)
o the registered component name with Angular (AvengersController)

Why?: Naming conventions help provide a consistent way to find content at


a glance. Consistency within the project is vital. Consistency with a team is
important. Consistency across a company provides tremendous efficiency.

Why?: The naming conventions should simply help you find your code faster
and make it easier to understand.

Feature File Names

[Style Y121]

Use consistent names for all components following a pattern that describes
the component's feature then (optionally) its type. My recommended
pattern is feature.type.js.

Why?: Provides a consistent way to quickly identify components.

Why?: Provides pattern matching for any automated tasks.

/**
* common options
*/

// Controllers
avengers.js
avengers.controller.js
avengersController.js
// Services/Factories
logger.js
logger.service.js
loggerService.js
/**
* recommended
*/

// controllers
avengers.controller.js
avengers.controller.spec.js

// services/factories
logger.service.js
logger.service.spec.js

// constants
constants.js

// module definition
avengers.module.js

// routes
avengers.routes.js
avengers.routes.spec.js

// configuration
avengers.config.js

// directives
avenger-profile.directive.js
avenger-profile.directive.spec.js
Note: Another common convention is naming controller files without the
word controller in the file name such as avengers.js instead
of avengers.controller.js. All other conventions still hold using a suffix of
the type. Controllers are the most common type of component so this just
saves typing and is still easily identifiable. I recommend you choose 1
convention and be consistent for your team. My preference
is avengers.controller.js identifying the AvengersController.
/**
* recommended
*/
// Controllers
avengers.js
avengers.spec.js

Test File Names


[Style Y122]

Name test specifications similar to the component they test with a suffix
of spec.

Why?: Provides a consistent way to quickly identify components.

Why?: Provides pattern matching for karma or other test runners.

/**
* recommended
*/
avengers.controller.spec.js
logger.service.spec.js
avengers.routes.spec.js
avenger-profile.directive.spec.js

Controller Names

[Style Y123]

Use consistent names for all controllers named after their feature. Use
UpperCamelCase for controllers, as they are constructors.

Why?: Provides a consistent way to quickly identify and reference controllers.

Why?: UpperCamelCase is conventional for identifying object that can be


instantiated using a constructor.

/**
* recommended
*/

// avengers.controller.js
angular
.module
.controller('HeroAvengersController', HeroAvengersController);

function HeroAvengersController() { }

Controller Name Suffix

[Style Y124]

Append the controller name with the suffix Controller.


Why?: The Controller suffix is more commonly used and is more explicitly
descriptive.
/**
* recommended
*/

// avengers.controller.js
angular
.module
.controller('AvengersController', AvengersController);

function AvengersController() { }

Factory and Service Names

[Style Y125]

Use consistent names for all factories and services named after their feature.
Use camel-casing for services and factories. Avoid prefixing factories and
services with $. Only suffix service and factories with Service when it is not
clear what they are (i.e. when they are nouns).

Why?: Provides a consistent way to quickly identify and reference factories.

Why?: Avoids name collisions with built-in factories and services that use
the $ prefix.
Why?: Clear service names such as logger do not require a suffix.
Why?: Service names such as avengers are nouns and require a suffix and
should be named avengersService.
/**
* recommended
*/

// logger.service.js
angular
.module
.factory('logger', logger);

function logger() { }
/**
* recommended
*/

// credit.service.js
angular
.module
.factory('creditService', creditService);

function creditService() { }
// customer.service.js
angular
.module
.service('customerService', customerService);

function customerService() { }

Directive Component Names

[Style Y126]

Use consistent names for all directives using camel-case. Use a short prefix
to describe the area that the directives belong (some example are company
prefix or project prefix).

Why?: Provides a consistent way to quickly identify and reference


components.

/**
* recommended
*/

// avenger-profile.directive.js
angular
.module
.directive('xxAvengerProfile', xxAvengerProfile);

// usage is <xx-avenger-profile> </xx-avenger-profile>

function xxAvengerProfile() { }

Modules

[Style Y127]

When there are multiple modules, the main module file is


named app.module.js while other dependent modules are named after what
they represent. For example, an admin module is named admin.module.js.
The respective registered module names would be app and admin.

Why?: Provides consistency for multiple module apps, and for expanding to
large applications.

Why?: Provides easy way to use task automation to load all module
definitions first, then all other angular files (for bundling).
Configuration

[Style Y128]

Separate configuration for a module into its own file named after the
module. A configuration file for the main appmodule is
named app.config.js (or simply config.js). A configuration for a module
named admin.module.js is named admin.config.js.

Why?: Separates configuration from module definition, components, and


active code.

Why?: Provides an identifiable place to set configuration for a module.

Routes

[Style Y129]

Separate route configuration into its own file. Examples might be app.route.js for
the main module and admin.route.js for the admin module. Even in smaller apps I
prefer this separation from the rest of the configuration.

Back to top

Application Structure LIFT Principle

LIFT

[Style Y140]

Structure your app such that you can Locate your code quickly, Identify the
code at a glance, keep the Flattest structure you can, and Try to stay DRY.
The structure should follow these 4 basic guidelines.

Why LIFT?: Provides a consistent structure that scales well, is modular, and
makes it easier to increase developer efficiency by finding code quickly.
Another way to check your app structure is to ask yourself: How quickly can
you open and work in all of the related files for a feature?
When I find my structure is not feeling comfortable, I go back and revisit
these LIFT guidelines

i. Locating our code is easy


ii. Identify code at a glance
iii. Flat structure as long as we can
iv. Try to stay DRY (Dont Repeat Yourself) or T-DRY

Locate

[Style Y141]

Make locating your code intuitive, simple and fast.

Why?: I find this to be super important for a project. If the team cannot find
the files they need to work on quickly, they will not be able to work as
efficiently as possible, and the structure needs to change. You may not know
the file name or where its related files are, so putting them in the most
intuitive locations and near each other saves a ton of time. A descriptive
folder structure can help with this.

/bower_components
/client
/app
/avengers
/blocks
/exception
/logger
/core
/dashboard
/data
/layout
/widgets
/content
index.html
.bower.json

Identify

[Style Y142]

When you look at a file you should instantly know what it contains and
represents.
Why?: You spend less time hunting and pecking for code, and become more
efficient. If this means you want longer file names, then so be it. Be
descriptive with file names and keeping the contents of the file to exactly 1
component. Avoid files with multiple controllers, multiple services, or a
mixture. There are deviations of the 1 per file rule when I have a set of very
small features that are all related to each other, they are still easily
identifiable.

Flat

[Style Y143]

Keep a flat folder structure as long as possible. When you get to 7+ files,
begin considering separation.

Why?: Nobody wants to search 7 levels of folders to find a file. Think about
menus on web sites anything deeper than 2 should take serious
consideration. In a folder structure there is no hard and fast number rule,
but when a folder has 7-10 files, that may be time to create subfolders. Base
it on your comfort level. Use a flatter structure until there is an obvious value
(to help the rest of LIFT) in creating a new folder.

T-DRY (Try to Stick to DRY)

[Style Y144]

Be DRY, but don't go nuts and sacrifice readability.

Why?: Being DRY is important, but not crucial if it sacrifices the others in LIFT,
which is why I call it T-DRY. I dont want to type session-view.html for a view
because, well, its obviously a view. If it is not obvious or by convention, then
I name it.

Back to top

Application Structure

Overall Guidelines
[Style Y150]

Have a near term view of implementation and a long term vision. In other
words, start small but keep in mind on where the app is heading down the
road. All of the app's code goes in a root folder named app. All content is 1
feature per file. Each controller, service, module, view is in its own file. All 3rd
party vendor scripts are stored in another root folder and not in
the app folder. I didn't write them and I don't want them cluttering my app
(bower_components, scripts, lib).

Note: Find more details and reasoning behind the structure at this original
post on application structure.

Layout

[Style Y151]

Place components that define the overall layout of the application in a folder
named layout. These may include a shell view and controller may act as the
container for the app, navigation, menus, content areas, and other regions.

Why?: Organizes all layout in a single place re-used throughout the


application.

Folders-by-Feature Structure

[Style Y152]

Create folders named for the feature they represent. When a folder grows to
contain more than 7 files, start to consider creating a folder for them. Your
threshold may be different, so adjust as needed.

Why?: A developer can locate the code, identify what each file represents at
a glance, the structure is flat as can be, and there is no repetitive nor
redundant names.

Why?: The LIFT guidelines are all covered.

Why?: Helps reduce the app from becoming cluttered through organizing
the content and keeping them aligned with the LIFT guidelines.
Why?: When there are a lot of files (10+) locating them is easier with a
consistent folder structures and more difficult in flat structures.

/**
* recommended
*/

app/
app.module.js
app.config.js
components/
calendar.directive.js
calendar.directive.html
user-profile.directive.js
user-profile.directive.html
layout/
shell.html
shell.controller.js
topnav.html
topnav.controller.js
people/
attendees.html
attendees.controller.js
people.routes.js
speakers.html
speakers.controller.js
speaker-detail.html
speaker-detail.controller.js
services/
data.service.js
localstorage.service.js
logger.service.js
spinner.service.js
sessions/
sessions.html
sessions.controller.js
sessions.routes.js
session-detail.html
session-detail.controller.js
Note: Do not structure your app using folders-by-type. This requires moving
to multiple folders when working on a feature and gets unwieldy quickly as
the app grows to 5, 10 or 25+ views and controllers (and other features),
which makes it more difficult than folder-by-feature to locate files.

/*
* avoid
* Alternative folders-by-type.
* I recommend "folders-by-feature", instead.
*/

app/
app.module.js
app.config.js
app.routes.js
directives.js
controllers/
attendees.js
session-detail.js
sessions.js
shell.js
speakers.js
speaker-detail.js
topnav.js
directives/
calendar.directive.js
calendar.directive.html
user-profile.directive.js
user-profile.directive.html
services/
dataservice.js
localstorage.js
logger.js
spinner.js
views/
attendees.html
session-detail.html
sessions.html
shell.html
speakers.html
speaker-detail.html
topnav.html
Back to top

Modularity

Many Small, Self Contained Modules

[Style Y160]

Create small modules that encapsulate one responsibility.

Why?: Modular applications make it easy to plug and go as they allow the
development teams to build vertical slices of the applications and roll out
incrementally. This means we can plug in new features as we develop them.

Create an App Module

[Style Y161]

Create an application root module whose role is to pull together all of the
modules and features of your application. Name this for your application.

Why?: Angular encourages modularity and separation patterns. Creating an


application root module whose role is to tie your other modules together
provides a very straightforward way to add or remove modules from your
application.

Keep the App Module Thin

[Style Y162]

Only put logic for pulling together the app in the application module. Leave
features in their own modules.
Why?: Adding additional roles to the application root to get remote data,
display views, or other logic not related to pulling the app together muddies
the app module and make both sets of features harder to reuse or turn off.

Why?: The app module becomes a manifest that describes which modules
help define the application.

Feature Areas are Modules

[Style Y163]

Create modules that represent feature areas, such as layout, reusable and
shared services, dashboards, and app specific features (e.g. customers,
admin, sales).

Why?: Self contained modules can be added to the application with little or
no friction.

Why?: Sprints or iterations can focus on feature areas and turn them on at
the end of the sprint or iteration.

Why?: Separating feature areas into modules makes it easier to test the
modules in isolation and reuse code.

Reusable Blocks are Modules

[Style Y164]

Create modules that represent reusable application blocks for common


services such as exception handling, logging, diagnostics, security, and local
data stashing.

Why?: These types of features are needed in many applications, so by


keeping them separated in their own modules they can be application
generic and be reused across applications.

Module Dependencies
[Style Y165]

The application root module depends on the app specific feature modules
and any shared or reusable modules.

Why?: The main app module contains a quickly identifiable manifest of the
application's features.
Why?: Each feature area contains a manifest of what it depends on, so it can
be pulled in as a dependency in other applications and still work.

Why?: Intra-App features such as shared data services become easy to locate
and share from within app.core (choose your favorite name for this module).

Note: This is a strategy for consistency. There are many good options here.
Choose one that is consistent, follows Angular's dependency rules, and is
easy to maintain and scale.

My structures vary slightly between projects but they all follow these
guidelines for structure and modularity. The implementation may vary
depending on the features and the team. In other words, don't get hung up
on an exact like-for-like structure but do justify your structure using
consistency, maintainability, and efficiency in mind.

In a small app, you can also consider putting all the shared dependencies in
the app module where the feature modules have no direct dependencies.
This makes it easier to maintain the smaller application, but makes it harder
to reuse modules outside of this application.
Back to top

Startup Logic

Configuration

[Style Y170]

Inject code into module configuration that must be configured before


running the angular app. Ideal candidates include providers and constants.

Why?: This makes it easier to have less places for configuration.

angular
.module('app')
.config(configure);

configure.$inject =
['routerHelperProvider', 'exceptionHandlerProvider', 'toastr'];

function configure (routerHelperProvider, exceptionHandlerProvider,


toastr) {
exceptionHandlerProvider.configure(config.appErrorPrefix);
configureStateHelper();
toastr.options.timeOut = 4000;
toastr.options.positionClass = 'toast-bottom-right';

////////////////

function configureStateHelper() {
routerHelperProvider.configure({
docTitle: 'NG-Modular: '
});
}
}

Run Blocks

[Style Y171]

Any code that needs to run when an application starts should be declared in
a factory, exposed via a function, and injected into the run block.

Why?: Code directly in a run block can be difficult to test. Placing in a factory
makes it easier to abstract and mock.

angular
.module('app')
.run(runBlock);

runBlock.$inject = ['authenticator', 'translator'];

function runBlock(authenticator, translator) {


authenticator.initialize();
translator.initialize();
}
Back to top

Angular $ Wrapper Services

$document and $window

[Style Y180]

Use $document and $window instead of document and window.


Why?: These services are wrapped by Angular and more easily testable than
using document and window in tests. This helps you avoid having to mock
document and window yourself.

$timeout and $interval

[Style Y181]

Use $timeout and $interval instead of setTimeout and setInterval .

Why?: These services are wrapped by Angular and more easily testable and
handle Angular's digest cycle thus keeping data binding in sync.

Back to top

Testing
Unit testing helps maintain clean code, as such I included some of my
recommendations for unit testing foundations with links for more information.

Write Tests with Stories

[Style Y190]

Write a set of tests for every story. Start with an empty test and fill them in
as you write the code for the story.

Why?: Writing the test descriptions helps clearly define what your story will
do, will not do, and how you can measure success.

it('should have Avengers controller', function() {


// TODO
});

it('should find 1 Avenger when filtered by name', function() {


// TODO
});

it('should have 10 Avengers', function() {


// TODO (mock data?)
});

it('should return Avengers via XHR', function() {


// TODO ($httpBackend?)
});

// and so on

Testing Library

[Style Y191]

Use Jasmine or Mocha for unit testing.

Why?: Both Jasmine and Mocha are widely used in the Angular community.
Both are stable, well maintained, and provide robust testing features.

Note: When using Mocha, also consider choosing an assert library such
as Chai. I prefer Mocha.

Test Runner

[Style Y192]

Use Karma as a test runner.

Why?: Karma is easy to configure to run once or automatically when you


change your code.

Why?: Karma hooks into your Continuous Integration process easily on its
own or through Grunt or Gulp.

Why?: Some IDE's are beginning to integrate with Karma, such


as WebStorm and Visual Studio.

Why?: Karma works well with task automation leaders such


as Grunt (with grunt-karma) and Gulp. When using Gulp, use Karma directly
and not with a plugin as the API can be called directly.

/* recommended */

// Gulp example with Karma directly


function startTests(singleRun, done) {
var child;
var excludeFiles = [];
var fork = require('child_process').fork;
var Server = require('karma').Server;
var serverSpecs = config.serverIntegrationSpecs;
if (args.startServers) {
log('Starting servers');
var savedEnv = process.env;
savedEnv.NODE_ENV = 'dev';
savedEnv.PORT = 8888;
child = fork(config.nodeServer);
} else {
if (serverSpecs && serverSpecs.length) {
excludeFiles = serverSpecs;
}
}

var karmaOptions = {
configFile: __dirname + '/karma.conf.js',
exclude: excludeFiles,
singleRun: !!singleRun
};

let server = new Server(karmaOptions, karmaCompleted);


server.start();

////////////////

function karmaCompleted(karmaResult) {
log('Karma completed');
if (child) {
log('shutting down the child process');
child.kill();
}
if (karmaResult === 1) {
done('karma: tests failed with code ' + karmaResult);
} else {
done();
}
}
}

Stubbing and Spying

[Style Y193]

Use Sinon for stubbing and spying.

Why?: Sinon works well with both Jasmine and Mocha and extends the
stubbing and spying features they offer.

Why?: Sinon makes it easier to toggle between Jasmine and Mocha, if you
want to try both.

Why?: Sinon has descriptive messages when tests fail the assertions.
Headless Browser

[Style Y194]

Use PhantomJS to run your tests on a server.

Why?: PhantomJS is a headless browser that helps run your tests without
needing a "visual" browser. So you do not have to install Chrome, Safari, IE,
or other browsers on your server.

Note: You should still test on all browsers in your environment, as


appropriate for your target audience.

Code Analysis

[Style Y195]

Run JSHint on your tests.

Why?: Tests are code. JSHint can help identify code quality issues that may
cause the test to work improperly.

Alleviate Globals for JSHint Rules on Tests

[Style Y196]

Relax the rules on your test code to allow for common globals such
as describe and expect. Relax the rules for expressions, as Mocha uses these.

Why?: Your tests are code and require the same attention and code quality
rules as all of your production code. However, global variables used by the
testing framework, for example, can be relaxed by including this in your test
specs.

/* jshint -W117, -W030 */

Or you can add the following to your JSHint Options file.

"jasmine": true,
"mocha": true,
Organizing Tests

[Style Y197]

Place unit test files (specs) side-by-side with your client code. Place specs
that cover server integration or test multiple components in a
separate tests folder.

Why?: Unit tests have a direct correlation to a specific component and file in
source code.

Why?: It is easier to keep them up to date since they are always in sight.
When coding whether you do TDD or test during development or test after
development, the specs are side-by-side and never out of sight nor mind,
and thus more likely to be maintained which also helps maintain code
coverage.

Why?: When you update source code it is easier to go update the tests at
the same time.

Why?: Placing them side-by-side makes it easy to find them and easy to
move them with the source code if you move the source.
Why?: Having the spec nearby makes it easier for the source code reader to
learn how the component is supposed to be used and to discover its known
limitations.

Why?: Separating specs so they are not in a distributed build is easy with
grunt or gulp.

/src/client/app/customers/customer-detail.controller.js
/customer-detail.controller.spec.js
/customers.controller.js
/customers.controller.spec.js
/customers.module.js
/customers.route.js
/customers.route.spec.js
Back to top

Animations

Usage

[Style Y210]

Use subtle animations with Angular to transition between states for views
and primary visual elements. Include the ngAnimate module. The 3 keys are
subtle, smooth, seamless.

Why?: Subtle animations can improve User Experience when used


appropriately.

Why?: Subtle animations can improve perceived performance as views


transition.

Sub Second

[Style Y211]

Use short durations for animations. I generally start with 300ms and adjust
until appropriate.

Why?: Long animations can have the reverse effect on User Experience and
perceived performance by giving the appearance of a slow application.
animate.css

[Style Y212]

Use animate.css for conventional animations.

Why?: The animations that animate.css provides are fast, smooth, and easy
to add to your application.

Why?: Provides consistency in your animations.

Why?: animate.css is widely used and tested.

Note: See this great post by Matias Niemel on Angular animations

Back to top

Comments

jsDoc

[Style Y220]

If planning to produce documentation, use jsDoc syntax to document


function names, description, params and returns.
Use @namespace and @memberOf to match your app structure.

Why?: You can generate (and regenerate) documentation from your code,
instead of writing it from scratch.

Why?: Provides consistency using a common industry tool.

/**
* Logger Factory
* @namespace Factories
*/
(function() {
angular
.module('app')
.factory('logger', logger);

/**
* @namespace Logger
* @desc Application wide logger
* @memberOf Factories
*/
function logger($log) {
var service = {
logError: logError
};
return service;

////////////

/**
* @name logError
* @desc Logs errors
* @param {String} msg Message to log
* @returns {String}
* @memberOf Factories.Logger
*/
function logError(msg) {
var loggedMsg = 'Error: ' + msg;
$log.error(loggedMsg);
return loggedMsg;
};
}
})();
Back to top

JS Hint

Use an Options File

[Style Y230]

Use JS Hint for linting your JavaScript and be sure to customize the JS Hint
options file and include in source control. See the JS Hint docs for details on
the options.

Why?: Provides a first alert prior to committing any code to source control.

Why?: Provides consistency across your team.

{
"bitwise": true,
"camelcase": true,
"curly": true,
"eqeqeq": true,
"esversion": 6,
"forin": true,
"freeze": true,
"immed": true,
"indent": 4,
"latedef": "nofunc",
"newcap": true,
"noarg": true,
"noempty": true,
"nonbsp": true,
"nonew": true,
"plusplus": false,
"quotmark": "single",
"undef": true,
"unused": false,
"strict": false,
"maxparams": 10,
"maxdepth": 5,
"maxstatements": 40,
"maxcomplexity": 8,
"maxlen": 120,
"asi": false,
"boss": false,
"debug": false,
"eqnull": true,
"esnext": false,
"evil": false,
"expr": false,
"funcscope": false,
"globalstrict": false,
"iterator": false,
"lastsemic": false,
"laxbreak": false,
"laxcomma": false,
"loopfunc": true,
"maxerr": 50,
"moz": false,
"multistr": false,
"notypeof": false,
"proto": false,
"scripturl": false,
"shadow": false,
"sub": true,
"supernew": false,
"validthis": false,
"noyield": false,

"browser": true,
"node": true,

"globals": {
"angular": false,
"$": false
}
}
Back to top
JSCS

Use an Options File

[Style Y235]

Use JSCS for checking your coding styles your JavaScript and be sure to
customize the JSCS options file and include in source control. See the JSCS
docs for details on the options.

Why?: Provides a first alert prior to committing any code to source control.

Why?: Provides consistency across your team.

{
"excludeFiles": ["node_modules/**", "bower_components/**"],

"requireCurlyBraces": [
"if",
"else",
"for",
"while",
"do",
"try",
"catch"
],
"requireOperatorBeforeLineBreak": true,
"requireCamelCaseOrUpperCaseIdentifiers": true,
"maximumLineLength": {
"value": 100,
"allowComments": true,
"allowRegex": true
},
"validateIndentation": 4,
"validateQuoteMarks": "'",

"disallowMultipleLineStrings": true,
"disallowMixedSpacesAndTabs": true,
"disallowTrailingWhitespace": true,
"disallowSpaceAfterPrefixUnaryOperators": true,
"disallowMultipleVarDecl": null,

"requireSpaceAfterKeywords": [
"if",
"else",
"for",
"while",
"do",
"switch",
"return",
"try",
"catch"
],
"requireSpaceBeforeBinaryOperators": [
"=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=",
"&=", "|=", "^=", "+=",

"+", "-", "*", "/", "%", "<<", ">>", ">>>", "&",


"|", "^", "&&", "||", "===", "==", ">=",
"<=", "<", ">", "!=", "!=="
],
"requireSpaceAfterBinaryOperators": true,
"requireSpacesInConditionalExpression": true,
"requireSpaceBeforeBlockStatements": true,
"requireLineFeedAtFileEnd": true,
"disallowSpacesInsideObjectBrackets": "all",
"disallowSpacesInsideArrayBrackets": "all",
"disallowSpacesInsideParentheses": true,

"jsDoc": {
"checkAnnotations": true,
"checkParamNames": true,
"requireParamTypes": true,
"checkReturnTypes": true,
"checkTypes": true
},

"disallowMultipleLineBreaks": true,

"disallowCommaBeforeLineBreak": null,
"disallowDanglingUnderscores": null,
"disallowEmptyBlocks": null,
"disallowTrailingComma": null,
"requireCommaBeforeLineBreak": null,
"requireDotNotation": null,
"requireMultipleVarDecl": null,
"requireParenthesesAroundIIFE": true
}
Back to top

Constants

Vendor Globals

[Style Y240]

Create an Angular Constant for vendor libraries' global variables.


Why?: Provides a way to inject vendor libraries that otherwise are globals.
This improves code testability by allowing you to more easily know what the
dependencies of your components are (avoids leaky abstractions). It also
allows you to mock these dependencies, where it makes sense.

// constants.js

/* global toastr:false, moment:false */


(function() {
'use strict';

angular
.module('app.core')
.constant('toastr', toastr)
.constant('moment', moment);
})();

[Style Y241]

Use constants for values that do not change and do not come from another
service. When constants are used only for a module that may be reused in
multiple applications, place constants in a file per module named after the
module. Until this is required, keep constants in the main module in
a constants.js file.

Why?: A value that may change, even infrequently, should be retrieved from
a service so you do not have to change the source code. For example, a url
for a data service could be placed in a constants but a better place would be
to load it from a web service.

Why?: Constants can be injected into any angular component, including


providers.

Why?: When an application is separated into modules that may be reused in


other applications, each stand-alone module should be able to operate on
its own including any dependent constants.

// Constants used by the entire app


angular
.module('app.core')
.constant('moment', moment);

// Constants used only by the sales module


angular
.module('app.sales')
.constant('events', {
ORDER_CREATED: 'event_order_created',
INVENTORY_DEPLETED: 'event_inventory_depleted'
});
Back to top

File Templates and Snippets


Use file templates or snippets to help follow consistent styles and patterns. Here
are templates and/or snippets for some of the web development editors and IDEs.

Sublime Text

[Style Y250]

Angular snippets that follow these styles and guidelines.

o Download the Sublime Angular snippets


o Place it in your Packages folder
o Restart Sublime
o In a JavaScript file type these commands followed by a TAB
ngcontroller // creates an Angular controller
ngdirective // creates an Angular directive
ngfactory // creates an Angular factory
ngmodule // creates an Angular module
ngservice // creates an Angular service
ngfilter // creates an Angular filter

Visual Studio

[Style Y251]

Angular file templates that follow these styles and guidelines can be found
at SideWaffle

o Download the SideWaffle Visual Studio extension (vsix file)


o Run the vsix file
o Restart Visual Studio

WebStorm

[Style Y252]

Angular live templates that follow these styles and guidelines.


o Download the webstorm-angular-live-templates.xml
o Place it in your templates folder
o Restart WebStorm
o In a JavaScript file type these commands followed by a TAB:
// These are full file snippets containing an IIFE
ngapp // creates an Angular module setter
ngcontroller // creates an Angular controller
ngdirective // creates an Angular directive
ngfactory // creates an Angular factory
ngfilter // creates an Angular filter
ngservice // creates an Angular service

// These are partial snippets intended to be chained
ngconfig // defines a configuration phase function
ngmodule // creates an Angular module getter
ngroute // defines an Angular ngRoute 'when' definition
ngrun // defines a run phase function
ngstate // creates an Angular UI Router state definition

Individual templates are also available for download within the webstorm-
angular-live-templates folder

Atom

[Style Y253]

Angular snippets that follow these styles and guidelines.

apm install angularjs-styleguide-snippets

or

o Open Atom, then open the Package Manager (Packages -> Settings View ->
Install Packages/Themes)
o Search for the package 'angularjs-styleguide-snippets'
o Click 'Install' to install the package
In a JavaScript file type these commands followed by a TAB
ngcontroller // creates an Angular controller
ngdirective // creates an Angular directive
ngfactory // creates an Angular factory
ngmodule // creates an Angular module
ngservice // creates an Angular service
ngfilter // creates an Angular filter

Brackets
[Style Y254]

Angular snippets that follow these styles and guidelines.

o Download the Brackets Angular snippets


o Brackets Extension manager ( File > Extension manager )
o Install 'Brackets Snippets (by edc)'
o Click the light bulb in brackets' right gutter
o Click Settings and then Import
o Choose the file and select to skip or override
o Click Start Import
In a JavaScript file type these commands followed by a TAB
// These are full file snippets containing an IIFE
ngcontroller // creates an Angular controller
ngdirective // creates an Angular directive
ngfactory // creates an Angular factory
ngapp // creates an Angular module setter
ngservice // creates an Angular service
ngfilter // creates an Angular filter

// These are partial snippets intended to chained
ngmodule // creates an Angular module getter
ngstate // creates an Angular UI Router state definition
ngconfig // defines a configuration phase function
ngrun // defines a run phase function
ngwhen // defines an Angular ngRoute 'when' definition
ngtranslate // uses $translate service with its promise

vim

[Style Y255]

vim snippets that follow these styles and guidelines.

o Download the vim Angular snippets


o set neosnippet.vim
o copy snippets to snippet directory

vim UltiSnips snippets that follow these styles and guidelines.

o Download the vim Angular UltiSnips snippets


o set UltiSnips
o copy snippets to UltiSnips directory
ngcontroller // creates an Angular controller
ngdirective // creates an Angular directive
ngfactory // creates an Angular factory
ngmodule // creates an Angular module
ngservice // creates an Angular service
ngfilter // creates an Angular filter

Visual Studio Code

[Style Y256]

Visual Studio Code snippets that follow these styles and guidelines.

o Download the VS Code Angular snippets


o copy snippets to snippet directory, or alternatively copy and paste the
snippets into your existing ones
ngcontroller // creates an Angular controller
ngdirective // creates an Angular directive
ngfactory // creates an Angular factory
ngmodule // creates an Angular module
ngservice // creates an Angular service

Emacs

[Style Y257]

Emacs snippets that follow these styles and guidelines.

o Download the Emacs Angular snippets

Note that yasnippet categorizes snippets by major mode, and there


are several Emacs major modes for editing Javascript code. The
snippets are in js2-mode, and the other directories contain only a
dotfile to reference them there.
o install yasnippet (M-x package-install RET yasnippet RET)
o copy snippets to snippet directory, or modify your Emacs init to add snippet
directory to yas-snippet-dirs
ngcontroller // creates an Angular controller
ngdirective // creates an Angular directive
ngfactory // creates an Angular factory
ngmodule // creates an Angular module
ngservice // creates an Angular service
ngfilter // creates an Angular filter
Back to top
Yeoman Generator

[Style Y260]

You can use the HotTowel yeoman generator to create an app that serves as a
starting point for Angular that follows this style guide.

1. Install generator-hottowel

2. npm install -g generator-hottowel

3. Create a new folder and change directory to it

4. mkdir myapp
5. cd myapp

6. Run the generator

7. yo hottowel helloWorld
Back to top

Routing
Client-side routing is important for creating a navigation flow between views and
composing views that are made of many smaller templates and directives.

[Style Y270]

Use the AngularUI Router for client-side routing.

Why?: UI Router offers all the features of the Angular router plus a few
additional ones including nested routes and states.

Why?: The syntax is quite similar to the Angular router and is easy to migrate
to UI Router.

Note: You can use a provider such as the routerHelperProvider shown below
to help configure states across files, during the run phase.
// customers.routes.js
angular
.module('app.customers')
.run(appRun);

/* @ngInject */
function appRun(routerHelper) {
routerHelper.configureStates(getStates());
}

function getStates() {
return [
{
state: 'customer',
config: {
abstract: true,
template: '<ui-view class="shuffle-animation"/>',
url: '/customer'
}
}
];
}
// routerHelperProvider.js
angular
.module('blocks.router')
.provider('routerHelper', routerHelperProvider);

routerHelperProvider.$inject = ['$locationProvider', '$stateProvider',


'$urlRouterProvider'];
/* @ngInject */
function routerHelperProvider($locationProvider, $stateProvider,
$urlRouterProvider) {
/* jshint validthis:true */
this.$get = RouterHelper;

$locationProvider.html5Mode(true);

RouterHelper.$inject = ['$state'];
/* @ngInject */
function RouterHelper($state) {
var hasOtherwise = false;

var service = {
configureStates: configureStates,
getStates: getStates
};

return service;

///////////////

function configureStates(states, otherwisePath) {


states.forEach(function(state) {
$stateProvider.state(state.state, state.config);
});
if (otherwisePath && !hasOtherwise) {
hasOtherwise = true;
$urlRouterProvider.otherwise(otherwisePath);
}
}
function getStates() { return $state.get(); }
}
}

[Style Y271]

Define routes for views in the module where they exist. Each module should
contain the routes for the views in the module.

Why?: Each module should be able to stand on its own.

Why?: When removing a module or adding a module, the app will only
contain routes that point to existing views.

Why?: This makes it easy to enable or disable portions of an application


without concern over orphaned routes.

Back to top

Task Automation
Use Gulp or Grunt for creating automated tasks. Gulp leans to code over
configuration while Grunt leans to configuration over code. I personally prefer Gulp
as I feel it is easier to read and write, but both are excellent.

Learn more about gulp and patterns for task automation in my Gulp Pluralsight
course

[Style Y400]

Use task automation to list module definition files *.module.js before all
other application JavaScript files.

Why?: Angular needs the module definitions to be registered before they are
used.

Why?: Naming modules with a specific pattern such as *.module.js makes it


easy to grab them with a glob and list them first.
var clientApp = './src/client/app/';

// Always grab module files first


var files = [
clientApp + '**/*.module.js',
clientApp + '**/*.js'
];
Back to top

Filters

[Style Y420]

Avoid using filters for scanning all properties of a complex object graph. Use
filters for select properties.

Why?: Filters can easily be abused and negatively affect performance if not
used wisely, for example when a filter hits a large and deep object graph.

Back to top

Angular docs
For anything else, API reference, check the Angular documentation.

Back to top
Single Responsibility o Responsabilidad nica

La regla del 1

[Style Y001]

Define 1 componente por archivo.

Por qu?: Un componente por archivo promueve pruebas unitarias ms


fciles.

Por qu?: Un componente por archivo hace que sea mucho ms fcil de leer,
mantener, y evita colisiones con los equipos en el control de cdigo.

Por qu?: Un componente por archivo evita errores ocultos que a menudo
surgen cuando se combinan componentes en un archivo donde pueden
compartir variables, crear closures (clausuras) no deseadas, o acoplamiento
indeseado de dependencias.

El siguiente ejemplo define el mdulo app y sus dependencias, define un


controlador, y defines una fbrica todo en el mismo archivo.
/* evitar */
angular
.module('app', ['ngRoute'])
.controller('SomeController', SomeController)
.factory('someFactory', someFactory);

function SomeController() { }

function someFactory() { }

Los mismos componentes estn separados en su propio archivo.

/* recomendado */

// app.module.js
angular
.module('app', ['ngRoute']);
/* recomendado */

// someController.js
angular
.module('app')
.controller('SomeController', SomeController);

function SomeController() { }
/* recomendado */
// someFactory.js
angular
.module('app')
.factory('someFactory', someFactory);

function someFactory() { }
Volver arriba

IIFE

Closures de JavaScript

[Style Y010]

Envuelve los componentes Angular en una expresin de funcin que se


invoca inmediatamente Immediately Invoked Function Expression (IIFE).

Por qu?: Una IIFE elimina las variables del scope global. Esto ayuda a
prevenir que las variables y las declaraciones de funciones vivan ms de lo
esperado en el scope global, evitando as colisin de variables.

Por qu?: Cuando tu cdigo se minimiza y se empaqueta en un archivo


nico para desplegar al servidor de produccin, podras tener colisin de
variables y muchas variables globales. Una IIFE te protege contra ambos,
creando una scope por cada archivo.

/* evitar */
// logger.js
angular
.module('app')
.factory('logger', logger);

// La funcin logger es aadida como variable global


function logger() { }

// storage.js
angular
.module('app')
.factory('storage', storage);

// la funcin storage es aadida como variable global


function storage() { }
/**
* recomendado
*
* as no dejamos ninguna variable global
*/

// logger.js
(function() {
'use strict';

angular
.module('app')
.factory('logger', logger);

function logger() { }
})();

// storage.js
(function() {
'use strict';

angular
.module('app')
.factory('storage', storage);

function storage() { }
})();

Nota: Para acortar nicamente, el resto de los ejemplos de esta gua podran
omitir la sintaxis IIFE.

Nota: IIFE previente que el cdigo de los tests llegue a sus variables privadas,
como expresiones regulares o funciones de ayuda que normalmente vienen
bien para hacer pruebas por s solas. Sin embargo, puedes acceder a ellas
creando accesorios o accediendo a travs de sus componentes. Por ejemplo,
poniendo las funciones de ayuda, expresiones regulares o constantes en su
propia fbrica.

Volver arriba

Mdulos

Evitando la colisin de nombres

[Style Y020]

Use una convencin de nombres nica con separadores para los sub-
mdulos.
Por qu?: Nombres nicos ayudan a evitar colisiones en los nombres de
mdulos. Los separadores ayudan a definir los mdulos y la jerarqua de sus
sub-mdulos. Por ejemplo app puede ser tu mdulo raz
y app.dashboard y app.userspueden ser mdulos que dependen de app.

Definiciones (aka Setters)

[Style Y021]

Declara los mdulos sin usar una variable, usando la sintaxis de los setters.

Por qu?: Con un componente por archivo, es raro que necesitemos


introducir una variable para el mdulo.

/* evitar */
var app = angular.module('app', [
'ngAnimate',
'ngRoute',
'app.shared',
'app.dashboard'
]);

En su lugar usa la sintaxis de los setters

/* recomendado */
angular
.module('app', [
'ngAnimate',
'ngRoute',
'app.shared',
'app.dashboard'
]);

Getters

[Style Y022]

Al usar un mdulo, evita usar una variable y en su lugar usa encadenamiento


con la sintaxis de los getter.

Por qu?: Esto hace ms legible el cdigo y evita que las variables
colisionen.

/* evitar */
var app = angular.module('app');
app.controller('SomeController', SomeController);
function SomeController() { }
/* recomendado */
angular
.module('app')
.controller('SomeController', SomeController);

function SomeController() { }

Setting vs Getting

[Style Y023]

Setea slo una vez y usa get para el resto de instancias.

Por qu?: Un mdulo debe ser creado slo una vez y recuperado desde ese
punto.

- Usa `angular.module('app', []);` para setear un mdulo.


- Usa `angular.module('app');` para recuperar un mdulo.

Funciones annimas vs funciones con nombre

[Style Y024]

Usa funciones con nombre en lugar de pasar una funcin annima en el


callback.

Por qu?: As el cdigo es ms legible, es ms fcil de debugear, y reduce la


cantidad de cdigo anidado en los callbacks.

/* evitar */
angular
.module('app')
.controller('Dashboard', function() { })
.factory('logger', function() { });
/* recomendado */

// dashboard.js
angular
.module('app')
.controller('Dashboard', Dashboard);

function Dashboard() { }
// logger.js
angular
.module('app')
.factory('logger', logger);
function logger() { }
Volver arriba

Controladores

controllerAs Sintaxis en la Vista

[Style Y030]

Usa la sintaxis controllerAs en lugar del clsico controlador con $scope.


Por qu?: Los Controladores se construyen, renuevan y proporcionan una
nueva instancia nica, y la sintaxis controllerAs se acerca ms a eso que
la sintaxis clsica de $scope.
Por qu?: Promueves el uso de binding usando el "." en el objeto dentro de
la Vista (ej. customer.name en lugar de name), as es ms contextual, fcil de
leer y evitas problemas de referencia que pueden aparecer con el "punto".
Por qu?: Ayuda a evitar usar $parent en las Vistas con controladores
anidados.
<!-- evitar -->
<div ng-controller="Customer">
{{ name }}
</div>
<!-- recomendado -->
<div ng-controller="Customer as customer">
{{ customer.name }}
</div>

controllerAs Sintaxis en el Controlador

[Style Y031]

Usa la sintaxis controllerAs en lugar del clsico controlador con $scope.


La sintaxis controllerAs usa this dentro de los controladores que se asocian
al $scope
Por qu?: controllerAs es azcar sintctico sobre el $scope. Puedes enlazar a
la vista y acceder a los mtodos del $scope.
Por qu?: Ayuda a evitar la tentacin de usar los mtodos del $scope dentro
de un controller cuando debera ser mejor evitar usarlos o moverlos a una
fbrica. Considera usar $scope en una factory, o en un controlador slo
cuando sea necesario. Por ejemplo cuando publicas y te suscribes a eventos
usando $emit, $broadcast, o $on considera mover estos usos a una fbrica e
invocarlos desde el controlador.
/* evitar */
function Customer($scope) {
$scope.name = {};
$scope.sendMessage = function() { };
}
/* recomendado - pero mira la seccin siguiente */
function Customer() {
this.name = {};
this.sendMessage = function() { };
}

controllerAs con vm

[Style Y032]

Usa una variable para capturar this cuando uses la sintaxis controllerAs.
Elige un nombre de variable consistente como vm, de ViewModel.
Por qu?: La palabra this es contextual y cuando es usada dentro de una
funcin en un controlador puede cambiar su contexto. Capturando el
contexto de this te evita encontrarte este problema.
/* evitar */
function Customer() {
this.name = {};
this.sendMessage = function() { };
}
/* recomendado */
function Customer() {
var vm = this;
vm.name = {};
vm.sendMessage = function() { };
}

Nota: Puedes evitar los warnings de jshint escribiendo un comentario


encima de la lnea de cdigo. Sin embargo no hace falta si el nombre de la
funcin empieza con maysculas, ya que esa es la convencin para las
funciones de los constructores, que es lo que un controller en Angular es.

/* jshint validthis: true */


var vm = this;
Nota: Cuando crees watchers en un controlador usando controller as,
puedes observar la variable vm.* usando la siguiente sintaxis.(Crea los
watchers con precaucin ya que aaden mucha carga al ciclo de digest)
<input ng-model="vm.title"/>
function SomeController($scope, $log) {
var vm = this;
vm.title = 'Some Title';
$scope.$watch('vm.title', function(current, original) {
$log.info('vm.title was %s', original);
$log.info('vm.title is now %s', current);
});
}

Miembros Bindeables Arriba

[Style Y033]

Coloca las asociaciones en la parte superior del controlador, ordenalas


alfabticamente y no las distribuyas a lo largo del cdigo del controlador.

Por qu?: Colocar las variables asignables arriba hace ms fcil la lectura y
te ayuda a identificar qu variables del controlador pueden ser asociadas y
usadas en la Vista.

Por qu?: Setear funciones annimas puede ser fcil, pero cuando esas
funciones tienen ms de una lnea de cdigo se hace menos legible. Definir
las funciones bajo las variables bindeables (las declaraciones de las
funciones sern movidas hacia arriba en el proceso de hoisting), hace que
los detalles de implementacin estn abajo, deja las variables arriba y ms
sencilla la lectura.

/* evitar */
function Sessions() {
var vm = this;

vm.gotoSession = function() {
/* ... */
};
vm.refresh = function() {
/* ... */
};
vm.search = function() {
/* ... */
};
vm.sessions = [];
vm.title = 'Sessions';
/* recomendado */
function Sessions() {
var vm = this;

vm.gotoSession = gotoSession;
vm.refresh = refresh;
vm.search = search;
vm.sessions = [];
vm.title = 'Sessions';
////////////

function gotoSession() {
/* */
}

function refresh() {
/* */
}

function search() {
/* */
}

Nota: Si la funcin es de una lnea, djala arriba, siempre y cuando no afecte


en la legibilidad.

/* evitar */
function Sessions(data) {
var vm = this;

vm.gotoSession = gotoSession;
vm.refresh = function() {
/**
* lneas
* de
* cdigo
* que afectan a
* la legibilidad
*/
};
vm.search = search;
vm.sessions = [];
vm.title = 'Sessions';
/* recomendado */
function Sessions(dataservice) {
var vm = this;

vm.gotoSession = gotoSession;
vm.refresh = dataservice.refresh; // 1 liner is OK
vm.search = search;
vm.sessions = [];
vm.title = 'Sessions';

Declaraciones de Funciones para Esconder los Detalles de


Implementacin

[Style Y034]

Declara funciones para ocultar detalles de implementacin. Mantn las


variables bindeables arriba. Cuando necesites bindear una funcin a un
controlador referencia una funcin que aparezca despus en el archivo. Esto
est directamente relacionado con la seccin: Miembros Bindeables Arriba.
Para ms detalles mira este post.

Por qu?: Colocar las variables bindeables arriba hace ms fcil la lectura y
te ayuda a identificar qu variables del controlador pueden ser asociadas y
usadas en la Vista.

Por qu?: Colocar los detalles de implementacin de una funcin al final del
archivo deja la complejidad fuera de vista as puedes ver las cosas
importantes arriba.

Por qu?: La declaracin de las funciones son movidas arriba por el proceso
de hoisting as que no tenemos que preocuparnos por usar una funcin
antes de que sea definida (como la habra si fueran funciones en forma de
expresin)
Por qu?: No tendrs que preocuparte de que si pones var a antes de var
b se rompa el cdigo porque a dependa de b.

Por qu?: El orden es crtico para las funciones en forma de expresin

/**
* evitar
* Using function expressions.
*/
function Avengers(dataservice, logger) {
var vm = this;
vm.avengers = [];
vm.title = 'Avengers';

var activate = function() {


return getAvengers().then(function() {
logger.info('Activated Avengers View');
});
}

var getAvengers = function() {


return dataservice.getAvengers().then(function(data) {
vm.avengers = data;
return vm.avengers;
});
}

vm.getAvengers = getAvengers;

activate();
}
Ntese que las cosas importantes estn dispersas en el ejemplo anterior. En
el siguiente ejemplo, lo importante est arriba. Por ejemplo, las variables
asociadas al controlador como vm.avengers y vm.title. Los detalles de
implementacin estn debajo. As es ms fcil de leer.
/*
* recomendado
* Usando declaraciones de funciones y
* miembros bindeables arriba.
*/
function Avengers(dataservice, logger) {
var vm = this;
vm.avengers = [];
vm.getAvengers = getAvengers;
vm.title = 'Avengers';

activate();

function activate() {
return getAvengers().then(function() {
logger.info('Activated Avengers View');
});
}

function getAvengers() {
return dataservice.getAvengers().then(function(data) {
vm.avengers = data;
return vm.avengers;
});
}
}

Diferir la Lgica del Controlador

[Style Y035]

Difiera la lgica dentro de un controlador delegndola a servicios y fbricas.

Por qu?: La lgica podra ser reutilizada por varios controladores cuando la
colocas en un servicio y la expones como una funcin.

Por qu?: La lgica en un servicio puede ser aislada en un test unitario,


mientras que la lgica de llamadas en un controlador se puede mockear
fcilmente.

Por qu?: Elimina dependencias y esconde detalles de implementacin del


controlador.

/* evitar */
function Order($http, $q, config, userInfo) {
var vm = this;
vm.checkCredit = checkCredit;
vm.isCreditOk;
vm.total = 0;

function checkCredit() {
var settings = {};
// Get the credit service base URL from config
// Set credit service required headers
// Prepare URL query string or data object with request data
// Add user-identifying info so service gets the right credit
limit for this user.
// Use JSONP for this browser if it doesn't support CORS
return $http.get(settings)
.then(function(data) {
// Unpack JSON data in the response object
// to find maxRemainingAmount
vm.isCreditOk = vm.total <= maxRemainingAmount
})
.catch(function(error) {
// Interpret error
// Cope w/ timeout? retry? try alternate service?
// Re-reject with appropriate error for a user to see
});
};
}
/* recomendado */
function Order(creditService) {
var vm = this;
vm.checkCredit = checkCredit;
vm.isCreditOk;
vm.total = 0;

function checkCredit() {
return creditService.isOrderTotalOk(vm.total)
.then(function(isOk) { vm.isCreditOk = isOk; })
.catch(showServiceError);
};
}

Mantn tus Controladores Enfocados

[Style Y037]

Define un controlador para una vista, no intentes reutilizar el controlador


para otras vistas. En lugar de eso, mueve la lgica que se pueda reutilizar a
fbricas y deja el controlador simple y enfocado en su vista.

Por qu?: Reutilizar controladores con varias vistas es arriesgado y


necesitaras buena cobertura de tests end to end (e2e) para asegurar que
todo funciona bien en la aplicacin.

Asignando Controladores

[Style Y038]

Cuando un controlador debe ser asociado a una vista y cada componente


puede ser reutilizado por otros controladores o vistas, define controladores
con sus rutas.

Nota: Si una Vista es cargada por otra adems de por la ruta, entonces usa
la sintaxis ng-controller="Avengers as vm".
Por qu?: Emparejar el controlador en la ruta permite a diferentes rutas
invocar diferentes pares de controladores y vistas. Cuando los controladores
son asignados en la vista usando ng-controller, esa vista siempre estar
asociada al mismo controlador.
/* evitar - cuando se use con una ruta y queramos asociarlo dinmicamente
*/

// route-config.js
angular
.module('app')
.config(config);

function config($routeProvider) {
$routeProvider
.when('/avengers', {
templateUrl: 'avengers.html'
});
}
<!-- avengers.html -->
<div ng-controller="Avengers as vm">
</div>
/* recomendado */

// route-config.js
angular
.module('app')
.config(config);

function config($routeProvider) {
$routeProvider
.when('/avengers', {
templateUrl: 'avengers.html',
controller: 'Avengers',
controllerAs: 'vm'
});
}
<!-- avengers.html -->
<div>
</div>
Volver arriba

Servicios

Singletons

[Style Y040]

Los Servicios son instanciados con un new, usan this para los mtodos
pblicos y las variables. Ya que son muy similares a las factories, usa una
factory en su lugar por consistencia.

Nota: Todos los servicios Angular son singletons. Esto significa que slo hay
una instancia de un servicio por inyector.
// service
angular
.module('app')
.service('logger', logger);

function logger() {
this.logError = function(msg) {
/* */
};
}
// factory
angular
.module('app')
.factory('logger', logger);

function logger() {
return {
logError: function(msg) {
/* */
}
};
}
Volver arriba

Fbricas

Responsabilidad nica

[Style Y050]

Las fbricas deben tener una responsabilidad nica, que es encapsulada por su
contexto. Cuando una fbrica empiece a exceder el principio de responsabilidad
nica, una nueva fbrica debe ser creada.

Singletons

[Style Y051]

Las Fbricas son singleton y devuelven un objeto que contiene las variables
del servicio.

Nota: Todos los servicios Angular son singletons.

Miembros accesibles Arriba


[Style Y052]

Expn las variables que se llaman del servicio (su interfaz) arriba, usando la
tcnica deribada de Revealing Module Pattern.

Por qu?: Colocar los elementos que se llaman arriba hace ms fcil la
lectura y te ayuda a identificar los elementos del servicio que se pueden
llamar y se deben testear (y/o mockear).

Por qu?: Es especialmente til cuando el archivo se hace ms largo, ya que


ayuda a evitar el scroll para ver qu se expone.

Por qu?: Setear las funciones puede ser fcil, pero cuando tienen ms de
una lnea ser reduce la legibilidad. Definiendo la interfaz mueve los detalles
de implementacin abajo, mantiene la interfaz que va a ser llamada arriba y
lo hace ms fcil de leer.

/* evitar */
function dataService() {
var someValue = '';
function save() {
/* */
};
function validate() {
/* */
};

return {
save: save,
someValue: someValue,
validate: validate
};
}
/* recomendado */
function dataService() {
var someValue = '';
var service = {
save: save,
someValue: someValue,
validate: validate
};
return service;

////////////

function save() {
/* */
};

function validate() {
/* */
};
}

De esta forma se asocian los bindeos desde el objeto que lo mantiene, los
valores primitivos no se pueden modificar por si solos usando este patrn

Declaracin de Funciones para Esconder los Detalles de


Implementacin
[Style Y053]

Declara funciones para esconder detalles de implementacin. Manten los


elementos accesibles en la parte superior de la fbrica. Referencia a los que
aparezcan despus en el archivo. Para ms detalles visita este post.

Por qu?: Coloca los elementos accesibles en la parte superior para hacerlo
ms fcil de leer y ayudarte a identificar instantneamente qu funciones de
la fbrica se pueden accesar externamente.

Por qu?: Colocar los detalles de implementacin de una funcin al final del
archivo mueve esa complejidad fuera de la vista, de esta forma puedes dejar
lo importante arriba.

Por qu?: Las declaraciones de las funciones son "elevadas" de esta forma
no hay problemas en usar una funcin antes de su definicin (como la
habra si fueran funciones en forma de expresin).

Por qu?: No tendrs que preocuparte de que si pones var a antes de var
b se rompa el cdigo porque a dependa de b.

Por qu?: El orden es crtico para las funciones en forma de expresin

/**
* evitar
* Usar funcin como expresin
*/
function dataservice($http, $location, $q, exception, logger) {
var isPrimed = false;
var primePromise;

var getAvengers = function() {


// detalles de implementacin van aqu
};

var getAvengerCount = function() {


// detalles de implementacin van aqu
};

var getAvengersCast = function() {


// detalles de implementacin van aqu
};

var prime = function() {


// detalles de implementacin van aqu
};

var ready = function(nextPromises) {


// detalles de implementacin van aqu
};

var service = {
getAvengersCast: getAvengersCast,
getAvengerCount: getAvengerCount,
getAvengers: getAvengers,
ready: ready
};

return service;
}
/**
* recomendado
* Usar declaracin de funciones
* y miembros accesibles arriba
*/
function dataservice($http, $location, $q, exception, logger) {
var isPrimed = false;
var primePromise;

var service = {
getAvengersCast: getAvengersCast,
getAvengerCount: getAvengerCount,
getAvengers: getAvengers,
ready: ready
};

return service;

////////////

function getAvengers() {
// detalles de implementacin van aqu
}

function getAvengerCount() {
// detalles de implementacin van aqu
}

function getAvengersCast() {
// detalles de implementacin van aqu
}

function prime() {
// detalles de implementacin van aqu
}

function ready(nextPromises) {
// detalles de implementacin van aqu
}
}
Volver arriba
Servicios de Datos

Separate Data Calls

[Style Y060]

Refactoriza la lgica para hacer operaciones e interaciones con datos en una


factory. Crear data services responsables de las peticiones XHR, local storage,
memoria o cualquier otra operacin con datos.

Por qu?: La responsabilidad del controlador es la de presentar y recoger


informacin para la vista. No debe importarle cmo se consiguen los datos,
slo saber cmo conseguirlos. Separando los datos de servicios movemos la
lgica de cmo conseguirlos al servicio de datos, y deja el controlador
simple, enfocndose en la vista.

Por qu?: Hace ms fcil testear (mock o real) las llamadas de datos cuando
testeamos un controlador que usa un data service.

Por qu?: La implementacin del servicio de datos puede tener cdigo muy
especfico para usar el repositorio de datos. Podra incluir cabeceras, cmo
hablar a los datos, u otros servicios como $http. Separando la lgica en
servicios de datos encapsulamos la lgica en un nico lugar, escondiendo la
implementacin de sus consumidores externos (quiz un controlador), de
esta forma es ms fcil cambiar la implementacin.

/* recomendado */

// dataservice factory
angular
.module('app.core')
.factory('dataservice', dataservice);

dataservice.$inject = ['$http', 'logger'];

function dataservice($http, logger) {


return {
getAvengers: getAvengers
};

function getAvengers() {
return $http.get('/api/maa')
.then(getAvengersComplete)
.catch(getAvengersFailed);

function getAvengersComplete(response) {
return response.data.results;
}

function getAvengersFailed(error) {
logger.error('XHR Failed for getAvengers.' + error.data);
}
}
}

Nota: El servicio de datos es llamado desde los consumidores, como el


controlador, escondiendo la implementacin del consumidor como se
muestra a continuacin.

/* recomendado */

// controller llamando a la factory del data service


angular
.module('app.avengers')
.controller('Avengers', Avengers);

Avengers.$inject = ['dataservice', 'logger'];

function Avengers(dataservice, logger) {


var vm = this;
vm.avengers = [];

activate();

function activate() {
return getAvengers().then(function() {
logger.info('Activated Avengers View');
});
}

function getAvengers() {
return dataservice.getAvengers()
.then(function(data) {
vm.avengers = data;
return vm.avengers;
});
}
}

Regresa una Promesa desde las Llamadas a Datos

[Style Y061]

Cuando llamamos a servicios de datos que devuelven una promesa como


$http, devuelve una promesa en la llamada de tu funcin tambin.
Por qu?: Puedes encadenar promesas y hacer algo cuando la llamada se
complete y resuelva o rechace la promesa.

/* recomendado */

activate();

function activate() {
/**
* Step 1
* Pide a la funcin getAvengers por los datos
* de los vengadores y espera la promesa
*/
return getAvengers().then(function() {
/**
* Step 4
* Ejecuta una accin cuando se resuelva la promesa final
*/
logger.info('Activated Avengers View');
});
}

function getAvengers() {
/**
* Step 2
* Pide al servicio de datos los datos y espera
* por la promesa
*/
return dataservice.getAvengers()
.then(function(data) {
/**
* Step 3
* setea los datos y resuelve la promesa
*/
vm.avengers = data;
return vm.avengers;
});
}

Volver arriba

Directivas

Limitadas a 1 Por Archivo

[Style Y070]

Crea una directiva por archivo. Llama al archivo como la directiva.


Por qu?: Es muy fcil colocar todas las directivas en un archivo, pero ser
ms difcil de partir para ser compartida entre aplicaciones, mdulos o para
un simple mdulo.

Por qu?: Una directiva por archivo es fcil de mantener.

/* evitar */
/* directives.js */

angular
.module('app.widgets')

/* directiva de rdenes que es especfica del mdulo de rdenes*/


.directive('orderCalendarRange', orderCalendarRange)

/* directiva de ventas que puede ser usada en algn otro lado a lo


largo de la aplicacin de ventas */
.directive('salesCustomerInfo', salesCustomerInfo)

/* directiva de spinner que puede ser usada a lo largo de las


aplicaciones */
.directive('sharedSpinner', sharedSpinner);

function orderCalendarRange() {
/* detalles de implementacin */
}

function salesCustomerInfo() {
/* detalles de implementacin */
}

function sharedSpinner() {
/* detalles de implementacin */
}
/* recomendado */
/* calendarRange.directive.js */

/**
* @desc directiva de rdenes que es especfica al mdulo de rdenes en la
compaa Acme
* @example <div acme-order-calendar-range></div>
*/
angular
.module('sales.order')
.directive('acmeOrderCalendarRange', orderCalendarRange);

function orderCalendarRange() {
/* detalles de implementacin */
}
/* recomendado */
/* customerInfo.directive.js */

/**
* @desc directiva de ventas que puede ser usada a lo largo de la
aplicacin de ventas en la compaa Acme
* @example <div acme-sales-customer-info></div>
*/
angular
.module('sales.widgets')
.directive('acmeSalesCustomerInfo', salesCustomerInfo);

function salesCustomerInfo() {
/* detalles de implementacin */
}
/* recomendado */
/* spinner.directive.js */

/**
* @desc directiva de spinner que puede ser usada a lo largo de las
aplicaciones en la compaa Acme
* @example <div acme-shared-spinner></div>
*/
angular
.module('shared.widgets')
.directive('acmeSharedSpinner', sharedSpinner);

function sharedSpinner() {
/* detalles de implementacin */
}

Nota: Hay muchas formas de llamar a las directivas, especialmente cuando


pueden ser usadas en mbitos especficos. Elige un nombre que tenga
sentido para la directiva y que su archivo sea distintivo y claro. Hemos visto
algunos ejemplos antes, pero veremos ms en la seccin de cmo nombrar.

Manipula el DOM en una Directiva

[Style Y072]

Cuando manipules DOM directamente, usa una directiva. Si hay alguna


alternativa como usando CSS para cambiar los estilos o los animation
services, Angular templating, ngShow o ngHide, entonces salos en su lugar.
Por ejemplo, si la directiva slo muestra o esconde elementos, usa
ngHide/ngShow.

Por qu?: Manipular el DOM puede ser difcil de testear, debugear y


normalmente hay mejores maneras (e.g. CSS, animations, templates)

Provee un Prefijo nico de Directiva


[Style Y073]

Proporciona un prefijo corto, nico y descriptivo


como acmeSalesCustomerInfo que se declare en el HTML como acme-sales-
customer-info.
Por qu?: El prefijo corto y nico identifica el contexto de la directiva y el
origen. Por ejemplo el prefijo cc- puede indicar que la directiva en particular
es parte de la aplicacin CodeCamper, mientras que acme- pudiera indicar
que la directiva es de la compaa Acme.
Nota: Evita ng- ya que est reservado para las directivas AngularJS. Estudia
sabiamente las directivas usadas para evitar conflictos de nombres,
como ion- de Ionic Framework.

Limitate a Elementos y Atributos

[Style Y074]

Cuando crees directivas que tengan sentido como elemento,


restringe E (elemento personalizado) y opcionalmente restringe A (atributo
personalizado). Generalmente, si puede ser su control propio, E es apropiado,
La pauta general es permitir EA pero intenta implementarlo como un
elemento cuando sea un elemento nico y como un atributo cuando aada
mejoras a su propio elemento existente en el DOM.

Por qu?: Tiene sentido.

Por qu?: Mientras permitamos que una directiva sea usada como una clase,
si esa directiva realmente est actuando como un elemento, tiene sentido
que sea un elemento, o al menos un atributo.

Nota: En Angular 1.3+ EA es el valor por defecto

<!-- evitar -->


<div class="my-calendar-range"></div>
/* evitar */
angular
.module('app.widgets')
.directive('myCalendarRange', myCalendarRange);

function myCalendarRange() {
var directive = {
link: link,
templateUrl: '/template/is/located/here.html',
restrict: 'C'
};
return directive;

function link(scope, element, attrs) {


/* */
}
}
<!-- recomendado -->
<my-calendar-range></my-calendar-range>
<div my-calendar-range></div>
/* recomendado */
angular
.module('app.widgets')
.directive('myCalendarRange', myCalendarRange);

function myCalendarRange() {
var directive = {
link: link,
templateUrl: '/template/is/located/here.html',
restrict: 'EA'
};
return directive;

function link(scope, element, attrs) {


/* */
}
}

Directivas y ControllerAs

[Style Y075]

Usa la sintaxis controller as con una directiva para ser consistente con el
uso de controller as con los pares de vista y controlador.

Por qu?: Tiene sentido y no es difcil.

Nota: La siguiente directiva demuestra algunas de las formas en las que


puedes usar el scope dentro del link y el controlador de una directiva,
usando controllerAs. He puesto la template para dejarlo todo en un lugar.

Nota: En cuanto a la inyeccin de dependencias, mira Identificar


Dependencias Manualmente.

Nota: Ntese que el controlador de la directiva est fuera del closure de la


directiva. Este estilo elimina los problemas que genera la inyeccin de
dependencias donde la inyeccin es creada en un cdigo no alcanzable
despus del return.
<div my-example max="77"></div>
angular
.module('app')
.directive('myExample', myExample);

function myExample() {
var directive = {
restrict: 'EA',
templateUrl: 'app/feature/example.directive.html',
scope: {
max: '='
},
link: linkFunc,
controller: ExampleController,
controllerAs: 'vm',
bindToController: true // porque el scope is aislado
};

return directive;

function linkFunc(scope, el, attr, ctrl) {


console.log('LINK: scope.min = %s *** should be undefined',
scope.min);
console.log('LINK: scope.max = %s *** should be undefined',
scope.max);
console.log('LINK: scope.vm.min = %s', scope.vm.min);
console.log('LINK: scope.vm.max = %s', scope.vm.max);
}
}

ExampleController.$inject = ['$scope'];

function ExampleController($scope) {
// Inyectando el $scope solo para comparacin
var vm = this;

vm.min = 3;

console.log('CTRL: $scope.vm.min = %s', $scope.vm.min);


console.log('CTRL: $scope.vm.max = %s', $scope.vm.max);
console.log('CTRL: vm.min = %s', vm.min);
console.log('CTRL: vm.max = %s', vm.max);
}
<!-- example.directive.html -->
<div>hello world</div>
<div>max={{vm.max}}<input ng-model="vm.max"/></div>
<div>min={{vm.min}}<input ng-model="vm.min"/></div>

[Style Y076]

Usa bindToController = true cuando uses controller as con una directiva


cuando quieras asociar el scope exterior al scope del controller de la
directiva.
Por qu?: Lo hace ms fcil a la hora de asociar el scope exterior al scope
del controlador de la directiva.

Nota: bindToController fue introducido en Angular 1.3.0.


<div my-example max="77"></div>
angular
.module('app')
.directive('myExample', myExample);

function myExample() {
var directive = {
restrict: 'EA',
templateUrl: 'app/feature/example.directive.html',
scope: {
max: '='
},
controller: ExampleController,
controllerAs: 'vm',
bindToController: true
};

return directive;
}

function ExampleController() {
var vm = this;
vm.min = 3;
console.log('CTRL: vm.min = %s', vm.min);
console.log('CTRL: vm.max = %s', vm.max);
}
<!-- example.directive.html -->
<div>hello world</div>
<div>max={{vm.max}}<input ng-model="vm.max"/></div>
<div>min={{vm.min}}<input ng-model="vm.min"/></div>
Volver arriba

Resolviendo Promesas en un Controlador

Promesas de Activacin de un Controlador

[Style Y080]

Resuelve la lgica de inicializacin de un controlador en una


funcin activate.

Por qu?: Colocar la lgica de inicializacin en un lugar consistente del


controlador lo hace ms fcil de localizar, ms consistente de testear, y
ayuda a evitar que la lgica de activacin se propage a lo largo del
controlador.

Por qu?: El activate del controlador hace que la lgica para refrescar el
controlador/Vista sea reutilizable, mantiene la lgica junta, lleva el usuario a
la Vista ms rpido, hace las animaciones ms fciles en ng-view o ui-view y
lo hace ms rpido a la vista del usuario.

Nota: Si necesitas condicionalmente cancelar la ruta antes de empezar el


controller, usa en su lugar route resolve.

/* evitar */
function Avengers(dataservice) {
var vm = this;
vm.avengers = [];
vm.title = 'Avengers';

dataservice.getAvengers().then(function(data) {
vm.avengers = data;
return vm.avengers;
});
}
/* recomendado */
function Avengers(dataservice) {
var vm = this;
vm.avengers = [];
vm.title = 'Avengers';

activate();

////////////

function activate() {
return dataservice.getAvengers().then(function(data) {
vm.avengers = data;
return vm.avengers;
});
}
}

Resolucin de Promesas en la Ruta

[Style Y081]

Cuando un controlador depende en una promesa a ser resuelta antes de


que el controlador se active, resuelve esas dependencias en
el $routeProvider antes de que la lgica del controlador sea ejecutada. Si
necesitas condicionalmente cancelar una ruta antes de que el controlador
sea activado, usa un route resolver.

Usa un route resolver cuando decidas cancelar la ruta antes de hacer la


transicin a la Vista.

Por qu?: Un controlador puede requerir datos antes de que se cargue. Esos
datos deben venir desde una promesa a travs de una fbrica o de $http.
Usando un route resolve permite que la promesa se resuelva antes de que la
lgica del controlador se ejecute, as puedes tomar decisiones basndote en
los datos de la promesa.

Por qu?: El cdigo se ejecuta despus de la ruta y la funcin activate del


controlador. La Vista empieza a cargar al instante. El bindeo de los datos se
ejecutan cuando la promesa del activate se resuelva. Una animacin de
"Cargando" se puede mostrar durante la transicin de la vista (via ng-view o
ui-view)

Nota: El cdigo se ejecuta antes que la ruta mediante una promesa.


Rechazar la promesa cancela la ruta. Resolverla hace que la nueva vista
espere a que la ruta sea resuelta. Una animacin de "Cargando" puede ser
mostrada antes de que se resuelva. Si quieres que la Vista aparezca ms
rpido y no necesitas un checkpoint para decidir si puedes mostrar o no la
view, considera la tcnica controller activate.
/* evitar */
angular
.module('app')
.controller('Avengers', Avengers);

function Avengers(movieService) {
var vm = this;
// sin resolver
vm.movies;
// resulta asincronamente
movieService.getMovies().then(function(response) {
vm.movies = response.movies;
});
}
/* better */

// route-config.js
angular
.module('app')
.config(config);

function config($routeProvider) {
$routeProvider
.when('/avengers', {
templateUrl: 'avengers.html',
controller: 'Avengers',
controllerAs: 'vm',
resolve: {
moviesPrepService: function(movieService) {
return movieService.getMovies();
}
}
});
}

// avengers.js
angular
.module('app')
.controller('Avengers', Avengers);

Avengers.$inject = ['moviesPrepService'];
function Avengers(moviesPrepService) {
var vm = this;
vm.movies = moviesPrepService.movies;
}

Nota: El siguiente ejemplo muestra una ruta que cuando se resuelve apunta
a una funcin, hacindolo ms fcil de debugear y ms fcil de manejar la
inyeccin de dependencias.

/* even better */

// route-config.js
angular
.module('app')
.config(config);

function config($routeProvider) {
$routeProvider
.when('/avengers', {
templateUrl: 'avengers.html',
controller: 'Avengers',
controllerAs: 'vm',
resolve: {
moviesPrepService: moviesPrepService
}
});
}

function moviePrepService(movieService) {
return movieService.getMovies();
}

// avengers.js
angular
.module('app')
.controller('Avengers', Avengers);
Avengers.$inject = ['moviesPrepService'];
function Avengers(moviesPrepService) {
var vm = this;
vm.movies = moviesPrepService.movies;
}
Nota: El cdigo del ejemplo de dependencia en movieService no se puede
minimizar tal cual. Para detalles en cmo hacer este cdigo sea minimizable,
mira la seccin en inyeccin de dependencias y en minimizacin y anotacin.
Volver arriba

Anotacin Manual para la Inyeccin de


Dependencias

Insegura despus de la Minificacin

[Style Y090]

Evita usar la sintaxis acortada para declarar dependencias sin usar algn
mtodo que permita minificacin.

Por qu?: Los parmetros al componente (e.g. controller, factory, etc) se


convertirn en variables acortadas. Por ejemplo, common y dataservice se
convertirn a o b y no sern encontradas por AngularJS.
/* evitar - not minification-safe*/
angular
.module('app')
.controller('Dashboard', Dashboard);

function Dashboard(common, dataservice) {


}

Este cdigo acortar las variables cuando se minimice y causar errores en


tiempo de ejecucin.

/* evitar - not minification-safe*/


angular.module('app').controller('Dashboard', d);function d(a, b) { }

Identifica Dependencias Manualmente

[Style Y091]

Usa $inject Para identificar manualmente las dependencias de tus


componentes AngularJS.
Por qu?: Esta tcnica es la misma que se usa con ng-annotate, la cul
recomiendo para automatizar la creacin de dependencias minificadas de
forma segura. Si ng-annotate detecta que la inyeccin ha sido hecha, no la
duplicar.
Por qu?: Esto salvaguarda tus dependencias de ser vulnerables de
problemas a la hora de minimizar cuando los parmetros se acorten. Por
ejemplo, common y dataservice se convertirn a o b y no sern encontradas
por AngularJS.

Por qu?: Evita crear dependencias en lnea, ya que las listas largas pueden
ser difcil de leer en el arreglo. Tambin puede ser confuso que el arreglo
sea una serie de cadenas mientras que el ltimo componente es una funcin.

/* evitar */
angular
.module('app')
.controller('Dashboard',
['$location', '$routeParams', 'common', 'dataservice',
function Dashboard($location, $routeParams, common,
dataservice) {}
]);
/* evitar */
angular
.module('app')
.controller('Dashboard',
['$location', '$routeParams', 'common', 'dataservice', Dashboard]);

function Dashboard($location, $routeParams, common, dataservice) {


}
/* recomendado */
angular
.module('app')
.controller('Dashboard', Dashboard);

Dashboard.$inject = ['$location', '$routeParams', 'common',


'dataservice'];

function Dashboard($location, $routeParams, common, dataservice) {


}

Nota: Cuando tu funcin est debajo de un return, $inject puede ser


inalcanzable (esto puede pasar en una directiva). Puedes solucionarlo
moviendo el $inject encima del return o usando la sintaxis de arreglo para
inyectar.

Nota: ng-annotate 0.10.0 introduce una funcionalidad donde


mueve $inject donde es alcanzable.
// dentro de la definicin de una directiva
function outer() {
return {
controller: DashboardPanel,
};

DashboardPanel.$inject = ['logger']; // Inalcanzable


function DashboardPanel(logger) {
}
}
// dentro de la definicin de una directiva
function outer() {
DashboardPanel.$inject = ['logger']; // alcanzable
return {
controller: DashboardPanel,
};

function DashboardPanel(logger) {
}
}

Identifica Manualmente Dependencias del Route Resolver

[Style Y092]

Usa $inject para identificar manualmente las dependencias de tu route


resolver para componentes de AngularJS.

Por qu?: Esta tcnica separa la funcin annima para el route resolver,
haciendola ms fcil de leer.

Por qu?: Una declaracin $inject puede ser fcilmente preceder al route
resolver para hacer cualquier minificacin de dependencias segura.
/* recomendado */
function config($routeProvider) {
$routeProvider
.when('/avengers', {
templateUrl: 'avengers.html',
controller: 'Avengers',
controllerAs: 'vm',
resolve: {
moviesPrepService: moviePrepService
}
});
}

moviePrepService.$inject = ['movieService'];
function moviePrepService(movieService) {
return movieService.getMovies();
}
Volver arriba
Minificacin y Anotacin

ng-annotate

[Style Y100]

Usa ng-annotate para Gulp or Grunt y comenta funciones que necesiten


inyeccin de dependencias automatizadas usando /** @ngInject */

Por qu?: Salvaguarda tu cdigo de cualquier dependencia que pueda no


estar usando prcticas de minificacin segura.

Por qu?: ng-min est obsoleto


Yo prefiero Gulp porque siento que es ms fcil de escribir, leer, y debugear.

El siguiente cdigo no est usando minificacin de dependencias segura.

angular
.module('app')
.controller('Avengers', Avengers);

/* @ngInject */
function Avengers(storageService, avengerService) {
var vm = this;
vm.heroSearch = '';
vm.storeHero = storeHero;

function storeHero() {
var hero = avengerService.find(vm.heroSearch);
storageService.save(hero.name, hero);
}
}
Cuando el cdigo de arriba es ejecutado a travs de ng-annotate producir
la siguiente salida con la anotacin $injecty ser seguro para ser minificado.
angular
.module('app')
.controller('Avengers', Avengers);

/* @ngInject */
function Avengers(storageService, avengerService) {
var vm = this;
vm.heroSearch = '';
vm.storeHero = storeHero;

function storeHero() {
var hero = avengerService.find(vm.heroSearch);
storageService.save(hero.name, hero);
}
}

Avengers.$inject = ['storageService', 'avengerService'];


Nota: Si ng-annotate detecta que la inyeccin ya ha sido hecha
(e.g. @ngInject fu detectado), no duplicar el cdigo de $inject.
Nota: Al usar un route resolver puedes prefijar a la funcin del resolver
con /* @ngInject */ y producir cdigo propiamente anotado, manteniendo
cualquier inyeccin de dependencias segura para ser minificada.
// Using @ngInject annotations
function config($routeProvider) {
$routeProvider
.when('/avengers', {
templateUrl: 'avengers.html',
controller: 'Avengers',
controllerAs: 'vm',
resolve: { /* @ngInject */
moviesPrepService: function(movieService) {
return movieService.getMovies();
}
}
});
}
Nota: A partir de Angular 1.3 usa el prametro ngStrictDi de la
directiva ngApp. Al presentarse el injector ser creado en modo "strict-di"
causando que la aplicacin falle al invocar funciones que no usan
explcitamente anotacin de funciones (stas podran no estar minificadas
en forma segura). Informacin para debugear ser mostrada en la consola
para ayudar a rastrear el cdigo infractor. <body ng-app="APP" ng-strict-di>

Usa Gulp o Grunt para ng-annotate

[Style Y101]

Usa gulp-ng-annotate o grunt-ng-annotate en una tarea de construccin


automtica. Inyecta /* @ngInject */ antes de cualquier funcin que tenga
dependecias.
Por qu?: ng-annotate atrapar la mayora de las dependencias, pero
algunas veces requiere indicios usando la sintaxis /* @ngInject */.

El cdigo siguiente es un ejemplo de una tarea de Gulp que usa ngAnnotate

gulp.task('js', ['jshint'], function() {


var source = pkg.paths.js;
return gulp.src(source)
.pipe(sourcemaps.init())
.pipe(concat('all.min.js', {newLine: ';'}))
// Agrega la notacin antes de ofuscar para que el cdigo sea
minificicado apropiadamente.
.pipe(ngAnnotate({
// true ayuda a aadir @ngInject donde no es usado. Infiere.
// No funciona con resolve, as que tenemos que ser
explcitos en ese caso
add: true
}))
.pipe(bytediff.start())
.pipe(uglify({mangle: true}))
.pipe(bytediff.stop())
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest(pkg.paths.dev));
});
Volver arriba

Manejo de Excepciones

Decoradores

[Style Y110]

Usa un decorador o decorator, en tiempo de configuracin usando el


servicio $provide, en el servicio $exceptionHandler para realizar acciones
personalizadas cuando una excepcin ocurra.

Por qu?: Provee una manera consistente de manejar excepciones de


Angular que no estn siendo capturadas en tiempo de desarrollo o en
tiempo de ejecucin.

Nota: Otra opcin es sobreescribir el servicio en lugar de usar un decorador.


Esto est bien, pero si quiere mantener el comportamiento por default y
extenderlo se recomienda usar un decorador.

/* recomendado */
angular
.module('blocks.exception')
.config(exceptionConfig);

exceptionConfig.$inject = ['$provide'];

function exceptionConfig($provide) {
$provide.decorator('$exceptionHandler', extendExceptionHandler);
}

extendExceptionHandler.$inject = ['$delegate', 'toastr'];


function extendExceptionHandler($delegate, toastr) {
return function(exception, cause) {
$delegate(exception, cause);
var errorData = {
exception: exception,
cause: cause
};
/**
* Pudieramos agregar el error a la coleccin de un servicio,
* agregar los errores en el $rootScope,
* logear los errores a un servidor remoto,
* o logear localmente. O arrojarlos llanamente. Dependende
totalmente de t.
* arrojar excepcin;
*/
toastr.error(exception.msg, errorData);
};
}

Cachadores de Excepciones

[Style Y111]

Crea una fbrica que exponga una interfaz para cachar y manejar
excepciones elegantemente.

Por qu?: Provee de una manera consistente de cachar excepciones que


puedan ser arrojadas en tu cdigo (e.g. durante llamadas XHR o promesas
que fallaron).

Nota: El cachador de excepciones es bueno para cachar y reaccionar a


excepciones especficas de llamadas que tu sabes van a arrojar una. Por
ejemplo, al hacer una llamada XHR para obtener datos desde un servicio
web remoto y quieres cachar cualquier excepcin de ese servicio y
reaccionar nicamente.

/* recomendado */
angular
.module('blocks.exception')
.factory('exception', exception);

exception.$inject = ['logger'];

function exception(logger) {
var service = {
catcher: catcher
};
return service;
function catcher(message) {
return function(reason) {
logger.error(message, reason);
};
}
}

Errores de Ruta

[Style Y112]

Maneja y logea todos los errores de enrutamiento usando $routeChangeError.

Por qu?: Provee una manera consistente de manejar todos los errores de
enrutamiento.

Por qu?: Potencialmente provee una mejor experiencia de usuario si un


error de enrutamiento ocurre y tu los rediriges a una pantalla amigable con
ms detalles u opciones de recuperacin.

/* recomendado */
function handleRoutingErrors() {
/**
* Route cancellation:
* Cancelacin de la Ruta:
* En un error de ruteo, ir al dashboard.
* Proveer una clusula de salida si trata de hacerlo dos veces.
*/
$rootScope.$on('$routeChangeError',
function(event, current, previous, rejection) {
var destination = (current && (current.title || current.name
|| current.loadedTemplateUrl)) ||
'unknown target';
var msg = 'Error routing to ' + destination + '. ' +
(rejection.msg || '');
/**
* Optionally log using a custom service or $log.
* Opcionalmente logear usando un servicio personalizado o
$log.
* (Don't forget to inject custom service)
*/
logger.warning(msg, [current]);
}
);
}
Volver arriba

Cmo Nombrar
Pautas para nombrar

[Style Y120]

Usa nombres consistentes para todos los componentes siguiendo un patrn


que describa las caractersticas del componente y despus (opcionalmente)
su tipo. Mi patrn recomendado es feature.type.js. Hay dos nombres para
la mayora de los assets:
o el nombre del archivo (avengers.controller.js)
o el nombre del componente registrado en Angular (AvengersController)

Por qu?: Las pautas de como nombrar nos ayudan a proveer una manera
consistente para encontrar contenido en un vistazo. La Consistencia es vital
dentro del proyecto. La Consistencia es importante dentro de un equipo. La
Consistencia a lo largo de una compaa provee de una tremenda eficacia.

Por qu?: Las pautas para nombrar deberan simplemente ayudarte a


encontrar tu cdigo rpidamente y hacerlo ms fcil de entender.

Nombres de Archivo para Caracterstica

[Style Y121]

Usa nombres consistentes para todos los componentes siguiendo un patrn


que describa la caracterstica o feature del componente y despus
(opcionalmente) su tipo. Mi patrn recomendado es feature.type.js.

Por qu?: Provee de una manera consistente para identificar componentes


rpidamente.

Por qu?: Provee un patrn de coincidencia para tareas automatizadas.

/**
* opciones comunes
*/

// Controladores
avengers.js
avengers.controller.js
avengersController.js

// Servicios/Factories
logger.js
logger.service.js
loggerService.js
/**
* recomendado
*/

// controllers
avengers.controller.js
avengers.controller.spec.js

// servicios/factories
logger.service.js
logger.service.spec.js

// constantes
constants.js

// definicin de mdulos
avengers.module.js

// rutas
avengers.routes.js
avengers.routes.spec.js

// configuracin
avengers.config.js

// directivas
avenger-profile.directive.js
avenger-profile.directive.spec.js
Nota: Otra convencin comn es nombrar archivos de controladores sin la
palabra controller en el archivo tal como avengers.js en lugar
de avengers.controller.js. Todas las dems convenciones todava usan un
sufijo del tipo. Los Controladores son el tipo ms comn de componente as
que esto nos ahorra escribir y an es fcilmente identificable. Yo
recomiendo que elijas 1 convencin y seas consistente dentro de tu equipo.
/**
* recomendado
*/
// Controladores
avengers.js
avengers.spec.js

Nombres de Archivos de Prueba

[Style Y122]

Nombra especificaciones de pruebas de manera similar a la del componente


que estn probando con un sufijo de spec.
Por qu?: Provee de una manera consistente de identificar componentes
rpidamente.

Por qu?: Provee de un patrn de coincidencia para karma u otros test


runners.

/**
* recomendado
*/
avengers.controller.spec.js
logger.service.spec.js
avengers.routes.spec.js
avenger-profile.directive.spec.js

Nombres de Controladores

[Style Y123]

Usa nombres consistentes para todos los controladores nombrados a partir


de lo que hacen. Usa UpperCamelCase para controladores, ya que son
constructores.

Por qu?: Provee de una manera consistente de identificar y referenciar


controladores rpidamente.

Por qu?: UpperCamelCase es convencional para identificar objetos que


pueden ser instanciados usando un constructor.

/**
* recomendado
*/

// avengers.controller.js
angular
.module
.controller('HeroAvengers', HeroAvengers);

function HeroAvengers() { }

Sufijo para el Nombre del Controlador

[Style Y124]

Agrega el sufijo Controller al nombre del controlador o djalo sin sufijo.


Escoge 1, no uses ambos.
Por qu?: El sufijo Controller es usado ms comnmente y es ms
descriptivo explcitamente.

Por qu?: Omitir el sufijo es ms breve y el controlador es fcilmente


identificable ms seguido incluso sin el sufijo.

/**
* recomendado: Opcin 1
*/

// avengers.controller.js
angular
.module
.controller('Avengers', Avengers);

function Avengers() { }
/**
* recomendado: Opcin 2
*/

// avengers.controller.js
angular
.module
.controller('AvengersController', AvengersController);

function AvengersController() { }

Nombres de Fbricas

[Style Y125]

Usa nombres consistentes para todas las fbricas nombradas a partir de lo


que hacen. Usa camel-casing para los servicios y las fbricas.

Por qu?: Provee una manera consistente de identificar y referenciar


fbricas rpidamente.

/**
* recomendado
*/

// logger.service.js
angular
.module
.factory('logger', logger);

function logger() { }

Nombres para Directivas


[Style Y126]

Usa nombres consistentes para todas las directivas usando camel-case. Usa
un prefijo corto para describir el rea a la que la directiva pertenece
(algunos ejemplos son un prefijo segn la compaa o un prefijo segn el
proyecto).

Por qu?: Provee una manera consistente de identificar y referenciar


componentes rpidamente.

/**
* recomendado
*/

// avenger-profile.directive.js
angular
.module
.directive('xxAvengerProfile', xxAvengerProfile);

// el uso es <xx-avenger-profile> </xx-avenger-profile>

function xxAvengerProfile() { }

Mdulos

[Style Y127]

Cuando haya mltiples mdulos, el archivo del mdulo principal es


nombrado app.module.js mientras que otros mdulos que dependan de l
son nombrados a partir de lo que ellos representan. Por ejemplo, un mdulo
de admin es nombrado admin.module.js. Los nombres de los mdulos
registrados sern respectivamente app y admin.

Por qu?: Provee consistencia para mltiples mdulos de aplicacin, y para


poder expandirse a aplicaciones ms grandes.

Por qu?: Provee una manera fcil de usar tareas automatizadas para cargar
todas las definiciones de mdulos primero, y luego todos los otros archivos
de angular (agrupacin).

Configuracin
[Style Y128]

Separa la configuracin de un mdulo en un archivo propio nombrado a


partir del nombre del mdulo. Un archivo de configuracin para el mdulo
principal de app es nombrado app.config.js (o simplemente config.js). La
configuracin para un mdulo llamado admin.module.js es
nombrada admin.config.js.

Por qu?: Separa la configuracin de la definicin del mdulo,


componentes y cdigo activo.

Por qu?: Provee un lugar identificable para establecer configuracin para


un mdulo.

Rutas

[Style Y129]

Separa la configuracin de la ruta en un archivo propio. Algunos ejemplos pueden


ser app.route.js para el mdulo principal y admin.route.js para el mdulo
admin admin. Incluso en aplicaciones pequeas prefiero esta separacin del resto
de la configuracin.

Volver arriba

Estructura de la Aplicacin El Principio LIFT

LIFT

[Style Y140]

Estructura tu aplicacin de tal manera que puedas Localizar (Locate) tu


cdigo rpidamente, Identificar (Identify) el cdigo de un vistazo, mantener
la estructura ms plana (Flattest) que puedas, y Trata (Try) de mantenerte
DRY. La estructura debe de seguir estas 4 pautas bsicas.

Por qu LIFT?: Provee una estructura consistente que escala bien, es


modular, y hace ms fcil incrementar la eficiencia de los desarrolladores al
encontrar cdigo rpidamente. Otra manera de checar la estructura de tu
aplicacin es preguntarte a ti mismo: Qu tan rpido puede abrir y trabajar
en todos los archivos relacionados a una caracteristica?

Cuando encuentro que mi estructura no se siente cmoda, regreso y reviso


estas pautas LIFT

i. Locating - Localizar nuestro cdigo es fcil


ii. Identify - Identificar cdigo de un vistazo
iii. Flat - Estructura plana tanto como sea posible
iv. Try - Tratar de mantenerse DRY (Don't Repeat Yourself) or T-DRY

Localizar

[Style Y141]

Has que la localizacin tu cdigo sea intuitivo, simple y rpido.

Por qu?: Encuentro que esto es super importante para un proyecto. Si el


equipo no puede encontrar los archivos en los que necesita trabajar
rpidamente, no podrn trabajar tan eficientemente como sea posible, y la
estructura necesita cambiar. Puede que no conozcas el nombre del archivo o
donde estn sus archivos relacionados, as que ponindolos en las
locaciones ms intuitivas y cerca de los otros ahorra mucho tiempo. Una
estructura de directorios descriptiva puede ayudar con esto.

/bower_components
/client
/app
/avengers
/blocks
/exception
/logger
/core
/dashboard
/data
/layout
/widgets
/content
index.html
.bower.json

Identificar
[Style Y142]

Cuando miras en un archivo deberas saber instantneamente qu contiene


y qu representa.

Por qu?: Gastas menos tiempo buscando y urgando por cdigo, y es ms


eficiente. Si esto significa que quieres nombres de archivos ms largos,
entonces que as sea. Se descriptivo con los nombres de los archivos y
mantn el contenido del archivo a exactamente 1 componente. Evita
archivos con mltiples controladores, o una mezcla. Hay excepciones a la
regla de 1 por archivo cuando tengo un conjunto de pequeas features que
estn relacionadas unas con otras, an as son fcilmente identificables.

Estructura Plana

[Style Y143]

Mantn una estructura de directorios plana tanto como sea posible. Cuando
llegues a un total de 7+ archivos, comienza a considerar separacin.

Por qu?: Nadie quiere buscar en 7 niveles de directorios por un arhivo.


Piensa en los mens de los sitios web cualquiera ms profundo que 2
debera ser seriamente considerado. En una estructura de directorios no hay
una regla dura o rpida en cuanto a un nmero, pero cuando un directorio
tiene de 7 a 10 archivos, tal vez ese sea el momento para empezar a crear
subdirectorios. Bsate en tu nivel de confort. Usa una estructura ms plana
hasta que haya un valor obvio (para ayudar al resto de LIFT) en crear un
nuevo directorio.

T-DRY (Try to Stick to DRY - Trata de Apegarte a DRY)

[Style Y144]

Se DRY, pero no te vuelvas loco y sacrifiques legibilidad.

Por qu?: Ser DRY es importante, pero no crucial si sacrifica otras partes de
LIFT, es por eso que lo llamo T-DRY. No quiero escribir session-view.html
por una vista porque, obviamente es una vista. Si no es obvio o por
convencin, entonces la nombro as.

Volver arriba
Estructura de la Aplicacin

Pautas Universales

[Style Y150]

Ten una visin de implementacin de corto y largo plazo. En otras palabras,


empieza con poco pero ten en mente hacia donde se dirige la aplicacin.
Todo el cdigo de la aplicacin va en el directorio raz llamado app. Todo el
contenido es separado en 1 caracterstica por archivo. Cada controlador,
servicio, mdulo, vista tiene su propio archivo. Todos los vendor scripts de
terceros son almacenados en otro directorio raz y no en el directorio app. Si
yo no lo escrib no los quiero saturando mi aplicacin
(bower_components, scripts, lib).

Nota: Encuentra ms detalles y el razonamiento detrs de esta estructura


en este post original sobre la estructura de una aplicacin.

Layout

[Style Y151]

Coloca los componentes que definen el layout universal de la aplicacin en


un directorio llamado layout. Estos pueden incluir una vista caparazn y un
controlador que acten como un contenedor para la aplicacin, navegacin,
mens, reas de contenido, y otras regiones.

Por qu?: Organiza todo el layout en un lugar nico reusado a lo largo de la


aplicacin.

Estructura de Carpetas-por-Caracterstica

[Style Y152]

Crea carpetas llamadas de acuerdo al caracterstica que representan. Cuando


una carpeta crezca para contener ms de 7 archivos, comienza a considerar
la creacin de una carpeta para ellos. Tu lmite puede ser diferente, as que
ajusta de acuerdo a tus necesidades.
Por qu?: Un desarrollador puede localizar el cdigo, identificar cada qu
representa cada archivo de un vistazo, la estructura es tan plana como
puede ser, y no hay nombres repetidos o redundantes.

Por qu?: Las pautas LIFT estarn cubiertas.

Por qu?: Ayuda a evitar que la aplicacin se sature a travs de organizar el


contenido y conservarlo alineado con las pautas LIFT.

Por qu?: Cuando hay demasiados archivos (10+) localizarlos es ms fcil


con una estructura de directorios consistente y ms difciles en una
estructura plana.

/**
* recomendado
*/

app/
app.module.js
app.config.js
app.routes.js
components/
calendar.directive.js
calendar.directive.html
user-profile.directive.js
user-profile.directive.html
layout/
shell.html
shell.controller.js
topnav.html
topnav.controller.js
people/
attendees.html
attendees.controller.js
speakers.html
speakers.controller.js
speaker-detail.html
speaker-detail.controller.js
services/
data.service.js
localstorage.service.js
logger.service.js
spinner.service.js
sessions/
sessions.html
sessions.controller.js
session-detail.html
session-detail.controller.js
Nota: No estructures tu aplicacin usando directorios-por-tipo. Esto
requiere mover mltiples directorios cuando se est trabajando en una
caracterstica y se vuelve difcil de manejar conforme la aplicacin crece a 5,
10 o 25+ vistas y controladores (y otras caractersticas), lo que lo hace ms
difcil que localizar archivos en una aplicacin estructura en directorios-por-
caracterstica.

/*
* evita
* Alternativa directorios-por-tipo
* Yo recomiendo "directorios-por-caracterstica", en su lugar.
*/

app/
app.module.js
app.config.js
app.routes.js
controllers/
attendees.js
session-detail.js
sessions.js
shell.js
speakers.js
speaker-detail.js
topnav.js
directives/
calendar.directive.js
calendar.directive.html
user-profile.directive.js
user-profile.directive.html
services/
dataservice.j
localstorage.js
logger.js
spinner.js
views/
attendees.html
session-detail.html
sessions.html
shell.html
speakers.html
speaker-detail.html
topnav.html
Volver arriba

Modularidad

Muy Pequeos, Mdulos Autocontenidos

[Style Y160]

Crea mdulos pequeos que encapsulen una responsabilidad.

Por qu?: Aplicaciones modulares hace ms fcil el plug and go ya que


permiten a los equipos de desarrollo construir porciones verticales de la
aplicacin y lanzarlas incrementalmente. Esto significa que podemos
conectar nuevas caractersticas conforme las desarrollamos.

Crea un Mdulo App

[Style Y161]

Crea una mdulo raz de aplicacin cuyo rol sea unir todos los mdulos y
caractersticas de tu aplicacin. Nombra ste de acuerdo a tu aplicacin.

Por qu?: Angular incentiva la modularidad y patrones de separacin. Crear


un mdulo raz de aplicacin cuyo rol es atar otros mdulos juntos provee
una manera muy directa de agregar o remover mdulos de tu aplicacin.

Mantn el Mdulo App Delgado

[Style Y162]

Solo coloca lgica para unir la aplicacin en el mdulo app. Deja las
caractersticas en sus propios mdulos.
Por qu?: Agregar roles adicionales a la aplicacin raz para obtener datos
remotos, mostrar vistas, u otra lgica no relaciona a la unin de la aplicacin
enturbia el mdulo app y hace ambos conjuntos de caractersticas difciles
de reusar y apagar.

Por qu?: El mdulo app se convierte en el manifiesto que describe qu


mdulos definen la aplicacin.

reas de Features son Mdulos

[Style Y163]

Crea mdulos que representen reas de caractersticas, como el layout,


servicios reusables y compartidos, dashboards, y caractersticas especfics
de la aplicacin (e.g. customers, admin, sales).

Por qu?: Mdulos autocontenidos pueden ser agregados a la aplicacin


con poca o sin ninguna friccin.

Por qu?: Sprints o iteraciones pueden enfocarse en reas de caractersticas


y encendarlas al final del sprint o iteracin.

Por qu?: Separar reas de caractersticas en mdulos hace ms fcil testear


mdulos en aislamiento y reusar cdigo.

Bloques Reusables son Mdulos

[Style Y164]

Crea mdulos que representen bloques de la aplicacin reusables para


servicios cmunes como manejo de excepciones, logeo, diagnstico,
seguridad, y almacenamiento local de datos.

Por qu?: Este tipo de caractersticas son necesarias en muchas aplicaciones,


as que mantenerlas separadas en sus propios mdulos pueden ser
genricas de aplicacin y pueden ser reusadas a lo largo de varias
aplicaciones.

Dependencias de Mdulos
[Style Y165]

El mdulo raz de la aplicacin depende de mdulos de caractersticas


especficas y cualquier mdulo compartido o reusable.

Por qu?: El mdulo principal de la aplicacin contiene un manifiesto


rpidamente identificable de las caractersticas de la aplicacin.
Por qu?: Cada rea de caractersticas contiene un manifiesto de lo que
depende, as que puede ser extrado como dependencia en otras
aplicaciones y seguir funcionando.

Por qu?: Caractersticas internas de la aplicacin como servicios de datos


compartidos se hacen fcil de localizar y compartir desde app.core (elije tu
nombre favorito para este mdulo).

Nota: Esta es una estrategia para consistencia. Hay muy buenas opciones
aqu. Escoge una que sea consistente, que siga las reglas de dependencias
de AngularJS, y que sea fcil de mantener y escalar.

Mis estructuras varan ligeramente entre proyectos pero todas ellas siguen
estas pautas para estructuras y modularidad. La implementacin puede
variar dependiendo de las caractersticas y el equipo. En otras palabras, no te
quedes colgado en una estructura igual pero justifica tu estructura usando
consistencia, mantenibilidad, y eficacia en mente.

En una aplicacin pequea, tambin puedes considerar poner todas las


dependencias compartidas en el mdulo principal dnde los mdulos de
caractersticas no tienen dependencias directas. Esto hace ms fcil
mantener aplicaciones pequeas, pero hace ms difcil el reusar mdulos
fuera de esta aplicacin.
Volver arriba

Lgica de Arranque

Configuracin

[Style Y170]

Inyecta cdigo dentro de module configuration que necesite ser


configurado antes de correr la aplicacin angular. Candidatos ideales
incluyen providers y constantes.

Por qu?: Esto hace ms fcil tener menos lugares para la configuracin.

angular
.module('app')
.config(configure);

configure.$inject =
['routerHelperProvider', 'exceptionHandlerProvider', 'toastr'];

function configure (routerHelperProvider, exceptionHandlerProvider,


toastr) {
exceptionHandlerProvider.configure(config.appErrorPrefix);
configureStateHelper();

toastr.options.timeOut = 4000;
toastr.options.positionClass = 'toast-bottom-right';

////////////////

function configureStateHelper() {
routerHelperProvider.configure({
docTitle: 'NG-Modular: '
});
}
}

Bloques Run

[Style Y171]

Cualquier cdigo que necesite ser ejecutado cuando una aplicacin arranca
debe ser declarado en una fbrica, ser expuesto a travs de una funcin, o
inyectado en el bloque run.

Por qu?: Cdigo que est directamente en un bloque run puede ser difcil
de testear. Colocarlo en una fbrica lo hace fcil de abstraer y mockear.

angular
.module('app')
.run(runBlock);

runBlock.$inject = ['authenticator', 'translator'];

function runBlock(authenticator, translator) {


authenticator.initialize();
translator.initialize();
}
Volver arriba

Servicios Envoltorios $ de Angular

$document y $window
[Style Y180]

Usa $document y $window en lugar de document y window.

Por qu?: Estos servicios son envueltos por Angular y son ms fciles de
testear en lugar de usar document y window en las pruebas. Esto te ayuda a
evitar que tener que mockear document y window tu mismo.

$timeout y $interval

[Style Y181]

Usa $timeout y $interval en lugar de setTimeout y setInterval .

Por qu?: Estos servicios estn envueltos por Angular y son ms fciles de
testear y manejar el ciclo digest de Angular as que mantienen el bindeo de
los datos en sincronizacin.

Volver arriba

Pruebas
Las pruebas unitarias ayudan a mantener el cdigo limpio, as que incluyo algunas
de mis recomendaciones en los fundamentos del testeo unitario con links para
mayor informacin.

Escribe Pruebas con Historias

[Style Y190]

Escribe un conjunto de pruebas para cada historia. Comienza con un test


vaco y llnalo conforme escribas el cdigo para la historia.

Por qu?: Escribir descripciones para la prueba ayuda a definir claramente


qu es lo que tu historia har, qu no har, y cmo puedes medir el xito.

it('should have Avengers controller', function() {


// TODO
});

it('should find 1 Avenger when filtered by name', function() {


// TODO
});
it('should have 10 Avengers', function() {
// TODO (mock data?)
});

it('should return Avengers via XHR', function() {


// TODO ($httpBackend?)
});

// y as

Librera para las Pruebas

[Style Y191]

Usa Jasmine o Mocha para las pruebas unitarias.

Por qu?: Ambas Jasmine y Mocha son usadas ampliamente por la


comunidad de AngularJS. Ambas son estables, bien mantenidas, y proveen
de caractersticas de pruebas robustas.

Nota: Cuando uses Mocha, tambin considera elegir una librera como Chai.

Test Runner

[Style Y192]

Usa Karma como test runner.

Por qu?: Karma es fcil de configurar para correr una vez o


automticamente cuando cambias tu cdigo.

Por qu?: Karma encaja en tu proceso de Integracin Continua fcilmente


por s sola o a travs de Grunt o Gulp.

Por qu?: Algunos IDE's estn comenzando a integrarse con Karma, tal
como WebStorm y Visual Studio.

Por qu?: Karma funciona bien con lderes de automatizacin de tareas


tales como Grunt (con grunt-karma) y Gulp (con gulp-karma).

Stubear y Espar
[Style Y193]

Usa Sinon para el stubeo y espar.

Por qu?: Sinon funciona bien con ambos Jasmine y Mocha y extiende las
caractersticas de stubeo y espo que ellos ofrecen.

Por qu?: Sinon hace ms fcil cambiar entre Jasmine y Mocha, si quieres
probar ambos.

Headless Browser

[Style Y194]

Usa PhantomJS para correr tus pruebas en un servidor.

Por qu?: PhantomJS es un navegador headless que ayuda a correr las


pruebas necesitar una navegador "visual". As que no necesitas instalar
Chrom, Safari u otros navegadores en tu servidor.

Nota: An debes testear en todos los navegadores de tu entorno, as como


sea apropiado para tu audiencia meta.

nalisis de Cdigo

[Style Y195]

Corre JSHint en tus pruebas.

Por qu?: Las pruebas son cdigo. JSHint puede ayudar a identificar
problemas en la calidad del cdigo que pueden causar que tus pruebas
funcionen inapropiadamente.

Mitiga Palabras Globales dentro de las Reglas de JSHint en las


Pruebas

[Style Y196]

Relaja las reglas en tu cdigo de prueba para permitir palabras globales


comnes como describe y expect.
Por qu?: Tus pruebas son cdigo y requieren la misma atencin y reglas de
calidad de cdigo que todo tu cdigo de produccin. Sin embargo, variables
globales usadas por el framework para pruebas, por ejemplo, puede ser
relajado al incluir esto en tus specs de prueba.

/* global sinon, describe, it, afterEach, beforeEach, expect, inject */

Organizando las Pruebas

[Style Y197]

Coloca archivos de pruebas unitarias (specs) lado a lado con tu cdigo del
cliente. Coloca tus specs que cubren la integracin con el servidor o que
prueban mltiples componentes en un directorio tests separado.

Por qu?: Las Pruebas Unitarias tiene una correlacin directa con un
componente y archivo especfico en tu cdigo fuente.

Por qu?: Es ms fcil mantenerlas actualizadas ya que siempre estn a la


vista. Al escribir cdigo ya sea que realices TDD o pruebes durante el
desarrollo o despus del desarrollo, los specs estn lado a lado y nunca
fuera de la vista o de la mente, as es ms probable que sean mantenidas lo
cual ayuda a mantener la cobertura de pruebas.
Por qu?: Cuando actualices cdigo fuente es ms fcil ir y actualizar las
pruebas al mismo tiempo.

Por qu?: Colocarlas lado a lado hace ms fcil encontrarlas y fcil de


moverlas con el cdigo fuente si mueves la fuente.

Por qu?: Tener el spec cerca hace ms fcil al lector del cdigo fuente
aprender cmo se supone que el componente es usado y descubrir sus
propias limitaciones.

Por qu?: Separar specs para que no estn un build de distribucin es fcil
con grunt o gulp.

/src/client/app/customers/customer-detail.controller.js
/customer-detail.controller.spec.js
/customers.controller.spec.js
/customers.controller-detail.spec.js
/customers.module.js
/customers.route.js
/customers.route.spec.js
Volver arriba

Animaciones

Uso

[Style Y210]

Usa sutiles animaciones con AngularJS para hacer transiciones entre estados
en vistas y elementos visuales primarios. Incluye el mdulo ngAnimate. Las 3
claves son sutil, fluido, transparente.

Por qu?: Animaciones sutiles pueden mejorar la Experiencia de Usuario


cuando son usadas apropiadamente.

Por qu?: Animaciones sutiles pueden mejorar el rendimiento percibido


como una transicin de vista.

Sub Segundos
[Style Y211]

Usa duraciones cortas para las animaciones. Yo generalmente empiezo con


300ms y ajusto hasta que es apropiado.

Por qu?: Animaciones largas pueden tener el efecto contrario en la


Experiencia de Usuario y el rendimiento percibido al dar la apariencia de una
aplicacin lenta.

animate.css

[Style Y212]

Usa animate.css para animaciones convencionales.

Por qu?: Las animaciones que animate.css provee son rpidas, fluidas, y
fciles de agregar en tu aplicacin.

Por qu?: Provee consistencia en tus animaciones.

Por qu?: animate.css est ampliamente usado y testeado.

Nota: Ve este excelente post de Matias Niemel sobre animaciones


AngularJS

Volver arriba

Comentarios

jsDoc

[Style Y220]

Si planeas producir documentacin, usa la sintaxis jsDoc para documentar


nombres de funciones, descripcin, parmetros y devoluciones.
Usa @namespace y @memberOf para igualar la estructura de tu aplicacin.

Por qu?: Puedes generar (y regenerar) documentacin desde tu cdigo, en


lugar de escribirla desde cero.

Por qu?: Provee consistencia al usar una herramienta industrial comn.


/**
* Logger Factory
* @namespace Factories
*/
(function() {
angular
.module('app')
.factory('logger', logger);

/**
* @namespace Logger
* @desc Application wide logger
* @memberOf Factories
*/
function logger($log) {
var service = {
logError: logError
};
return service;

////////////

/**
* @name logError
* @desc Logs errors
* @param {String} msg Message to log
* @returns {String}
* @memberOf Factories.Logger
*/
function logError(msg) {
var loggedMsg = 'Error: ' + msg;
$log.error(loggedMsg);
return loggedMsg;
};
}
})();
Volver arriba

JS Hint

Usa un Archivo de Opciones

[Style Y230]

Usa JS Hint para resaltar problemas en tu JavaScript y asegurate de


personalizar el arhivo de opciones de JS Hint e incluirlo en el control de
versiones. Ve los JS Hint docs para detalles sobre estas opciones.
Por qu?: Provee una primera alerta antes de hacer commit de cualquier
cdigo al control de versiones.

Por qu?: Provee consistencia a lo largo de tu equipo.

{
"bitwise": true,
"camelcase": true,
"curly": true,
"eqeqeq": true,
"es3": false,
"forin": true,
"freeze": true,
"immed": true,
"indent": 4,
"latedef": "nofunc",
"newcap": true,
"noarg": true,
"noempty": true,
"nonbsp": true,
"nonew": true,
"plusplus": false,
"quotmark": "single",
"undef": true,
"unused": false,
"strict": false,
"maxparams": 10,
"maxdepth": 5,
"maxstatements": 40,
"maxcomplexity": 8,
"maxlen": 120,

"asi": false,
"boss": false,
"debug": false,
"eqnull": true,
"esnext": false,
"evil": false,
"expr": false,
"funcscope": false,
"globalstrict": false,
"iterator": false,
"lastsemic": false,
"laxbreak": false,
"laxcomma": false,
"loopfunc": true,
"maxerr": false,
"moz": false,
"multistr": false,
"notypeof": false,
"proto": false,
"scripturl": false,
"shadow": false,
"sub": true,
"supernew": false,
"validthis": false,
"noyield": false,

"browser": true,
"node": true,

"globals": {
"angular": false,
"$": false
}
}
Volver arriba

Constantes

Globales de Vendor

[Style Y240]

Crea una Constante de Angular para variables globales en libreras vendor.

Por qu?: Provee una manera de inyectar libreras vendor que de otra
manera son globales. Esto mejora la testeabilidad al permitirte saber ms
fcilmente cules son las dependencias de tus componentes (evita
abstraciones malformadas). Tambin te permite mockear estas
dependencias, cuando tiene sentido.

// constants.js

/* global toastr:false, moment:false */


(function() {
'use strict';

angular
.module('app.core')
.constant('toastr', toastr)
.constant('moment', moment);
})();

[Style Y241]

Usa constantes para valores que no cambian y no vienen de otro servicio.


Cuando las constantes son usadas solo por para un mdulo que pueda ser
reutilizado en mltiples aplicaciones, coloca las constantes en un archivo por
mdulo nombrado a partir del mdulo. Hasta que esto sea requerido,
mantn las constantes en el mdulo principal en un archivo constants.js.

Por qu?: Un valor que puede cambiar, incluso infrecuentemente, debera


ser obtenido desde un servicio as no tendrs que cambiar el cdigo fuente.
Por ejemplo, una url para un servicio de datos puede ser colocada en una
constante pero un mejor lugar sera cargarla desde un servicio web.

Por qu?: Las Constantes pueden ser inyectadas en cualquier componente


de angular, incluyendo providers.

Por qu?: Cuando una aplicacin es separada en mdulos que pueden ser
reutilizados en otras aplicaciones, cada mdulo autnomo debera ser capaz
de operar por s mismo incluyendo cualquier constante de la cual dependa.

// Constantes usadas por la aplicacin entera


angular
.module('app.core')
.constant('moment', moment);

// Constantes usadas solo por el mdulo de ventas


angular
.module('app.sales')
.constant('events', {
ORDER_CREATED: 'event_order_created',
INVENTORY_DEPLETED: 'event_inventory_depleted'
});
Volver arriba

Plantillas y Snippets
Usa Plantillas o snippets para ayudarte a seguir estilos consistentes o patrones.
Aqu hay plantillas y/o snippets para algunos de los editores de desarrollo web e
IDEs.

Sublime Text

[Style Y250]

Snippets de Angular que siguen estos estilos y directrices.

o Descarga los snippets de Angular para Sublime


o Colcalos en tu directorio de Packages
o Reinicia Sublime
o En un archivo de JavaScript escibe estos comandos seguidos de un TAB
ngcontroller // crea un controlador de Angular
ngdirective // crea una directiva de Angular
ngfactory // crea una factory de Angular
ngmodule // crea un mdulo de Angular

Visual Studio

[Style Y251]

Plantillas de Angular que siguen estos estilos y directrices pueden ser


encontrados en SideWaffle

o Descarga la extensin SideWaffle de Visual Studio (archivo vsix)


o Corre el archivo vsix
o Reinicia Visual Studio

WebStorm

[Style Y252]

Snippets y arhicos de Angular que siguen estos estilos y directrices. Puedes


importarlos en tus configuraciones de WebStorm:

o Descarga los snippets y plantillas de Angular para WebStorm


o Abre WebStorm y ve al men File
o Elije la opcin Import Settings
o Selecciona el archivo y da click en OK
o En un archivo de JavaScript escribe estos comandos seguidos de un TAB:
ng-c // crea un controlador de Angular
ng-f // crea una factory de Angular
ng-m // crea un mdulo de Angular
Volver arriba

Generador de Yeoman

[Style Y260]

Puedes usar el generador de yeoman HotTowel para crear una aplicacin que te
sirve como punto de inicio en Angular que sigue esta gua de estilos.

1. Instala generator-hottowel
2. npm install -g generator-hottowel

3. Crea un nuevo directorio y entra en el

4. mkdir myapp
5. cd myapp

6. Corre el generador

7. yo hottowel helloWorld
Volver arriba

Enrutamiento
Enrutamiento del lado del Cliente es importante para crear un flujo de navegacin
entre vistas y vistas de composicin que estn hechas de muchas pequeas
plantillas y directivas.

[Style Y270]

Usa el AngularUI Router para ruteo del lado del cliente.

Por qu?: UI Router ofrece todas las caractersticas del router de Angular
mas algunas adicionales incluyendo rutas anidadas y estados.

Por qu?: La sintaxis es bastante similar al router de Angular y es fcil de


migrar al UI Router.

[Style Y271]

Define rutas para vistas en el mdulo dnde stas existen. Cada mdulo
debera contener las rutas para las vistas en ese mdulo.

Por qu?: Cada mdulo debe ser capaz de funcionar por s mismo.

Por qu?: Al remover un mdulo o al agregar un mdulo, la aplicacin solo


contendr rutas que apunten a las vistas existentes.

Por qu?: Esto hace ms fcil habilitar o deshabilitar porciones de una


aplicacin sin preocuparse de rutas hurfanas.

Volver arriba
Automatizacin de Tareas
Usa Gulp o Grunt para crear tareas automatizadas. Gulp deriva a cdigo sobre
configuracin mientras que Grunt deriva a configuracin sobre cdigo.
Personalmente yo prefiero Gulp ya que se siente ms fcil de leer y escribir, pero
ambos son excelentes.

[Style Y400]

Usa automatizacin de tareas para listar archivos que definan


mdulos *.module.js antes que otros archivos de JavaScript en la aplicacin.

Por qu?: Angular necesita la definicin de mdulos para ser registrados


antes de que sean usados.

Por qu?: Nombra mdulos con un patrn especfico


como *.module.js hace ms fcil tomarlos con un glob y listarlos primero.
var clientApp = './src/client/app/';

// Siempre toma archivos de mdulos primero


var files = [
clientApp + '**/*.module.js',
clientApp + '**/*.js'
];
Volver arriba

Angular docs
Para cualquier otra cosa, refirete a la API, mira la documentacin de Angular.

You might also like