Angular 2: The three types of directives
Apr 08 2016 02:00pm | | Share

Angular 2: The three types of directives

By Alain Thibodeau

In many ways Angular 2 is simpler than the previous version. Directives are now the focal point and easier to create and maintain. In the previous version of Angular directives were a soar topic. Some people altogether avoided using them. Most eventually took the plunge to figure them out, but they always remained awkward. They are now first class citizens, so let’s take a high level look at them in TypeScript.

There are 3 types of directives:

  1. Components
  2. Structural Directives
  3. Attribute Directives


1. Components

You cannot create an Angular application without one. A component directive requires a view along with its attached behaviour. This type of directive adds DOM elements. The naming convention for components is: name.component.ts.

import {Component} from 'angular2/core'

@Component({

  selector: 'my-app',

  template: `<h3>The Super Button</h3><button (click)="btnClick()">Click Me</button>`

  })

export class App {

      btnClick() {

        alert('You clicked me!');

    }

}

The above is a sample of a simple component. In this case the main component of the app, also called the root component. It contains the bare minimum needed to create a component. Once rendered, it displays a title and a button that alerts you when clicked.

To tell Angular you want a class to be a component you need to import and assign the component decorator to it. Decorators in TypeScript are like annotations in Java or attributes in C#. Learn more about decorators here. The component decorator allows us to declare many attributes, but we are only using the minimum here. The selector tells Angular what will be the tag name for my component. The template attribute specifies the DOM elements to add. You can also use “templateUrl” and point to a template file.

We export this class to make it available as a module and we add the btnClick method.

Now in your HTML you can call this component like this:
<my-app></my-app>

 

Structural & Attribute Directives

Rather than adding new DOM elements, both these types of directives modify the existing DOM and do not have templates.
 

Attribute: Modifies the appearance or behavior of an element.               

Structural: Modifies the DOM layout.

 

2. Attribute Directive

Examples of built-in attribute directives that ship with Angular 2 are ngStyle and ngClass. As mentioned before, these directives modify the DOM and we are going to do the same in a simple custom attribute directive of our own. The naming convention for directives is: name.directive.ts
 

import {Directive, ElementRef, Renderer} from 'angular2/core';

 

@Directive({

  selector: '[my-outline]',

  host: {

    '(mouseenter)': 'onMouseEnter()',

    '(mouseleave)': 'onMouseLeave()'

  }

})

 

export class MyOutline {

  constructor(private _element: ElementRef, private _renderer:Renderer) { }

  onMouseEnter() { this._outlineToggle(true); }

  onMouseLeave() { this._outlineToggle(false); }

 

  private _outlineToggle(color: string) {

    this._renderer.setElementStyle(this._element.nativeElement, 'border', 'solid red 1px' );

  }

}

Taking a quick look reveals that this directive looks very much like a component. But, there are differences, one is as mentioned before we do not have a template because this directive modifies the DOM.

We are also introducing the host in the decorator meta data. This host refers to the DOM element that this directive is attached to. It is recommended to use the host rather than attaching listeners to the element directly. This is because we could introduce memory leaks or be confronted with naming issues. In the host object, we declare the event listeners that we are interested in listening too. These are linked to methods in the MyOutline class.

MyOutline class declares a constructor that injects the ElementRef service and creates a private _element property which gives us access to the DOM element. ElementRef  has a nativeElement property that we could use to add our border directly like so:

el.nativeElement.style.border = 'solid red 1px';


But that is not recommended and dangerous. Instead, we use the Renderer and pass it our ElementRef’s nativeElement. The Renderer class contains everything we need to manipulate the element safely. This includes the setElementStyle method that we are using to set the border of the element.

That is it, a very simple directive that adds a border to an element when the user hovers over it.

 

3. Structural Directive

Examples of built-in structural directives that ship with Angular 2 are ngIf, ngFor and ngSwitch. As mentioned before these directives change the layout of the DOM, and we will also do the same in a small simple custom example. The naming convention remains the same as before: name.directive.ts.

We are now going to create a simple custom variation of the ngFor directive. Our version will take an integer input. This int represents how many times we want to repeat the element that we are attached to.

import {Directive, Input} from 'angular2/core';

import {TemplateRef, ViewContainerRef} from 'angular2/core';

 

@Directive({ selector: '[repeatMe]' })

export class RepeatMe {

  constructor(

    private _templateRef: TemplateRef,

    private _viewContainer: ViewContainerRef

    ) {}

 

  @Input() set repeatMe(count: int) {

    for (var i = 0; i < count; i++) {

      this._viewContainer.createEmbeddedView(this._templateRef);

    }

   }

}
 

It is worth noting at this point that the HTML 5 template tag is used in structural directives. It is your choice to use the template tag directly, other wise you can use the * which takes care of the template tag for you. Take a look at the documentation for ngFor for more information.

Much like our previous example, in our repeatMe class we declare a constructor that injects two things: TemplateRef and ViewContainerRef.  TemplateRef points to our template and ViewContainerRef will handle creating the view and attaching it to the container.

Next, we use the @Input() to specify our repeatMe input which takes in our integer and set it at the same time.

With the help of the ViewContainerRef, we can use its createEmbeddedView method to do the rest. It creates a view using the template and attaches it to the element as many times as specified with the count value. 

 

Putting it all together

I’ve created a Plunker that demonstrates all three types of directives working together. https://plnkr.co/edit/pRBX77

Plunker Example

I am using the main App component as the parent for the other 2 types of directives. In order to do this we must import and add the directives meta data in the parent component and declare the 2 directives like this:

import {MyOutline} from './my-outline.directive'

import {RepeatMe} from './repeat-me.directive'

…..

directives: [MyOutline, RepeatMe]

…..
 

So there we have it. This was an introduction to the three types of directives in Angular 2. There is much more to discover and you can read further in the documentation about the Structural and Attribute directives.