Shining some light on Angular 2 directives: What are they and how do they differ from components?

So I was trying to wrap my head around the difference between a component, a directive and other angular-y elements. After all, Angular is all about making your own custom HTML elements right?

When I read the docs initially the concepts seemed a bit ephemeral at first, a little difficult to crystalize the concepts in my mind. After a bit more reading I came to a broader more layman's term for what a directive is: A directive is any code that is applied to the DOM which makes Angular invoke it's own corresponding code.

That is a bit of a broad definition so let's give that definition some more characteristics.

Let's take a look at a fictional component in Angular 2.

<my-component> </my-component>  

One of the things that confused me about A2 directives is that the component above can be written like this also:

<div my-component> </div>  

This is the same as the my-component component up above. Exactly the same thing will happen and exactly the same code will be run. So I started scratching my head and asking a few questions:

1) If the first and second are the same, what is the difference between an Angular 2 component / directive and an HTML attribute?

2) So if an HTML attribute triggers code like a directive, what do built in angular methods like *ngFor or *ngIf do behind the scences? How are they treated by Angular?

In the following paragraphs the aim is to clear up any cloudiness on the questions above, to explain clearly what directives are, how they work, and what you can do with them. Buckle up!

So according to Angular there are three kinds of directives in Angular.

  • Components: Directives with a template
  • Structural Directives: Directives which add and remove stuff from the DOM based on certain criteria.
  • Attribute Directives: Directives that change the appearance or behavior of the DOM.

So contrary to popular belief though... there really isn't a prime directive. ( I'll see myself out.. :)) )

So how do we create a directive?

As with everything in Angular, you start by importing some stuff. The stuff in question is: Directive and Input.

Next, you use the @Directive decorator method with a selector. This tells angular to watch our for the directive selector. Similar to how a component watches out for it's selector which causes the template to be rendered and its' corresponding code executed.

Next you create a class to handle the conditional logic and rendering the results into the view.

Ok check out this example from the Angular docs and we'll step through it:

import { Directive, Input } from '@angular/core';  
import { TemplateRef, ViewContainerRef } from '@angular/core';

@Directive({ selector: '[myUnless]' })
export class UnlessDirective {  
  constructor(
    private templateRef: TemplateRef<any>,
    private viewContainer: ViewContainerRef
    ) { }
  @Input() set myUnless(condition: boolean) {
    if (!condition) {
      this.viewContainer.createEmbeddedView(this.templateRef);
    } else {
      this.viewContainer.clear();
    }
  }
}

So, you need Directive so you can annotate the class so the compiler know's it's a directive. Input, let's you take the template from the HTML that the directive is attached to.

When we write@Directive above and pass it the object with the selector property, we are telling Angular's change detection to put out a BOLO for any directives that look like myUnless. When it sees that term, it needs to trigger the corresponding code that is matched in the UnlessDirective class, in this case it executes the condition block which shares the same property name.

If the condition passes, in this case false values pass, then it creates an EmbeddedView passing it the view that it received as the input. ( more on embedded views below )

Beginning to Answering Question 1

So we have some concepts from question 1 starting to take shape. Components are just directives. Ok, so if components are just directives, what happens when you use the HTML attribute like syntax or an *ngIf. Let's dig into the Angular source and see:

import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';

export var NgIf = (function () {  
    function NgIf(_viewContainer, _template) {
        this._viewContainer = _viewContainer;
        this._template = _template;
        this._hasView = false;
    }

    Object.defineProperty(NgIf.prototype, "ngIf", {
        set: function (condition) {
            if (condition && !this._hasView) {
                this._hasView = true;
                this._viewContainer.createEmbeddedView(this._template);
            }
            else if (!condition && this._hasView) {
                this._hasView = false;
                this._viewContainer.clear();
            }
        },
        enumerable: true,
        configurable: true
    });

    NgIf.decorators = [
        { type: Directive, args: [{ selector: '[ngIf]' },] },
    ];

    /** @nocollapse */
    NgIf.ctorParameters = [
        { type: ViewContainerRef, },
        { type: TemplateRef, },
    ];

    NgIf.propDecorators = {
        'ngIf': [{ type: Input },],
    };
    return NgIf;
}());

There is a lot going on here so let's walk through it step by step. First off, when angular creates certain parts of itself, it uses a SIAF and then returns an object. That object has been decorated with properties that Angular's DI system knows how to use to inject the necessary dependencies in and invoke the necessary code when these are used.

Essentially, instead of creating a directive with the @Directive annotaion the code above is creating a directive programatically and then exporting it when it registers the the rest of this group of directives which Angular calls it's COMMON_DIRECTIVES.

View Containers

When a directive is created, it get's access to the view container. This is the thing that angular uses to inject stuff into the DOM and manipulate it. Think of the view container as a wrapped version of the view. What does that mean?

We are all familiar with this type of selector: $('#someDiv'), I hope ;)
This doesn't return the div directly, but rather a jQuery result set, that has the element in it, but also exposes methods like .show() or .html(). In the same way, think of the view container as a wrapped version of the template that angular is working with.

View containers can have two kinds of views attached to them: Host views, which work with components, or embedded views, which work with templates. Since we are working with a structural directive then the *ngIF need to use an embedded view to modify the DOM, hence this line: this._viewContainer.createEmbeddedView(this._template); with this.template being the thing that this structural directive is attached to.

So to restate our simplified definition above:

A directive is any code that is applied to the DOM which makes Angular invoke it's own corresponding code.

Conclusion

So to wrap things up, components are directives and directives are just attributes placed on html elements that angular sees and then runs some code. It sees those attributes and knows to run it's corresponding code because of the selector we attach to the directive when we create it. I hope this article has made directives and components and the mechanics behind them a little more clear. As always, feel free to ask question in the comments and if you spot an error please correct me.

Thanks!