Skip to content

openmindlab/angularjs-best-practices

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

22 Commits
 
 

Repository files navigation

AngularJS best practices

Hints and tips for large AngularJS applications

This guide is based on openmind team experience in large SPA (web / mobile apps) based on AngularJS.

##Project and code organization

http://cliffmeyers.com/blog/2013/4/21/code-organization-angularjs-javascript http://trochette.github.io/Angular-Design-Patterns-Best-Practices/#/intro

In a large project, AngularJS code could be logically splitted in 2 parts

  • core: All the code needed at startup or shared by more than a single feature.
    Ideally, the code organization of the core follows a module-based criteria; usually modules are defined in a type-based criteria, ending in a project organized as follows:
/core
  /controllers
    login.ctrl.js
    error.ctrl.js
  /services
    cache.srv.js
    store.srv.js
    mail.srv.js
  /filters
    currency.flt.js
  /directives 
  • features: Every application feature ("page") has its own folder, with specific controllers, services, filters, directives and views
/features
  /homepage
    homepage.ctrl.js
    homepage.srv.js
    homepage.tmpl.html    
  /about
	about.ctrl.js
    about.tmpl.html
  /books
    books.ctrl.js
    books.tmpl.html
    books.show.tmpl.html

This code organizations helps you defining the concatenation and minification rules in order to output a single javascript file for the whole core and one javascript file for each feature.

Naming convention

In order to help you searching and defining the build rules, suffix your files with the code type:

  • controller: *.ctrl.js
  • service: *.srv.js
  • filter: *.flt.js
  • directive: *.drv.js
  • template: *.tmpl.html

AMD

If you use AMD in your project, there are some changes to the files organization that ease AMD modules <-> AngularJS modules definition and loading:

/core
  index.js
  /controllers
    ...
    index.js
    module.js
  /services
    ...
    index.js
    module.js
  /filters
    ...
    index.js
    module.js
  /directives
    ...
    index.js
    module.js
/features
  /homepage
    index.js
    ...
  /about
    index.js
    ...

Add an index.js file to each folder to define dependency from subfolders or files.

/core/index.js

define([
  './controllers/index',
  './services/index',
  './filters/index',
  './directives/index'
], function () {
});

/core/controllers/index.js

define([
  './login.ctrl',
  './error.ctrl'
], function (angular) {
});

Following the module-based organization criteria, add to each module folder a module.js file in which the angular module is defined.

/core/controllers/module.js

define([
  'angular',
  '../services/index',
  '../filters/index',
  '../directives/index'
], function (angular) {
  return angular.module('myapp.controllers', [
    'myapp.services',
    'myapp.filters',
    'myapp.directives'
  ]);
});

Then make each controller / service / filter / directive dependant on the right module.js file:

/core/controllers/*.ctrl.js

define([
  './module'
], function (module) {
  module.controller(...)
});

Controllers

http://toddmotto.com/digging-into-angulars-controller-as-syntax/ http://blog.thoughtram.io/angularjs/2015/01/02/exploring-angular-1.3-bindToController.html http://plnkr.co/edit/PzPjYhtuopKvlwiVsYhT?p=preview https://scotch.io/tutorials/making-skinny-angularjs-controllers

$scope is evil

Ok. This is not true. You are evil... $scope (and its prototypal inheritance) is one of the main and best features of AngularJS but often led to the overuse of $watch, $emit, $on and $broadcast, ... Using "controller as" syntax and defining controllers like classes, you could avoid to inject $scope in most cases.

function CounterController(addOneService) {
	var self = this;
    self.count = 0;
    self.addOne = function() {
    	addOneService.execute(self.count);
    }
}
CounterController.$inject = ['AddOneService'];

angular.controller('counterController', CounterController);
<div ng-controller="counterController as beans">
	{{beans.count}} beans
    <button ng-click="beans.addOne()">Add a bean!</button>
</div>

If you really need $scope inheritance or $watch or events related functions, you could inject $scope and use it.

function CounterController($scope, addOneService) {
	var self = this;
    self.count = 0;
    $scope.$watch(function(){ return self.count;}, function(){
    	...
    });
    $scope.$on('resetCounters', function(){
    	self.count = 0;
    });
    ...
}
CounterController.$inject = ['$scope', 'AddOneService'];

angular.controller('counterController', CounterController);

Dealing with this

The controllers functions called by angular expressions has different context. You could fix it by using angular.bind(...) or by capturing this to a local context variable:

function BindController() {
  this.aProperty = "i'm a property";
  
  $scope.$watch(angular.bind(this, function () {
    return this.aProperty;
  }), function () {
    ...
  });
}
function CaptureController() {
  var self = this;
  self.aProperty = "i'm a property";
  
  $scope.$watch(function () {
    return self.aProperty;
  }, function () {
    ...
  });
}

Prototypal inheritance in javascript

The controller as class pattern opens some nice scenarios thanks to prototypal inheritance.

function BaseController() {
  this.aMethod = function () {

  };
}
BaseController.prototype.inheritedObject = {};
BaseController.prototype.inheritedMethod = function () {

};

ConcreteController.prototype = Object.create(BaseController.prototype);

function ConcreteController () {
  this.anotherMethod = function () {

  };
}

My controllers are lean

The controllers should be as lean as possible. Here are some hints:

  • move any business code to services

from:

function FatController(){
	var self = this;
    
    ...
    very long body, lot of functions and watches
    ...
        
}

to:

function SkinnyController(service1, service2){
	var self = this;
    
    self.method1 = function() {
    	service1.method1(self);
    }
    
    self.aService = service2; // in view use {{skinnyCtrl.aService.aMethod()}}
    
    ...
}
  • use resolve in router

from:

...
$stateProvider.state('loading', {
  ...
  controller: 'LoadingController'
  ...
});
function LoadingController(service) {
  var self = this;
  service.loadLongList().then(function(theLongList) {
    self.theLongList = theLongList;
  });
}

to:

...
$stateProvider.state('loading', {
  ...
  controller: 'LoadingController',
  resolve: {
    theLongList: ['service', function(service) {
      return service.loadLongList();
    }]
  }
  ...
});
function LoadingController(theLongList) {
  var self = this;
  service.loadLongList = theLongList;
}

Directives Short but clear explaination about directive lifecycle. http://filimanjaro.com/blog/2014/angular-directive-lifecycle-vs-link-compile-controller/ http://www.jvandemo.com/the-nitty-gritty-of-compile-and-link-functions-inside-angularjs-directives/

Misc. Performance tips for item lists http://tech.small-improvements.com/2013/09/10/angularjs-performance-with-large-lists/

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published