An attribute directive adds behavior to an existing element without a template of its own. It can bind to the host element through the host property and compose other directives with hostDirectives, which lets a component adopt a directive’s behavior as part of its public API.
Before you start
You should be comfortable with directives, element refs and change detection. This article covers host binding and composition; it assumes basic directive syntax.
Step-by-step walkthrough
Step 1: Bind to the host declaratively
Use the host object on the directive to set classes, attributes and listeners instead of manipulating the DOM directly. host: { '[class.active]': 'isActive', '(click)': 'toggle()' } keeps the bindings visible in the metadata and works with server rendering.
Step 2: Compose instead of duplicating
hostDirectives: [Tooltip] makes a directive apply another directive to its host, so a component can expose tooltip behavior without repeating it. You can also re-export the host directive’s inputs and outputs so consumers can set them on the component.
Step 3: Prefer host bindings over Renderer calls
Reach for ElementRef and Renderer2 only when there is no declarative binding, because direct DOM access assumes a browser and breaks server rendering. Host bindings and attribute bindings are the idiomatic path and keep the directive testable.
Worked scenario
The directive sets a class and listens to clicks through the host metadata.
import { Directive, HostListener } from '@angular/core';
@Directive({
selector: '[appToggle]',
host: { '[class.on]': 'on' },
})
export class ToggleDirective {
on = false;
@HostListener('click')
toggle(): void {
this.on = !this.on;
}
}Walk through the example
The host binding toggles the on class from the on field, and @HostListener handles the click. The directive never touches the DOM directly, so it renders correctly on the server and the bindings are declared in one place. Any element with appToggle gains the behavior.
Common mistake
Using ElementRef.nativeElement to add classes or listeners, which bypasses Angular’s change detection and fails under server rendering. Another is duplicating a directive’s logic in a component instead of composing it with hostDirectives.
Verify the behavior
Apply the directive and assert the class toggles on click. Test the directive in isolation with a host component. Add a hostDirectives composition and confirm the composed behavior works and its re-exported inputs are settable from the template.
Interview exercise
When would you use hostDirectives instead of adding the directive in the template?
Answer and reasoning
When a component should always carry a directive’s behavior as part of its own API, so consumers get it automatically and can set its inputs without remembering to add the directive. It also keeps the selector list shorter and the behavior encapsulated. If the behavior is optional or configured per use, adding the directive in the template is clearer.
Continue learning
Compare directive composition in Angular content projection and standalone imports. Read the Angular attribute directives guide and try the Angular interview questions.