Thanks to a huge amount of valuable sources I've got some general recommendations for implementing components in AngularJS apps:
Controller
Controller should be just an interlayer between model and view. Try to make it as thin as possible.
It is highly recommended to avoid business logic in controller. It should be moved to model.
Controller may communicate with other controllers using method invocation (possible when children wants to communicate with parent) or $emit, $broadcast and $on methods. The emitted and broadcasted messages should be kept to a minimum.
Controller should not care about presentation or DOM manipulation.
Try to avoid nested controllers. In this case parent controller is interpreted as model. Inject models as shared services instead.
Scope in controller should be used for binding model with view and
encapsulating View Model as for Presentation Model design pattern.
Scope
Treat scope as read-only in templates and write-only in controllers. The purpose of the scope is to refer to model, not to be the model.
When doing bidirectional binding (ng-model) make sure you don't bind directly to the scope properties.
Model
Model in AngularJS is a singleton defined by service.
Model provides an excellent way to separate data and display.
Models are prime candidates for unit testing, as they typically have exactly one dependency (some form of event emitter, in common case the $rootScope) and contain highly testable domain logic.
Model should be considered as an implementation of particular unit.
It is based on single-responsibility-principle. Unit is an instance that is responsible for its own scope of related logic that may represent single entity in real world and describe it in programming world in terms of data and state.
Model should encapsulate your application’s data and provide an API
to access and manipulate that data.
Model should be portable so it can be easily transported to similar
application.
By isolating unit logic in your model you have made it easier to
locate, update, and maintain.
Model can use methods of more general global models that are common
for the whole application.
Try to avoid composition of other models into your model using dependency injection if it is not really dependent to decrease components coupling and increase unit testability and usability.
Try to avoid using event listeners in models. It makes them harder to test and generally kills models in terms of single-responsibility-principle.
Model Implementation
As model should encapsulate some logic in terms of data and state, it should architecturally restrict access to its members thus we can guarantee loose coupling.
The way to do it in AngularJS application is to define it using factory service type. This will allow us to define private properties and methods very easy and also return publically accessible ones in single place that will make it really readable for developer.
An example:
angular.module('search')
.factory( 'searchModel', ['searchResource', function (searchResource) {
var itemsPerPage = 10,
currentPage = 1,
totalPages = 0,
allLoaded = false,
searchQuery;
function init(params) {
itemsPerPage = params.itemsPerPage || itemsPerPage;
searchQuery = params.substring || searchQuery;
}
function findItems(page, queryParams) {
searchQuery = queryParams.substring || searchQuery;
return searchResource.fetch(searchQuery, page, itemsPerPage).then( function (results) {
totalPages = results.totalPages;
currentPage = results.currentPage;
allLoaded = totalPages <= currentPage;
return results.list
});
}
function findNext() {
return findItems(currentPage + 1);
}
function isAllLoaded() {
return allLoaded;
}
// return public model API
return {
/**
* @param {Object} params
*/
init: init,
/**
* @param {Number} page
* @param {Object} queryParams
* @return {Object} promise
*/
find: findItems,
/**
* @return {Boolean}
*/
allLoaded: isAllLoaded,
/**
* @return {Object} promise
*/
findNext: findNext
};
});
Creating new instances
Try to avoid having a factory that returns a new able function as this begins to break down dependency injection and the library will behave awkwardly, especially for third parties.
A better way to accomplish the same thing is to use the factory as an API to return a collection of objects with getter and setter methods attached to them.
angular.module('car')
.factory( 'carModel', ['carResource', function (carResource) {
function Car(data) {
angular.extend(this, data);
}
Car.prototype = {
save: function () {
// TODO: strip irrelevant fields
var carData = //...
return carResource.save(carData);
}
};
function getCarById ( id ) {
return carResource.getById(id).then(function (data) {
return new Car(data);
});
}
// the public API
return {
// ...
findById: getCarById
// ...
};
});
Global Model
In general try to avoid such situations and design your models properly thus it can be injected into controller and used in your view.
In particular case some methods require global accessibility within application.
To make it possible you can define ‘common’ property in $rootScope and bind it to commonModel during application bootstrap:
angular.module('app', ['app.common'])
.config(...)
.run(['$rootScope', 'commonModel', function ($rootScope, commonModel) {
$rootScope.common = 'commonModel';
}]);
All your global methods will live within ‘common’ property. This is some kind of namespace.
But do not define any methods directly in your $rootScope. This can lead to unexpected behavior when used with ngModel directive within your view scope, generally littering your scope and leads to scope methods overriding issues.
Resource
Resource lets you interact with different data sources.
Should be implemented using single-responsibility-principle.
In particular case it is a reusable proxy to HTTP/JSON endpoints.
Resources are injected in models and provide possibility to send/retrieve data.
Resource implementation
A factory which creates a resource object that lets you interact with RESTful server-side data sources.
The returned resource object has action methods which provide high-level behaviors without the need to interact with the low level $http service.
Services
Both model and resource are services.
Services are unassociated, loosely coupled units of functionality that are self-contained.
Services are a feature that Angular brings to client-side web apps from the server side, where services have been commonly used for a long time.
Services in Angular apps are substitutable objects that are wired together using dependency injection.
Angular comes with different types of services. Each one with its own use cases. Please read Understanding Service Types for details.
Try to consider main principles of service architecture in your application.
In general according to Web Services Glossary:
A service is an abstract resource that represents a capability of
performing tasks that form a coherent functionality from the point of
view of providers entities and requesters entities. To be used, a
service must be realized by a concrete provider agent.
Client-side structure
In general client side of the application is splitted into modules. Each module should be testable as a unit.
Try to define modules depending on feature/functionality or view, not by type.
See Misko’s presentation for details.
Module components may be conventionally grouped by types such as controllers, models, views, filters, directives etc.
But module itself remains reusable, transferable and testable.
It is also much easier for developers to find some parts of code and all its dependencies.
Please refer to Code Organization in Large AngularJS and JavaScript Applications for details.
An example of folders structuring:
|-- src/
| |-- app/
| | |-- app.js
| | |-- home/
| | | |-- home.js
| | | |-- homeCtrl.js
| | | |-- home.spec.js
| | | |-- home.tpl.html
| | | |-- home.less
| | |-- user/
| | | |-- user.js
| | | |-- userCtrl.js
| | | |-- userModel.js
| | | |-- userResource.js
| | | |-- user.spec.js
| | | |-- user.tpl.html
| | | |-- user.less
| | | |-- create/
| | | | |-- create.js
| | | | |-- createCtrl.js
| | | | |-- create.tpl.html
| |-- common/
| | |-- authentication/
| | | |-- authentication.js
| | | |-- authenticationModel.js
| | | |-- authenticationService.js
| |-- assets/
| | |-- images/
| | | |-- logo.png
| | | |-- user/
| | | | |-- user-icon.png
| | | | |-- user-default-avatar.png
| |-- index.html
Good example of angular application structuring is implemented by angular-app - https://github.com/angular-app/angular-app/tree/master/client/src
This is also considered by modern application generators - https://github.com/yeoman/generator-angular/issues/109