Angular component method, the natural evolution

March 29, 2016

One of the cool things that we "were/are/will be" using while coding our front applications with AngularJs is the option to use directives to create our own reusable components. Basically, the main idea of the component was (...and is ...and will be) to have reusable elements that brings their own encapsulated functionality and also the ways to manipulate the data they need. If we were following the common known best practices, we were creating custom components by:

To help us achieving this coding way, Angular 1.5 has introduced the component() method in order to simplify the task of creating components (concept that will be also completely integrated in Angular 2). Now, instead of implementing similar views and controllers over and over again, this approach enables to easily create components. So, what is a component in Angular?... Quoting Angular documentation: "a component is a special kind of directive which represents a self-contained UI component and that uses a simpler configuration which is suitable for a component-based application structure". For us, as developers, it means that we can stop implementing similar directives/controllers/... over and over again, and start to use components that can be created once, reusable and also composed into bigger components (components tree).

Let's go deeper...

Component works internally as a directive with some default properties. To see it, let's review the Angular code . There, we can find the component method definition:

this.component = function registerComponent(name, options) {

First of all, the function that is behind the component method. As we can see, it is created following the pattern named function expression ("NFE") and uses two parameters:

...and what does the function return?

return this.directive(name, factory);

As I mentioned, component works internally like one directive, so what it returns is a definition of a directive, with its name (that comes from the first parameter used in the definition) and its factory function. Let's review it...

function factory($injector) {

factory is a function and we should know what it returns. Basically one object with configuration for the directive, but in this case, with some remarkable default values and without other usual ones.

return {
    controller: controller,

Controller uses the value of the homonym variable, which is set with a default value if options doesn't have a controller function.

    controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl',

It uses automatically controllerAs syntax and if it is not defined in the configuration object, the default value is $ctrl

    template: makeInjectable(template),
    templateUrl: makeInjectable(options.templateUrl),

template and templateUrl are used in the same way than in all directives, and if it is not defined in options, it will set as empty string (done by makeInjectable function).

    transclude: options.transclude,

Transclusion is optional and it is disabled by default.

    scope: {},

The returned directive has an isolate scope and it doesn't have any property bound.

    bindToController: options.bindings || {},

It uses the property bindings to extend the controller and, as we can see, it is on by default (if bindings property is not defined, it sets an empty object) . bindings is an object which properties that are attributes of the component. It works as an API that handles the Input and Outputs of the component. The only data that is passed is the one that is needed for the component to behave as expected and the component should never modify any data that is out of their own scope. In order to accomplish this goal, Angular allow us to use one-way bindings by the use of < in our bindings. So < and @ bindings are useful for the inputs, and the outputs can be realized with & bindings:

// inputs for the component
     '<' for one way binding
     '@' for strings
// output that works as function callbacks
    '&' for callbacks to component events.

As I have mentioned, the component should never modify the input data. Instead of it, the component can call the Output Event with the changed data, sending it back to the owner component (ancestor component or directive). The parent is the one that modify the data it owns.

    restrict: 'E',

The components are directives restricted to elements

    require: options.require

It allows the communication between components, if the value is an object in which the property values are the names of the required controllers (components name) and the keys are the values that will hold in the current controller. And this is all. There are no more properties, so this is the returned object and, as we can see, it is a subset of properties that could be used in the definition of any directive (https://docs.angularjs.org/api/ng/service/$compile#directive-definition-object). But as remarkable as is the default properties that the object has, there are properties that are often in directives but are not defined in component...   Do you miss any any of them?... if the answer is yes, I guess you have seen that it doesn't include link and compile functions. Neither others like priority or terminal. This lead us to think that components shouldn't be used if we need/want to manipulate the DOM. At the end of the article there is a table with all the properties that directive and component definition object allow to use.

Code simpler and clean...

So we could compare our component code with and old style directive:

myapp.directive('panel', function() {
   return {
       restrict: 'E',
       templateUrl: 'panel.tpl.html',
       controller: function panelCtrl() {},
       controllerAs: '$ctrl',
       bindToController: true,
       scope: {
           config: '='
       }
    }
});

And as component, saving from boilerplate and with the default properties:

myapp.component('panel', {
   controller: function panelCtrl() {},
   templateUrl: 'panel.tpl.html',
   bindings: {
      config: '< '  // one way binding
   }
});

The mandatory function that always return an object has disappeared, also other often repeated properties. Therefore we have a simpler and more straightforward code that does the same. Also in the component the property attached to the bindings uses '<', which has more sense because there is no reason to keep the two way binding for the configuration that the component uses.

Concluding...

So what is component() method good for? What are its advantages?:

...but, as we saw, we should not use component method if:

Differences between directive and component definitions:
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)

...extracted from https://docs.angularjs.org/guide/component

About the author: Jorge Rumoroso
Comments
Join us