Okay, so you've heard the word "directive" thrown around in Angular land and it sounds fancy and intimidating. It's not. Let's break it down with zero jargon (well, minimal jargon) and lots of copy-pasteable code.
First, what even IS a directive?
Here's the simplest way to think about it: a directive is a little instruction you attach to an HTML element that tells Angular "hey, do something special here."
That's it. That's the whole idea.
You already use directives every day without realizing it:
<div *ngIf="isLoggedIn">Welcome back!</div>
<li *ngFor="let item of items">{{ item }}</li>
*ngIf and *ngFor are directives that ship with Angular. A custom directive is just one you build yourself, for whatever weird specific thing your app needs to do.
The three flavors of directives
Angular has three kinds. Think of them like three different tools in a toolbox:
| Type | What it does | Real-world analogy |
|---|---|---|
| Attribute | Changes how an element looks or behaves | A light switch — flips something on/off |
| Structural | Adds or removes elements from the page entirely | A bouncer — decides who gets in the room |
| Component | A directive with its own HTML template | A whole mini-app inside your app |
Let's go through each one with real code you can actually run.
1. Attribute Directives (the easy one)
What it's for: changing the style or behavior of an element that's already on the page — without touching the element's own code.
Classic example: highlight text yellow when the mouse hovers over it.
Step 1 — Generate it
ng generate directive highlight
This creates a file called highlight.directive.ts with some boilerplate already filled in.
Step 2 — Write the logic
import { Directive, ElementRef, HostListener } from '@angular/core';
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
constructor(private el: ElementRef) {}
@HostListener('mouseenter') onMouseEnter() {
this.highlight('yellow');
}
@HostListener('mouseleave') onMouseLeave() {
this.highlight(null);
}
private highlight(color: string | null) {
this.el.nativeElement.style.backgroundColor = color;
}
}
What's happening here in plain English:
@HostListener('mouseenter')= "listen for the mouse hovering over this element"this.el.nativeElement= "grab the actual HTML element this directive is sitting on"- When the mouse enters, turn it yellow. When it leaves, turn it back to normal.
Step 3 — Use it in your HTML
<p appHighlight>Hover over me and watch the magic happen!</p>
Notice: appHighlight is just the selector name from the @Directive decorator, minus the brackets. Slap it on any element like an attribute (hence the name) and boom — it works.
2. Structural Directives (the powerful one)
What it's for: deciding whether an element exists on the page at all, or repeating it multiple times. This is what *ngIf and *ngFor are doing under the hood.
Let's build a simplified version of *ngIf ourselves, just so it clicks.
Step 1 — Generate it
ng generate directive unless
We'll call it appUnless — it shows the element ONLY when a condition is false (the opposite of *ngIf).
Step 2 — Write the logic
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[appUnless]'
})
export class UnlessDirective {
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef
) {}
@Input() set appUnless(condition: boolean) {
if (!condition) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.clear();
}
}
}
Plain English breakdown:
TemplateRef= a reference to the chunk of HTML we might showViewContainerRef= the "slot" in the page where that HTML could gocreateEmbeddedView()= "okay, actually insert the HTML now"clear()= "nope, remove it"
Step 3 — Use it in your HTML
<p *appUnless="isLoading">Content has finished loading!</p>
The little * in front is special Angular syntax that tells the compiler "this is a structural directive, treat it differently under the hood." You don't need to fully understand the compiler magic yet — just know the asterisk is required.
3. Component Directives (the one you already know)
Here's a fun secret: every Angular component you've ever written IS a directive. Specifically, it's a directive with a template attached. That's the only real difference.
import { Component } from '@angular/core';
@Component({
selector: 'app-user-card',
template: `
<div class="card">
<h3>{{ name }}</h3>
<p>{{ email }}</p>
</div>
`
})
export class UserCardComponent {
name = 'Jamie';
email = '[email protected]';
}
Used like this:
<app-user-card></app-user-card>
Notice @Component instead of @Directive — but structurally, it's the same family. Components are just directives that also come with their own HTML.
