Angular Core Components Roles

Every once in a while I got questions like that:

  • "Hey Alex, what's the difference between those services and factories?"
  • "Why do I need to use service, if I can get the entity from server in the controller?"
  • "Should I wrap every component in the module like angular.module('controllers.supercontroller').controller('supercontroller', ...)" ?

Yeah, you can find all those answers using Google-fu, Stackoverlow-jutsu and practicing Zen-Youtube observations.

But I will try to put all those things together to make the whole picture clear.

Keep in mind that this is not the comprehensive guide, but an attempt to scratch the dust from the surface to make things look nice.

Constant

angular.module('limits')  
  .constant('MAX_SPEED', 50);

This thing is pretty straightforward. You are using constants in majority of programming languages, so it's easy: constants are values, which are not intended to be modified during the app lifecycle.

When do I use it:

  • You need a value which is not intended to change
  • You need to inject this value in different components to reduce the duplication
  • You need access to this constant during configuration phase

Gotchas:

  • Do not put modifiable objects into constants for semantic reasons - it is really misleading. Put those objects in .value, until you need it during the config phase.

Naming convention:

Use UPPER_CASE names for constants.

Value

angular.module('weather')  
  .value('currentHumidity', '80%');

Value has the same role as constant, but it is not available during the configuration phase and should be used for potentially modifiable values.

When do I use it:

  • Use it instead of constant for objects, simple pure functions and literals when the value can potentially change (for example, some jquery plugin configuration object).
  • You need to inject this value in different components to reduce the duplication.

Naming convention:

Use camelCase starting from lower case letter for value names - this denotes that values are singleton instances and the "code user" do not need to create it.

Controller

// generic controller
angular.module('login')  
  .controller('LoginCtrl', ...);

// screen controller
$routeProvider
  .when('/User/:userId', {
    templateUrl: 'user-profile.html',
    controller: 'UsersCtrl'
  });

// directive controller
angular.module('myApp')  
  .directive('tabs', tabsDirective);

function tabsDirective(){  
  return {
    controller: 'TabsCtrl',
    link: ...
  };
}

Controller should answer the questions:

  • "What data and logic do I need to show this particular view/screen?"
  • "How do I combine the logic to get the data for this view/screen?"

When do I use it:

  • Compose data and logic (methods calls from various services) for a single given View/Screen
  • Share some logic between tightly coupled directives (in combination with require). Example:

    ```html

    ```

  • Incapsulate the directive state when you do not need to share it with other components (using services), but you need to share state between same directives. Example:
<!-- this directives can share the same state -->  
<!-- the state cannot be accessed from other services -->  
<switcher></switcher>  
<switcher></switcher>  

Gotchas

  • Controller is not a singleton - each time you declare a controller, a new instance will be created.
  • Directives with the same controller will share a single controller instance, which is get created when the first "directive-with-controller" is used.

Naming convention

Use CamelCase with the upper case first letter to denote that a new controller instance will be created each time the controller is used.

Service

angular.module('login')  
  .service('loginService', LoginService);

// this is a standard js constructor 
// hence the UpperCase naming
function LogicService(){  
  this.login = "";
}

LoginService.prototype.tryLogin = ...;  

Service is a singleton instance containing some generic business logic. It hides the details of data manipulation and can be reused in other components.
Service should answer the questions:

  • "How do I get the data?"
  • "What do I do with that data to get the useful result?"

When do I use it:

  • Incapsulate common logic for any operation over data
  • Incapsulate mutable state and its modification operations
  • Get data from the server - use service as a Model Facade

Gotchas:

  • Is singleton.

Naming convention:

Use camelCase with the lower case first letter to denote that the injected instance should not be created and can be used as-is.

Factory

angular.module('user')  
  .factory('User', UserModelFactory);

// this is factory constructor
// hence uppercase name
function UserModelFactory(){  
  // returns something that can be called/instantiated
  return User;
}

// this is a model contructor
// it will be returned from the factory 
function User(name){  
  this.name = name;
}

// usage example
// User is a model contructor and should be instantiated manually
function someService(User){  
  var user = new User('hardcoded_name');
}

Factory looks the same as service. The difference is that factory should return contructor or "factory function", used to create new instances after injection.
Should answer the question:

  • "How do I create the fresh instance of something to use from scratch?"

When do I use it:

  • Create a new instance after each injection manually.
  • Return a "factory function" to reset the state of the component before usage.

Gotchas:

  • Is singleton.
  • Should return function/constructor. Do not use for returning objects! - it's really misleading .

Naming convention:

Use CamelCase with the upper case first letter to denote that the instance should be created by "code user"by calling the function/instantiating the constructor.

Directive

angular.module('avatar', [])  
  .directive('avatar', function() {
    return {
      restrict: 'E',
      transclude: true,
      scope: {},
      controller: function() {
         // some logic here
      },
      templateUrl: 'avatar.tpl.html',
      link: function(scope, element, attrs, ctrl){
        // work with DOM here
      }
    };
  })

Directive is mainly used for all DOM interactions.

When do I use it:

  • All DOM interactions goes here.
  • You need to create some custom tags to hide markup complexity.
  • You need to create an independent widgets.
  • You need to create a composable declarative component to use in templates.

Gotchas

  • Link function should be used to programmatically modify resulting DOM element instances, add event listeners, and set up data binding.
  • Controller should be used to have the same API across related directives.
  • Compile function should be used to modify the template across copies of a directive.
  • Directive has three settings for scopes: new inheriting scope scope: true, new isolated scope scope: {}, no scope scope: false.
  • Has a complex lifecycle of controller, link and compile functions.
  • Transcluded element scope is very different from what you may expect.

Naming Convention

Use camelCase with lower case first letter for directive definition and a <snake-case> in tags.

Module

// create module
angular.module('myModule', []);

// get already created module
angular.module('myModule');  

Module can be used as a namespace for your components, it helps unite logically related components.

When do I use it:

  • You need to keep your components logically related.
  • You need to show the dependency relation between modules.
  • You need to create an independent piece of "vertical" dissection of functionality (for example, a login functionality).
  • Yo need to have the component with full application lifecycle - routing, configuration, starting (for example, a reusable login page, which you can drop in any application).

Gotchas

  • Does not create a scope for dependency injection, i.e. it does not prevent name collisions.