Improve this Doc

Understanding Components

In Angular, a Component is a special kind of directive that uses a simpler configuration which is suitable for a component-based application structure.

This makes it easier to write an app in a way that's similar to using Web Components or using Angular 2's style of application architecture.

Advantages of Components:

When not to use Components:

Creating and configuring a Component

Components can be registered using the .component() method of an Angular module (returned by angular.module()). The method takes two arguments:

angular.module('heroApp', []).controller('mainCtrl', function() {
  this.hero = {
    name: 'Spawn'
  };
});

It's also possible to add components via $compileProvider in a module's config phase.

Comparison between Directive definition and Component definition

Directive Component
bindings No Yes (binds to controller)
bindToController Yes (default: false) No (use bindings instead)
compile function Yes No
controller Yes Yes (default function() {})
controllerAs Yes (default: false) Yes (default: $ctrl)
link functions Yes No
multiElement Yes No
priority Yes No
require Yes Yes
restrict Yes No (restricted to elements only)
scope Yes (default: false) No (scope is always isolate)
template Yes Yes, injectable
templateNamespace Yes No
templateUrl Yes Yes, injectable
terminal Yes No
transclude Yes (default: false) Yes (default: false)

Component-based application architecture

As already mentioned, the component helper makes it easier to structure your application with a component-based architecture. But what makes a component beyond the options that the component helper has?

By implementing these methods, your component can hook into its lifecycle.

Example of a component tree

The following example expands on the simple component example and incorporates the concepts we introduced above:

Instead of an ngController, we now have a heroList component that holds the data of different heroes, and creates a heroDetail for each of them.

The heroDetail component now contains new functionality:

var mode = angular.module('heroApp', []);

Components as route templates

Components are also useful as route templates (e.g. when using ngRoute). In a component-based application, every view is a component:

var myMod = angular.module('myMod', ['ngRoute']);
myMod.component('home', {
  template: '<h1>Home</h1><p>Hello, {{ $ctrl.user.name }} !</p>',
  controller: function() {
    this.user = {name: 'world'};
  }
});
myMod.config(function($routeProvider) {
  $routeProvider.when('/', {
    template: '<home></home>'
  });
});


When using $routeProvider, you can often avoid some boilerplate, by passing the resolved route dependencies directly to the component. Since 1.5, ngRoute automatically assigns the resolves to the route scope property $resolve (you can also configure the property name via resolveAs). When using components, you can take advantage of this and pass resolves directly into your component without creating an extra route controller:

var myMod = angular.module('myMod', ['ngRoute']);
myMod.component('home', {
  template: '<h1>Home</h1><p>Hello, {{ $ctrl.user.name }} !</p>',
  bindings: {
    user: '<'
  }
});
myMod.config(function($routeProvider) {
  $routeProvider.when('/', {
    template: '<home user="$resolve.user"></home>',
    resolve: {
      user: function($http) { return $http.get('...'); }
    }
  });
});

Intercomponent Communication

Directives can require the controllers of other directives to enable communication between each other. This can be achieved in a component by providing an object mapping for the require property. The object keys specify the property names under which the required controllers (object values) will be bound to the requiring component's controller.

Note that the required controllers will not be available during the instantiation of the controller, but they are guaranteed to be available just before the $onInit method is executed!

Here is a tab pane example built from components:

angular.module('docsTabsExample', [])
.component('myTabs', {
  transclude: true,
  controller: function() {
    var panes = this.panes = [];
    this.select = function(pane) {
      angular.forEach(panes, function(pane) {
        pane.selected = false;
      });
      pane.selected = true;
    };
    this.addPane = function(pane) {
      if (panes.length === 0) {
        this.select(pane);
      }
      panes.push(pane);
    };
  },
  templateUrl: 'my-tabs.html'
})
.component('myPane', {
  transclude: true,
  require: {
    tabsCtrl: '^myTabs'
  },
  bindings: {
    title: '@'
  },
  controller: function() {
    this.$onInit = function() {
      this.tabsCtrl.addPane(this);
      console.log(this);
    };
  },
  templateUrl: 'my-pane.html'
});

Unit-testing Component Controllers

The easiest way to unit-test a component controller is by using the $componentController that is included in ngMock. The advantage of this method is that you do not have to create any DOM elements. The following example shows how to do this for the heroDetail component from above.

The examples use the Jasmine testing framework.

Controller Test:

describe('component: heroDetail', function() {
  var component, scope, hero, $componentController;

  beforeEach(module('simpleComponent'));

  beforeEach(inject(function($rootScope, _$componentController_) {
    scope = $rootScope.$new();
    $componentController = _$componentController_;
    hero = {name: 'Wolverine'};
  }));

  it('should set the default values of the hero', function() {
    // It's necessary to always pass the scope in the locals, so that the controller instance can be bound to it
    component = $componentController('heroDetail', {$scope: scope});

    expect(component.hero).toEqual({
      name: undefined,
      location: 'unknown'
    });
  });

  it('should assign the name bindings to the hero object', function() {
    // Here we are passing actual bindings to the component

    component = $componentController('heroDetail',
      {$scope: scope},
      {hero: hero}
    );
    expect(component.hero.name).toBe('Wolverine');
  });

  it('should call the onDelete binding when a hero is deleted', function() {
    component = $componentController('heroDetail',
      {$scope: scope},
      {hero: hero, onDelete: jasmine.createSpy('deleteSpy')}
    );

    component.onDelete({hero: component.hero});
    expect(spy('deleteSpy')).toHaveBeenCalledWith(component.hero);
  });

});