I'm updating my Learn With tutorial books that teach Angular Development for Angular 8. I decided to do a deep dive into Angular Directives. This series of posts is a snippet of what will be in the full series, and is focused on creating attribute directives.

What is an Angular Directive?

An Angular directive is just like an Angular component, except with no template. A directive can be applied to any HTML Tag or template. There are two types of directives:

  • Attribute: An Attribute directive will apply something to an existing HTML Element, or even your own Angular component.
  • Structural: A Structural directive will modify the HTML body by creating, or removing, elements.

This series of posts will focus on creating an attribute directive.

Setup

First, create a new project with the Angular CLI:

view plain print about
1ng new

You can follow through the instructions at your discretion, but you'll go through a wizard like this:

I named the project ud, for Understanding Directives. I set up routing and used regular CSS.

In the app.component.html file, delete everything except the router-outlet, so the file looks like this:

view plain print about
1<router-outlet></router-outlet>

You can run the app, but you won't see much until we start adding routes.

Create the Attribute Directive

We're going to create a directive that will toggle an HTML Element between two separate visual states, done by changing different CSS elements on the directive.

First, we'll do some setup. Create a component for creating our own attribute directive:

view plain print about
1ng generate component create-attribute-directive

You'll see something like this:

Open up the app-routing.module.ts file and a sample2 route to the Routes array:

view plain print about
1const routes: Routes = [
2 {path: 'sample2', component: CreateAttributeDirectiveComponent},
3 { path: '**', redirectTo: '/sample2'}
4];

Now, use the Angular CLI to create our first directive:

view plain print about
1ng generate directive directives/reverse

You'll see something like this:

I put the new directive in the directive directory. If nothing is specified it will be put in src/app by default, but long term I like to keep my code better organized without a lot of things in the main app directory. Open up the reverse.directive.ts:

view plain print about
1import { Directive } from '@angular/core';
2@Directive({
3 selector: '[appReverse]'
4})
5export class ReverseDirective {
6 constructor() { }
7}

First, we import the Directive class. This is metadata used to define the directive. The only thing we define, at this point in the metadata is the selector. We'll use this when we apply the directive to an HTML element, the Angular module, something like this:

view plain print about
1<button appReverse></button>

Right now, the directive doesn't do anything, but we'll come back to this in the HTML template later. It is strongly recommended you do not name your custom directives with a prefix ng because that is used by the Angular framework and we want to avoid conflicts with other existing directives.

I want to note that the directive is loaded in the main Angular module. Open up app.module.ts and you'll see it in the declarations section:

view plain print about
1@NgModule({
2 declarations: [
3 AppComponent,
4 UseAttributeDirectiveComponent,
5 CreateAttributeDirectiveComponent,
6 ReverseDirective
7 ],
8 // other stuff
9})

If you do not add your directive to the @NgModule your app won't know how to find it and you'll get compiler errors.

The first thing we want to do is inject the ElementRef into the directive class:

view plain print about
1constructor(private el: ElementRef) { }

Dependency Injection for Directives works just like it does for other components, so we inject the value into the constructor. This will give the directive class a reference to the component which is was placed on.

This directive needs to pass in a value of the directive name, and then make changes to the ElementRef's styles based on that. To do that we'll need to define an Input() metadata. The simplest way to do this:

view plain print about
1@Input('appReverse') reversedState: Boolean;

When the directive sees the appReverse directive applied to an HTML tag as an attribute, the value will be set in the reversedState value inside the directive class. In order to run some code whenever that value changes I'm going to switch it to a get/set method:

view plain print about
1private _reversedState = false;
2@Input('appReverse')
3get reversedState() {
4 return this._reversedState;
5}
6set reversedState(input) {
7 this._reversedState = input;
8 this.changeState();
9}

The get and set methods are pretty standard. A private variable holds the state. The get method will return that private variable, and the set method will set it. The set method also runs a changeState() method, so let's look at that:

view plain print about
1changeState() {
2 if (this.reversedState) {
3 this.el.nativeElement.style.backgroundColor = '#000000';
4 this.el.nativeElement.style.color = '#ffffff';
5 } else {
6 this.el.nativeElement.style.backgroundColor = '#ffffff';
7 this.el.nativeElement.style.color = '#000000';
8 }
9}

This method checks the reversedState value and sets the background and foreground colors. I use nativeElement property on the ElementRef to drill down into the component and set specific styles. For the moment, I'm hard coding the color values, but we'll revisit that in a later sample.

How do we use the directive? Open the create-attribute-directive.component.html file:

view plain print about
1<button (click)="reverseState()" [appReverse]="reversedState">
2 Reverse State
3</button>

The appReverse directive is placed directly on the button; and it is given the value of reversedState, which is a Boolean value in the component that we'll create. The click button is used to call a reverseState() method that will toggle the reversedState property.

Open the create-attribute-directive.ts file. First create the reversedState property:

view plain print about
1reversedState = false;

Then add the reverseState() method:

view plain print about
1reverseState() {
2 this.reversedState = !this.reversedState;
3}

We've seen this method, and property, set up identically in the previous sample. The major change is in how the new styles are applied to the button.

Rerun the app:

Click the button and the state should switch:

Play with it to switch the state back and forth.

Congratulations you did it!

What's Next?

While this is a simple example, it does show that we can create our own directive and use it to affect the HTML element is is applied to. This type of example is pretty common when you search for directive tutorials throughout the web, however by accessing the nativeElement directively it creates a dependency between your directive and the internals of Angular which may change.

The next article in this series will show you how to address this using the Angular renderer.