Custom Directive in Angular - Attribute Directive | Structural Directive | Component Directive
Custom Directive in Angular - Attribute Directive | Structural Directive | Component Directive

In this post you will learn everything that you need to know about Angular directives, about structural and attribute directives and how to create your custom directives of these types.

Component directives

Inside Angular we have 3 different types of directives. We have component directives, structural directives and attribute directives.

When we create a component like this it is considered a directive.

<child></child>

Yes we name it component but essentially it is a component directive. To create it we are using component decorator and inside we can provide a template and styling. This is the main difference with other directives.

Attribute directives

When we are talking about attribute directives then we define them like this.

<div foo>Here is some text</div>

In this case foo is an attribute directive. It provides some behaviour for our element where we apply this directive.

Most important difference is that we don't have a template inside attribute directive but only inside component directive.

When we create attribute directives we simply apply some behaviour or some styles on already existing element.

Inside Angular the most popular attribute directives are ngClass and ngStyle which we use to change the behaviour of existing component.

<div [ngClass]="{active: isActive}">Here is some text</div>
<div [ngStyle]="{background: isActive ? 'red' : 'blue'}">Here is some text</div>

Structural directives

The last type that we have is structural directive. This is a directive which changes the structure of our DOM.

<div *ngIf="isActive">Here is some text</div>

Typically you will see a star symbol which shows that this is a structural directive. Additionally we have ngFor which is also a structural directive.

<div *ngFor="let user of users">{{user.title}}</div>

So if you are getting such question on the interview then you have to name these 3 types of directives.

Attribute directive implementation

Now let's create a custom directive and let's start with attribute directive. Let's say that we just need to create a simple directive which will change the background of our element. And this is it's usage.

<div highlight>This is some text</div>

Let's create this directive.

// highlight.directive.ts
@Directive({
  selector: '[highlight]'
})
export class HighlightDirective implements AfterViewInit {
  constructor(private elementRef: ElementRef) {}

  ngAfterViewInit() {
    console.log(this.elementRef)
  }
}

Here we defined our basic directive. First of all we should not forget to write a selector in square brackets. Secondly we inject elementRef in constructor to get access to an element where out directive is attached.

Now we need to register it inside our app.module.ts

@NgModule({
  declaration: [AppComponent, HighlightDirective]
})
export class AppModule {}

In our directive we can directly access DOM element and change it.

export class HighlightDirective implements AfterViewInit {
  ...
  ngAfterViewInit() {
    this.elementRef.nativeElement.style.background = 'yellow';
  }
}

This will change the background of the element where our directive is attached.

But typically we are interested in some configuration from the outside so let's make it possible to provide a background to our directive.

export class HighlightDirective implements AfterViewInit {
  @Input() color: string = 'yellow'
  ...
  ngAfterViewInit() {
    this.elementRef.nativeElement.style.background = this.color;
  }
}

As you can see we provided an Input inside and if it is not set we get a default color. Now let's use it.

<div highlight color="red">This is some text</div>

Attribute directive

As you can see in browser our text has background red because we changed it through the input.

Structural directives

Now let's create a structural directive. But actually I want to tell you that I never wrote a structural directive in the last 5 years. But if for some reason you need to do it this is how it is done.

Here we waht to create a directive like ngIf but the opposite. It will be unless.

@Directive({
  selector: '[unless]'
})
export class UnlessDirective {}

This is just an empty directive. Let's try to apply it to some element.

<div *unless="condition">This is some text</div>

<ng-template [unless]="condition">
  <div>This is some text</div>
</ng-template>

Here we added condition as a boolean in our app.component and used unless inside ng-template. This are both the same usages. The usage with the star is just a sugar.

But actually now we need to provide an input unless to our directive.

export class UnlessDirective {
  @Input() set unless(condition: boolean) {
    if (condition) {
      this.viewContainer.clear()
    } else {
      this.viewContainer.createEmbeddedView(this.templateRef)
    }
  }

  constructor(private templateRef: TemplateRef<unknown>, private viewContainer: ViewContainerRef) {}
}

Here we didn't just get unless as an input but we do some logic depending on the value that we get. If it is true then we remove the element. If it is false we render the element with createEmbeddedView function.

Now if we set our condition property to false we will see the element on the screen.

Structural

As you can see in browser our 2 elements are rendered if we set condition to false.

And actually if you want to learn Angular with NgRx from empty folder to a fully functional production application make sure to check my Angular and NgRx - Building Real Project From Scratch course.

📚 Source code of what we've done