Pebellslearn by building
0%
RoadmapsMatrixCoursesSandboxSign in
Templates and Directives  ›  Capstone Project

Writing a Custom Directive

Custom Directives 15 minA custom attribute directive is a class with the @Directive decorator and an attribute selector, which uses inputs and host bindings to add reusable behaviour to any element.

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);
}
  • @Directive instead 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".
  • host maps 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, with honeydew as 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.

Designing appThirstyStep 1 of 4
  1. What turns it on?

    Any element with appThirsty, so selector: '[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.

quick drill

What does the selector [appHighlight] match?

quick drill

In host: { '(mouseleave)': 'active.set(false)' }, what does the key do?

quick drill

What does a directive NOT have that a component does?

quick drill

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.

Practice — level up

0/1 solved

Write your answer, then compare it with a rubric and a model answer.

ExplainBuild appHighlight

Write an appHighlight directive for Greenhouse that tints any element's background while the mouse is over it, with the colour settable per use. Show how a component uses it and explain why this is a directive rather than a component.