Writing a Custom Directive
Designers want every clickable thing in Greenhouse, from plant cards to table rows, to tint gently on hover. Copying the same mouse handlers into ten components would be tedious and easy to get wrong. A directive lets you write the behaviour once and attach it to any element with one attribute.
The rule
A directive is a component without a template: it decorates its host.
import { Directive, input, signal } from '@angular/core';
@Directive({
selector: '[appHighlight]',
host: {
'(mouseenter)': 'active.set(true)',
'(mouseleave)': 'active.set(false)',
'[style.backgroundColor]': "active() ? highlightColor() : ''",
},
})
export class Highlight {
highlightColor = input('honeydew');
active = signal(false);
}@Directiveinstead of@Component, and there is no template: the element it sits on, its host, is what it changes.selector: '[appHighlight]'is an attribute selector. The square brackets mean "any element with this attribute".hostmaps events and bindings onto the host element. Keys in round brackets listen; keys in square brackets bind. They work exactly like template bindings, written from the host's side.input()lets each use pick a colour, withhoneydewas the default.
Using it is two steps, import and attach:
@Component({
selector: 'app-plant-list',
imports: [Highlight],
template: `
<article appHighlight>Fern</article>
<article appHighlight highlightColor="lightyellow">Cactus</article>
`,
})
export class PlantList {}Older code does the same with decorators, @HostListener('mouseenter') and @HostBinding('style.backgroundColor'). Current guidance prefers the host object. When you truly need the raw element, inject(ElementRef) gives it, but binding through host is safer and simpler.
The shortcut
Write a directive in three questions: What attribute turns it on? (the selector). What does it listen to? (round-bracket host keys). What does it change? (square-bracket host keys). Anything the user should customise becomes an input.
What turns it on?
Any element with
appThirsty, soselector: '[appThirsty]'.
The trap is forgetting that a directive, like a component, must be in the imports of the component that uses it. Without the import the attribute is just an ordinary HTML attribute that does nothing, and there is no error to warn you.
Drills
Answer fast — trust the rule.
What does the selector [appHighlight] match?
In host: { '(mouseleave)': 'active.set(false)' }, what does the key do?
What does a directive NOT have that a component does?
You write <article appHighlight> but nothing happens on hover and there is no error. What is the most likely cause?
Remember
- A directive is a class with
@Directive, an attribute selector and no template; it changes its host. - Design it with three questions: what turns it on, what it listens to, what it changes.
- Trap: a forgotten import fails silently, because the attribute is still valid HTML.
