In the course of creating applications, you will often find it useful to be able to attach component-style behavior to DOM elements, but without the need to have templating. If you were to attempt to construct an Angular 2 component without providing a template in some way, you will meet with a stern error telling you that some form of template is required.
Here lies the difference between Angular 2 components and directives: components have views (which can take the form of a template
, templateUrl
, or @View
decorator), whereas directives do not. They otherwise behave identically and provide you with the same behavior.
The code, links, and a live example of this are available at .
Suppose you have the following application:
[app/article.component.ts] import {Component} from '@angular/core'; @Component({ selector: 'article', template: `<h1>{{title}}</h1>`, styles: [` h1 { text-overflow: ellipsis; white-space: nowrap; overflow: hidden; max-width: 300px; } `] }) export class ArticleComponent { title:string = `Presidential Candidates Respond to Allegations Involving Ability to Dunk`; }
Currently, this application is using CSS to truncate the article title with an ellipsis. You would like to expand this application so that the Article component reveals the entire title when clicked by simply adding an HTML attribute.
Begin by defining the basic class that will power the attribute directive and add it to the application module:
[app/click-to-reveal.directive.ts] export class ClickToRevealDirective { reveal(target) { target.style['white-space'] = 'normal'; } } [app/app.module.ts] import {NgModule} from '@angular/core'; import {BrowserModule} from '@angular/platform-browser'; import {ArticleComponent} from './article.component'; import {ClickToRevealDirective} from './click-to-reveal.directive'; @NgModule({ imports: [ BrowserModule ], declarations: [ ArticleComponent, ClickToRevealDirective ], bootstrap: [ ArticleComponent ] }) export class AppModule {}
First, you must decorate the ClickToRevealDirective
class as @Directive
and use it inside the Article component:
[app/click-to-reveal.directive.ts] import { Directive} from '@angular/core'; @Directive({ selector: '[click-to-reveal]' }) export class ClickToRevealDirective { reveal(target) { target.style['white-space'] = 'normal'; } }
Next, add the attribute to the element that you wish to apply the directive to:
[app/article.component.ts] import {Component} from '@angular/core'; @Component({ selector: 'article', template: `<h1 click-to-reveal>{{ title }}</h1>`, styles: [` h1 { text-overflow: ellipsis; white-space: nowrap; overflow: hidden; max-width: 300px; } `] }) export class ArticleComponent { title: string; constructor() { this.title = `Presidential Candidates Respond to Allegations Involving Ability to Dunk`; } }
Note that the Directive is using an attribute CSS selector to associate itself with any elements that have click-to-reveal
. This of course approximates an Angular 1 attribute's directive behavior, but this form is far more flexible since it can wield the innate matchability of selectors.
Now that the Article component is aware of ClickToRevealDirective
, you must provide it the ability to attach itself to click events.
An attentive developer will have noticed that up until this point in the chapter, you have created components that listen to the events generated by the children. This is no problem since you can expressively set listeners in a parent component template on the child tag.
However, in this situation, you are looking to add a listener to the same element that the directive is being attached to. What's more, you do not have a good way of adding an event binding expression to the template from inside a directive. Ideally, you would like to not have to expose this method from inside the directive. How should you proceed then?
The solution is to utilize a new Angular construct called HostListener
. Simply put, it allows you to capture self-originating events and handle them internally:
[app/click-to-reveal.directive.ts] import { Directive, HostListener} from '@angular/core'; @Directive({ selector: '[click-to-reveal]' }) export class ClickToRevealDirective { @HostListener('click', ['$event.target']) reveal(target) { target.style['white-space'] = 'normal'; } }
With this, click
events on the <h1>
element should successfully invoke the reveal()
method.
The directive needs a way to attach to native click events. Furthermore, it needs a way to capture objects such as $event
that Angular provides to you; these objects would normally be captured in the binding expression.
@HostListener
decorates a directive method to act as the designated event handler. The first argument in its invocation is the event identification string (here, click
, but it could just as easily be a custom event from EventEmitter
), and the second argument is an array of string arguments that are evaluated as expressions.
You are not restricted to one HostListener
inside a directive. Using it merely associates an event with a directive method. So you are able to stack multiple HostListener
declarations on a single handler, for example, to listen for both a click
and mouseover
event.