Angular Directives

Read one Angular interview question at a time, then flip for the complete explanation, example, and code.

0/82 Known filtered set
Level
Read Full Guide Question: 1 of 82

Angular Interview Questions · Angular Directives Companion Article

Chapter 9: Angular Directives

Angular directives are one of the main ways Angular adds reusable behavior to HTML elements and components. A directive can change how an element looks, react to user events, expose configurable inputs, or even control how templates are added to and removed from the DOM. Instead of repeating the same UI behavior in several components, you can move that behavior into a directive and reuse it wherever required.

What Are Directives?

A directive is a TypeScript class that Angular associates with elements in a template.

Directives can be used to:

  • change an element's appearance
  • add reusable behavior
  • react to mouse or keyboard events
  • manipulate classes and styles
  • expose configurable inputs
  • interact with the host element
  • control template rendering
  • combine multiple reusable behaviors
  • add functionality to components without modifying their source code

A simple custom directive is created with the @Directive() decorator.

TypeScript
import { Directive } from '@angular/core';

@Directive({
  selector: '[appHighlight]'
})
export class HighlightDirective {
}

The selector:

Text
[appHighlight]

means Angular activates the directive whenever it finds an element containing the appHighlight attribute.

HTML
<p appHighlight>Important information</p>

Angular directives are especially useful when the same behavior needs to be applied to multiple elements or components. Angular's current documentation describes directives as reusable units for changing an element's appearance or behavior or influencing how it participates in the DOM.

Main Types of Angular Directives

Angular directives can be understood through three major categories:

Directive TypeMain PurposeExample
Component directiveCreates UI with its own template@Component()
Attribute directiveChanges appearance or behaviorNgClass, custom highlight directive
Structural directiveControls template renderingCustom *appPermission directive

A useful way to remember the difference is:

Text
Component → creates a UI block
Attribute directive → changes an existing element
Structural directive → controls whether/how template content exists

Component Directives

An Angular component is a specialized kind of directive.

The main difference is that a component has its own template.

TypeScript
import { Component } from '@angular/core';

@Component({
  selector: 'app-user-card',
  template: `
    <h2>User Profile</h2>
    <p>Welcome to the application.</p>
  `
})
export class UserCardComponent {
}

Usage:

HTML
<app-user-card></app-user-card>

Angular creates the component and renders its template inside the matching host element.

A normal directive does not define its own view in this way.

For example:

HTML
<p appHighlight>Hello</p>

Here the <p> already exists. appHighlight only adds behavior to it.

Component vs Directive

FeatureComponentDirective
Has templateYesNormally no
Creates UIYesUsually modifies existing UI
Uses selectorYesYes
Can accept inputsYesYes
Can respond to eventsYesYes
Can use dependency injectionYesYes
Typical useComplete UI featureReusable behavior

When to Use a Component

Use a component when the feature:

  • owns a visual section
  • requires its own HTML template
  • represents something such as a card, form, table, modal, toolbar, or page section

When to Use a Directive

Use a directive when you already have an element or component and only want to add behavior.

Good directive examples include:

  • autofocus
  • highlighting
  • keyboard shortcuts
  • permission behavior
  • input formatting
  • tooltips
  • drag behavior
  • accessibility behavior
  • validation indicators

If reusable behavior needs its own markup and UI structure, a component is normally the better abstraction.

Attribute Directives

An attribute directive changes the appearance or behavior of an existing element or component.

It does not normally create a separate template.

Examples include:

Text
NgClass
NgStyle
custom highlight directive
custom autofocus directive
custom tooltip behavior

A basic attribute directive could look like this:

TypeScript
import { Directive } from '@angular/core';

@Directive({
  selector: '[appImportant]'
})
export class ImportantDirective {
}

Usage:

HTML
<p appImportant>This information is important.</p>

The square brackets inside the selector:

TypeScript
selector: '[appImportant]'

represent an attribute selector.

You do not write those selector brackets when using the directive:

HTML
<p appImportant>...</p>

Why Create Attribute Directives?

Consider an application where many elements should highlight when a user moves the mouse over them.

Without a directive, you might repeat:

HTML
<p
  (mouseenter)="highlight()"
  (mouseleave)="removeHighlight()">
  Product
</p>

in many templates.

A reusable directive lets you centralize that behavior:

HTML
<p appHighlight>Product</p>

<div appHighlight>Order</div>

<span appHighlight>Customer</span>

The directive becomes a reusable UI behavior.

One-Off Binding vs Attribute Directive

Not every style or event needs a custom directive.

For a single element, Angular template binding is usually simpler.

For example:

HTML
<button [class.active]="selected">
  Save
</button>

There is usually no reason to create an ActiveButtonDirective just for this one case.

Create a directive when the behavior:

  • appears in several places
  • contains meaningful reusable logic
  • combines several events or bindings
  • needs configurable inputs
  • should be maintained independently

Angular specifically recommends normal template bindings for one-off behaviors and attribute directives when that behavior should be packaged into a reusable unit.

Structural Directives

Structural directives control the structure of rendered content.

Instead of merely changing an existing element's color or class, a structural directive can:

  • create embedded views
  • remove views
  • repeat templates
  • conditionally render templates
  • provide values to a template
  • implement reusable rendering rules

Historically, common Angular examples included:

Text
*ngIf
*ngFor
*ngSwitchCase

In modern Angular, everyday conditions and loops should normally use the built-in control-flow syntax:

HTML
@if (loggedIn) {
  <p>Welcome back.</p>
}
HTML
@for (product of products; track product.id) {
  <p>{{ product.name }}</p>
}
HTML
@switch (status) {
  @case ('active') {
    <p>Active</p>
  }

  @default {
    <p>Unknown</p>
  }
}

As of Angular 22, NgIf, NgFor, and NgSwitch are deprecated and Angular recommends the newer @if, @for, and @switch blocks instead. Custom structural directives are still useful when your application requires reusable rendering behavior that normal control flow does not provide.

When Would You Still Create a Structural Directive?

Suppose your application frequently needs this rule:

> Display an element only if the current user has a specific permission.

Instead of repeating permission logic everywhere, you could design:

HTML
<button *appPermission="'ADMIN'">
  Delete User
</button>

Another example might be:

HTML
<section *appFeatureEnabled="'new-dashboard'">
  New Dashboard
</section>

These rules are different from ordinary @if conditions because they represent reusable application behavior.

How Structural Directive Shorthand Works

Traditional custom structural directives commonly use the * shorthand.

For example:

HTML
<p *appPermission="'ADMIN'">
  Administrator content
</p>

Conceptually, Angular transforms structural-directive shorthand into an <ng-template>.

The idea is similar to:

HTML
<ng-template appPermission [appPermission]="'ADMIN'">
  <p>Administrator content</p>
</ng-template>

A structural directive commonly works with:

Text
TemplateRef
ViewContainerRef

TemplateRef

Provides access to the template controlled by the directive.

ViewContainerRef

Represents a location where Angular can insert or remove rendered views.

A simplified example:

TypeScript
import {
  Directive,
  TemplateRef,
  ViewContainerRef,
  inject
} from '@angular/core';

@Directive({
  selector: '[appShow]'
})
export class ShowDirective {
  private template = inject(TemplateRef);
  private container = inject(ViewContainerRef);
}

Custom structural directives can therefore control whether and how the associated template is instantiated.

One Structural Directive Per Element

With traditional *directive shorthand, only one structural directive can be attached directly to the same element.

For example, avoid trying to combine multiple structural directives on one element.

A wrapper such as <ng-container> can instead create separate structural layers.

Conceptually:

HTML
<ng-container *appPermission="'ADMIN'">
  <button *appFeatureEnabled="'delete-feature'">
    Delete
  </button>
</ng-container>

<ng-container> is useful because it groups template behavior without creating an unnecessary HTML element in the final DOM.

Built-in Directives

Angular provides built-in directives for several common behaviors.

Important examples include:

  • NgClass
  • NgStyle
  • NgOptimizedImage
  • NgComponentOutlet
  • NgTemplateOutlet
  • NgNonBindable
  • legacy structural directives such as NgIf, NgFor, and NgSwitch

Some built-in directives come from:

TypeScript
@angular/common

For example:

TypeScript
import { NgClass, NgStyle } from '@angular/common';

Modern standalone components can import only the directives they require.

TypeScript
@Component({
  imports: [NgClass]
})
export class ExampleComponent {
}

This makes dependencies visible directly in the component.

NgClass

NgClass dynamically adds or removes CSS classes from an element.

Suppose we have:

CSS
.success {
  color: green;
}

.error {
  color: red;
}

.highlight {
  font-weight: bold;
}

A simple conditional class can be written as:

HTML
<p [ngClass]="{ 'success': isSuccessful }">
  Operation status
</p>

If:

TypeScript
isSuccessful = true;

Angular adds:

Text
success

to the element.

Using Multiple Conditions with NgClass

HTML
<div
  [ngClass]="{
    'success': status === 'success',
    'error': status === 'error',
    'highlight': important
  }">
  Order Status
</div>

Each class is evaluated separately.

NgClass with a String

TypeScript
currentClasses = 'card selected';
HTML
<div [ngClass]="currentClasses">
  Product
</div>

Both classes are applied.

NgClass with an Array

TypeScript
currentClasses = ['card', 'selected'];
HTML
<div [ngClass]="currentClasses">
  Product
</div>

NgClass with an Object

TypeScript
currentClasses = {
  active: true,
  disabled: false,
  premium: true
};
HTML
<div [ngClass]="currentClasses">
  Account
</div>

The resulting classes include:

Text
active
premium

but not:

Text
disabled

NgClass supports string, array, set, and object-based class definitions.

Should You Always Use NgClass?

No.

Modern Angular recommends normal class binding for simpler cases.

Instead of:

HTML
<div [ngClass]="{ 'active': isActive }">
  User
</div>

prefer:

HTML
<div [class.active]="isActive">
  User
</div>

You can also bind several classes:

HTML
<div
  [class]="{
    active: isActive,
    premium: isPremium
  }">
  User
</div>

Direct class bindings are often:

  • easier to read
  • closer to normal HTML
  • simpler for small conditions
  • free from the additional directive dependency

Angular's current style guide recommends class bindings over NgClass when practical.

NgStyle

NgStyle dynamically assigns inline CSS styles.

Example:

HTML
<p [ngStyle]="{ 'color': textColor }">
  Dynamic text
</p>

Component:

TypeScript
textColor = 'blue';

Angular produces an inline color style based on the value.

Multiple Styles with NgStyle

HTML
<div
  [ngStyle]="{
    'color': textColor,
    'background-color': backgroundColor,
    'font-size.px': fontSize
  }">
  Styled content
</div>

Component:

TypeScript
textColor = 'white';
backgroundColor = 'navy';
fontSize = 18;

Notice:

Text
font-size.px

The .px suffix tells Angular that the numeric value should use pixels.

Other units can also be expressed where appropriate.

NgStyle with an Object

You can keep the styles in the component.

TypeScript
boxStyles = {
  'padding': '16px',
  'border-radius': '8px',
  'font-weight': '600'
};

Template:

HTML
<div [ngStyle]="boxStyles">
  Product card
</div>

This can be useful when a group of styles comes from application state.

Should You Always Use NgStyle?

No.

For a single style, direct style binding is clearer:

HTML
<p [style.color]="textColor">
  Hello
</p>

For numeric values:

HTML
<div [style.width.px]="boxWidth">
  Box
</div>

You can also bind a style object:

HTML
<div
  [style]="{
    color: textColor,
    backgroundColor: backgroundColor
  }">
  Content
</div>

Angular's modern style guidance prefers style bindings over NgStyle for cases that do not require the directive.

NgClass vs NgStyle

Although both modify presentation, they solve different problems.

NgClassNgStyle
Adds/removes CSS classesAdds/removes inline CSS styles
Better when styles already exist in CSSUseful for truly dynamic style values
Example: active classExample: dynamic width
Encourages reusable CSS rulesCreates element-specific styles

For maintainable applications, CSS classes are usually preferable when the visual state has a meaningful name.

For example:

HTML
<div [class.error]="hasError">

is usually more maintainable than repeatedly specifying:

HTML
<div [style.color]="hasError ? 'red' : 'black'">

when an application already has a reusable .error design.

Custom Attribute Directives

Custom directives are useful when application-specific behavior should be reusable.

Consider a directive that highlights an element when the pointer enters it.

TypeScript
import { Directive, ElementRef, inject } from '@angular/core';

@Directive({
  selector: '[appHighlight]',
  host: {
    '(mouseenter)': 'onMouseEnter()',
    '(mouseleave)': 'onMouseLeave()'
  }
})
export class HighlightDirective {
  private element = inject(ElementRef);

  onMouseEnter(): void {
    this.element.nativeElement.style.backgroundColor = 'yellow';
  }

  onMouseLeave(): void {
    this.element.nativeElement.style.backgroundColor = '';
  }
}

Usage:

HTML

<p appHighlight>
  Move the pointer over this text.
</p>

The HTML element receiving the directive is known as its host element.

Here:

HTML
<p appHighlight>

the <p> is the host element.

Creating a Directive with Angular CLI

Angular CLI can generate the basic directive files.

Bash
ng generate directive highlight

The shorter form is:

Bash
ng g d highlight

The generated class contains the basic @Directive() configuration.

This is useful because Angular CLI creates the expected files and naming structure automatically.

Directive Inputs

A directive becomes much more reusable when its behavior can be configured from the template.

For example, a highlighting directive should not always force the same color.

Instead of:

HTML
<p appHighlight>

we could allow:

HTML
<p [appHighlight]="'lightblue'">
  Highlight me
</p>

Modern Angular provides the input() API for component and directive inputs.

TypeScript
import {
  Directive,
  ElementRef,
  inject,
  input
} from '@angular/core';

@Directive({
  selector: '[appHighlight]',
  host: {
    '(mouseenter)': 'onMouseEnter()',
    '(mouseleave)': 'onMouseLeave()'
  }
})
export class HighlightDirective {
  private element = inject(ElementRef);

  appHighlight = input('');

  onMouseEnter(): void {
    this.element.nativeElement.style.backgroundColor =
      this.appHighlight() || 'yellow';
  }

  onMouseLeave(): void {
    this.element.nativeElement.style.backgroundColor = '';
  }
}

Usage:

HTML
<p [appHighlight]="'lightgreen'">
  Product available
</p>

Notice that:

TypeScript
appHighlight = input('');

has the same name as the directive selector.

Therefore:

HTML
[appHighlight]

both activates the directive and supplies its input value.

Angular's current directive documentation uses this pattern for configurable attribute directives.

Multiple Directive Inputs

A directive can expose more than one input.

Example:

TypeScript
defaultColor = input('yellow');
appHighlight = input('');

The template can provide both:

HTML
<p
  [appHighlight]="selectedColor"
  defaultColor="lightgray">
  Highlighted content
</p>

The directive can then decide which value should be used.

TypeScript
const color =
  this.appHighlight() ||
  this.defaultColor();

Required Directive Inputs

Modern signal inputs can also be required.

TypeScript
role = input.required<string>();

Angular expects the consumer to provide the value.

For example:

HTML
<div appAccessControl role="ADMIN">
  Admin section
</div>

Required inputs are useful when the directive cannot work correctly without configuration.

Input Aliases

The internal TypeScript property name does not always need to match the public template name.

Example:

TypeScript
color = input('', {
  alias: 'appHighlight'
});

Template:

HTML
<p [appHighlight]="'orange'">
  Warning
</p>

Inside TypeScript you read:

TypeScript
this.color()

while consumers bind:

Text
appHighlight

Aliases can help preserve a clean public directive API or avoid property-name collisions.

Angular's input() API supports optional, required, transformed, and aliased inputs.

input() vs @Input()

Older and existing Angular applications frequently use:

TypeScript
@Input() color = '';

This API continues to exist.

Modern Angular also supports:

TypeScript
color = input('');

The signal-based input is read using:

TypeScript
this.color()

while a decorator-based input is normally read as:

TypeScript
this.color

Angular's signal input API is production-ready and is the modern input model, while @Input() remains supported for existing code.

Host Element Interaction

The element on which a directive exists is called the host element.

Example:

HTML
<button appPermissionButton>
  Delete
</button>

The host element is:

HTML
<button>

A directive may need to interact with properties, attributes, classes, styles, or events of that host.

Common host interactions include:

Text
adding a class
changing a style
setting an ARIA attribute
changing tabindex
reacting to clicks
reacting to mouseenter
reacting to keyboard events
setting disabled state

Accessing the Host with ElementRef

Angular can provide a reference to the host element using ElementRef.

TypeScript
import {
  Directive,
  ElementRef,
  inject
} from '@angular/core';

@Directive({
  selector: '[appFocus]'
})
export class FocusDirective {
  private element = inject(ElementRef);
}

The underlying native element is available through:

TypeScript
this.element.nativeElement

For example:

TypeScript
this.element.nativeElement.focus();

ElementRef is useful when direct access to a DOM element is genuinely required.

However, reusable directives should avoid unnecessary low-level DOM manipulation when Angular's declarative host bindings can express the same behavior more clearly.

Host Bindings

A host binding connects directive state to the host element.

Possible host bindings include:

  • properties
  • attributes
  • classes
  • styles

Modern Angular allows these bindings through the host property of @Directive().

Example:

TypeScript
import { Directive, signal } from '@angular/core';

@Directive({
  selector: '[appSelectable]',
  host: {
    '[class.selected]': 'selected()',
    '[attr.aria-selected]': 'selected()'
  }
})
export class SelectableDirective {
  selected = signal(false);
}

Usage:

HTML
<div appSelectable>
  Product
</div>

When:

TypeScript
selected()

returns true, Angular can apply:

Text
selected

to the host element and update the ARIA attribute.

Binding Host Styles

TypeScript
@Directive({
  selector: '[appStatus]',
  host: {
    '[style.opacity]': 'disabled ? 0.5 : 1'
  }
})
export class StatusDirective {
  disabled = false;
}

The directive controls the host element's opacity.

Binding Host Attributes

Accessibility information can also be attached to the host.

TypeScript
@Directive({
  selector: '[appExpandable]',
  host: {
    'role': 'button',
    '[attr.aria-expanded]': 'expanded'
  }
})
export class ExpandableDirective {
  expanded = false;
}

Static host value:

TypeScript
'role': 'button'

Dynamic host value:

TypeScript
'[attr.aria-expanded]': 'expanded'

This is an important use case because reusable directives can package accessibility behavior together with interaction logic.

@HostBinding

You may encounter this syntax in existing Angular applications:

TypeScript
import {
  Directive,
  HostBinding
} from '@angular/core';

@Directive({
  selector: '[appActive]'
})
export class ActiveDirective {
  @HostBinding('class.active')
  active = true;
}

This binds:

Text
active

to:

Text
class.active

on the host element.

Another example:

TypeScript
@HostBinding('attr.aria-disabled')
disabled = false;

Although @HostBinding remains available, current Angular guidance recommends using the host property for new code. Angular documentation states that @HostBinding and @HostListener remain primarily for backwards compatibility.

Preferred modern form:

TypeScript
@Directive({
  selector: '[appActive]',
  host: {
    '[class.active]': 'active'
  }
})
export class ActiveDirective {
  active = true;
}

Host Listeners

A directive often needs to react to events generated by its host element.

Examples:

Text
click
mouseenter
mouseleave
focus
blur
keydown
keyup
input

Modern Angular host listeners can be defined through host.

TypeScript
@Directive({
  selector: '[appClickable]',
  host: {
    '(click)': 'onClick()'
  }
})
export class ClickableDirective {
  onClick(): void {
    console.log('Host clicked');
  }
}

Usage:

HTML
<button appClickable>
  Click
</button>

When the button emits a click event, Angular invokes:

TypeScript
onClick()

Passing the Event Object

Use $event when the handler needs the actual browser event.

TypeScript
@Directive({
  selector: '[appKeyLogger]',
  host: {
    '(keydown)': 'onKeyDown($event)'
  }
})
export class KeyLoggerDirective {
  onKeyDown(event: KeyboardEvent): void {
    console.log(event.key);
  }
}

Usage:

HTML
<input appKeyLogger>

@HostListener

Existing Angular applications may use:

TypeScript
import {
  Directive,
  HostListener
} from '@angular/core';

@Directive({
  selector: '[appHover]'
})
export class HoverDirective {
  @HostListener('mouseenter')
  onMouseEnter(): void {
    console.log('Mouse entered');
  }

  @HostListener('mouseleave')
  onMouseLeave(): void {
    console.log('Mouse left');
  }
}

Passing $event:

TypeScript
@HostListener('keydown', ['$event'])
onKeyDown(event: KeyboardEvent): void {
  console.log(event.key);
}

This remains valid Angular syntax.

For new code, however, Angular currently recommends:

TypeScript
host: {
  '(keydown)': 'onKeyDown($event)'
}

instead of the decorator-based form.

Global Host Events

Host event bindings can also target certain global objects.

Examples include:

Text
window:
document:
body:

For example:

TypeScript
host: {
  '(document:keydown.escape)': 'close()'
}

This could be useful in reusable UI behavior such as:

  • dialogs
  • menus
  • dropdowns
  • keyboard shortcuts

Global event handlers should be used carefully because each directive instance can participate in the event.

Host Binding vs Host Listener

The difference is straightforward.

Host Binding

Controls a value on the host.

Example:

TypeScript
'[class.active]': 'active'

Direction:

Text
Directive state → Host element

Host Listener

Responds to an event from the host.

Example:

TypeScript
'(click)': 'toggle()'

Direction:

Text
Host event → Directive method

A directive commonly uses both.

TypeScript
@Directive({
  selector: '[appToggle]',
  host: {
    '[class.active]': 'active',
    '(click)': 'toggle()'
  }
})
export class ToggleDirective {
  active = false;

  toggle(): void {
    this.active = !this.active;
  }
}

Usage:

HTML
<button appToggle>
  Toggle
</button>

This is a good example of a directive encapsulating both state-related presentation and reusable interaction behavior.

Directive Composition API

Large applications often develop several small reusable behaviors.

Imagine:

Text
KeyboardBehavior
TooltipBehavior
MenuBehavior
FocusBehavior
AccessibilityBehavior

A component may need several of them.

Instead of requiring every consumer to manually apply every directive:

HTML
<app-menu
  appKeyboard
  appTooltip
  appFocus>
</app-menu>

Angular provides the Directive Composition API.

A component can automatically apply directives to its own host element through:

Text
hostDirectives

Example:

TypeScript
import {
  Component,
  Directive
} from '@angular/core';

@Directive({
  selector: '[appMenuBehavior]',
  host: {
    '[attr.role]': '"menu"'
  }
})
export class MenuBehaviorDirective {
}

@Component({
  selector: 'app-admin-menu',
  template: `
    <button>Users</button>
    <button>Settings</button>
  `,
  hostDirectives: [
    MenuBehaviorDirective
  ]
})
export class AdminMenuComponent {
}

When Angular creates:

HTML
<app-admin-menu></app-admin-menu>

it also creates the configured host directive and applies its host behavior to the component's host element.

Why Directive Composition Is Useful

Without composition, components may duplicate behavior.

For example:

Text
ButtonComponent
MenuComponent
DropdownComponent
ToolbarComponent

might all implement similar keyboard handling.

Instead, the application can create:

Text
KeyboardNavigationDirective

and compose it into the components that need it.

Benefits include:

  • less duplicate code
  • smaller focused directives
  • easier testing
  • reusable behavior
  • cleaner component classes
  • separation of responsibilities
  • consistent interaction across components

Exposing Host Directive Inputs

An important rule is that inputs and outputs from host directives are not automatically exposed through the component.

Suppose:

TypeScript
@Directive({
  selector: '[appMenuBehavior]'
})
export class MenuBehaviorDirective {
  menuId = input('');
}

If a component uses it:

TypeScript
@Component({
  selector: 'app-menu',
  hostDirectives: [
    MenuBehaviorDirective
  ],
  template: `...`
})
export class MenuComponent {
}

the menuId input is not automatically public on <app-menu>.

You can explicitly expose it:

TypeScript
@Component({
  selector: 'app-menu',
  hostDirectives: [
    {
      directive: MenuBehaviorDirective,
      inputs: ['menuId']
    }
  ],
  template: `...`
})
export class MenuComponent {
}

Now a consumer can use:

HTML
<app-menu menuId="main-menu"></app-menu>

Aliasing Host Directive Inputs

The public API can also use a different name.

TypeScript
hostDirectives: [
  {
    directive: MenuBehaviorDirective,
    inputs: ['menuId: id']
  }
]

Now consumers use:

HTML
<app-menu id="main-menu"></app-menu>

while the underlying directive still uses:

Text
menuId

This allows a component to reuse behavior while presenting a cleaner public API.

Important Directive Composition Rules

Host directives have some important characteristics:

  • they are applied statically at compile time
  • they cannot simply be added dynamically at runtime through hostDirectives
  • their selector is ignored when they are applied through composition
  • their inputs and outputs are private to the composition unless explicitly exposed
  • a directive can compose other directives
  • multiple reusable behaviors can therefore be layered
  • host directives run before the component or directive that includes them

Angular supports composing directives into both components and other directives, allowing reusable behavior to be built in layers.

Directives and Standalone Angular

Modern Angular applications commonly use standalone components and directives.

A custom directive can be imported directly into the component that requires it.

Example:

TypeScript
@Component({
  selector: 'app-root',
  imports: [
    HighlightDirective
  ],
  template: `
    <p appHighlight>
      Angular Directives
    </p>
  `
})
export class AppComponent {
}

This keeps dependencies close to the component where they are used.

For built-in directives:

TypeScript
import {
  NgClass,
  NgStyle
} from '@angular/common';

you can similarly import only what the component needs.

Practical Example: Reusable Status Directive

Consider an application that displays many statuses:

Text
ACTIVE
PENDING
ERROR

We can create reusable behavior rather than repeating class logic everywhere.

TypeScript
import {
  Directive,
  input
} from '@angular/core';

@Directive({
  selector: '[appStatus]',
  host: {
    '[class.status-active]': 'appStatus() === "ACTIVE"',
    '[class.status-pending]': 'appStatus() === "PENDING"',
    '[class.status-error]': 'appStatus() === "ERROR"',
    '[attr.data-status]': 'appStatus()'
  }
})
export class StatusDirective {
  appStatus = input.required<string>();
}

Usage:

HTML
<span [appStatus]="order.status">
  {{ order.status }}
</span>

The component using the directive no longer needs to know how every status should map to host behavior.

That rule belongs to the directive.

This becomes especially valuable when the same behavior appears in:

Text
orders
payments
users
shipments
notifications

Practical Example: Autofocus Directive

Some applications need an element to receive focus automatically.

Instead of repeating focus logic across many components, it can be packaged in a directive.

TypeScript
import {
  AfterViewInit,
  Directive,
  ElementRef,
  inject
} from '@angular/core';

@Directive({
  selector: '[appAutofocus]'
})
export class AutofocusDirective implements AfterViewInit {
  private element = inject(ElementRef);

  ngAfterViewInit(): void {
    this.element.nativeElement.focus();
  }
}

Usage:

HTML
<input appAutofocus>

Possible real-world uses include:

  • search boxes
  • dialogs
  • forms
  • command interfaces

Before automatically moving focus, however, consider accessibility and user expectations. Focus behavior should assist users rather than unexpectedly interrupt navigation.

Practical Example: Clickable Host Behavior

A reusable interactive directive might combine:

  • event handling
  • CSS state
  • accessibility attributes
TypeScript
import {
  Directive,
  signal
} from '@angular/core';

@Directive({
  selector: '[appSelectable]',
  host: {
    'role': 'button',
    '[class.selected]': 'selected()',
    '[attr.aria-pressed]': 'selected()',
    '(click)': 'toggle()',
    '(keydown.enter)': 'toggle()'
  }
})
export class SelectableDirective {
  selected = signal(false);

  toggle(): void {
    this.selected.update(value => !value);
  }
}

Usage:

HTML
<div appSelectable>
  Select product
</div>

This example demonstrates why directives can be more valuable than scattered template bindings: the related behavior exists in one reusable place.

Directive Selector Design

Custom directive selectors commonly use an application-specific prefix.

Good:

TypeScript
selector: '[appHighlight]'
TypeScript
selector: '[appPermission]'
TypeScript
selector: '[shopPrice]'

Generic selectors such as:

TypeScript
selector: '[highlight]'

can potentially clash with other libraries or future HTML-related names.

A project-specific prefix makes ownership clearer.

Avoid Using Directives for Everything

Directives are powerful, but unnecessary abstraction can make an Angular application harder to understand.

Do not create a directive simply to replace:

HTML
[class.active]="active"

with:

HTML
appActive

unless appActive actually represents reusable behavior.

A directive provides the most value when it combines or encapsulates logic that belongs together.

Good candidates include:

Text
DOM behavior + events
accessibility attributes + keyboard support
permission logic + rendering
validation state + presentation
tooltip logic + positioning behavior
reusable input formatting
drag/drop interaction

Common Directive Mistakes

Creating a Directive for One Simple Binding

Unnecessary:

HTML
<div appRedText>

when this would be enough:

HTML
<div class="red-text">

Use directives for behavior, not simply to avoid writing CSS.

Repeating Complex Logic in Templates

Instead of repeating:

HTML
[class.disabled]="..."
[attr.aria-disabled]="..."
(keydown)="..."
(click)="..."

across many elements, consider whether the behavior represents a reusable directive.

Confusing Attribute and Structural Directives

Attribute directive:

HTML
<p appHighlight>

changes the existing <p>.

Structural directive:

HTML
<p *appPermission="'ADMIN'">

can determine whether the template is instantiated.

Using Legacy Structural Directives in New Angular Code

For ordinary loops and conditions, avoid starting new code with:

HTML
*ngIf
*ngFor
[ngSwitch]

Prefer:

Text
@if
@for
@switch

Custom structural directives remain appropriate for reusable rendering abstractions.

Overusing NgClass

Instead of:

HTML
[ngClass]="{ active: active }"

prefer:

HTML
[class.active]="active"

when the requirement is simple.

Overusing NgStyle

Instead of:

HTML
[ngStyle]="{ color: color }"

prefer:

HTML
[style.color]="color"

for a straightforward style binding.

Putting Business Logic Inside Presentation Directives

A directive should usually focus on reusable UI or template behavior.

Large business workflows, API coordination, or domain processing are normally better placed in services or appropriate application layers.

Creating Large "Do Everything" Directives

Avoid one directive that handles:

Text
tooltip
permissions
animations
keyboard behavior
tracking
styling
validation

Smaller directives are easier to understand and test.

The Directive Composition API exists partly so focused behaviors can be combined when necessary.

Directives vs Components vs Pipes vs Services

These Angular features solve different problems.

FeatureMain Responsibility
ComponentOwn and render UI
DirectiveAdd reusable behavior
PipeTransform a value for display
ServiceShare application logic or data

Example requirements:

"Create a user profile card"

Use a:

Text
Component

"Highlight elements when hovered"

Use a:

Text
Directive

"Format a date"

Use a:

Text
Pipe

"Retrieve users from an API"

Use a:

Text
Service

Understanding these boundaries helps prevent Angular applications from becoming unnecessarily complex.

Attribute Directive vs Structural Directive

Attribute DirectiveStructural Directive
Changes an existing elementControls template rendering
Normally keeps the element in the DOMCan create/remove embedded views
Example: highlightingExample: permission rendering
Commonly interacts with hostCommonly uses template/view APIs
Does not normally own a templateOperates on template content

Think of it as:

Text
Attribute directive:
"How should this element behave?"

Structural directive:
"Should/how should this template exist?"

Modern Angular Directive Practices

For new Angular applications, a practical set of guidelines is:

  1. Use normal template bindings when behavior is needed only once.
  2. Create attribute directives for genuinely reusable element behavior.
  3. Use @if, @for, and @switch for ordinary template control flow.
  4. Create custom structural directives only when reusable rendering semantics justify them.
  5. Prefer signal-based input() for modern directive inputs.
  6. Prefer [class...] and [style...] bindings over NgClass and NgStyle for simple cases.
  7. Prefer the host property for host properties and events in new code.
  8. Understand @HostBinding and @HostListener because they remain common in existing Angular projects.
  9. Keep directives small and focused.
  10. Use the Directive Composition API when several focused behaviors need to be combined.

Choosing the Correct Technique

Use this decision process when implementing a feature.

Requirement: Change one element's class

Use:

HTML
[class.active]="active"

Requirement: Change one element's style

Use:

HTML
[style.color]="color"

Requirement: Reuse the same element behavior across the application

Create:

Text
Attribute Directive

Requirement: Display content conditionally

Use:

Text
@if

Requirement: Repeat content

Use:

Text
@for

Requirement: Implement reusable permission-based rendering

Consider:

Text
Custom Structural Directive

Requirement: Build a complete reusable UI block

Create:

Text
Component

Requirement: Apply reusable behavior automatically to a component

Consider:

Text
Directive Composition API

Real-World Uses of Angular Directives

Directives are frequently useful for application behaviors such as:

  • automatic focus
  • element highlighting
  • role-based visibility
  • feature flags
  • input masking
  • currency input behavior
  • keyboard navigation
  • tooltip behavior
  • click-outside detection
  • drag interactions
  • permission controls
  • reusable validation presentation
  • analytics hooks
  • accessibility behavior
  • responsive UI behavior
  • scroll-based interaction

The important point is not simply that directives can manipulate an element. Their greater value is that they allow reusable behavior to have a clear home in the application architecture.

Chapter Summary

Angular directives extend the behavior of elements and components without forcing the same logic to be repeated throughout an application.

The main ideas from this chapter are:

  • Angular components are specialized directives with templates.
  • Attribute directives modify existing elements or components.
  • Structural directives control how templates are instantiated.
  • Modern Angular uses @if, @for, and @switch for normal template control flow.
  • NgClass manages dynamic CSS classes.
  • NgStyle manages dynamic inline styles.
  • Simple class and style bindings are generally preferred over NgClass and NgStyle.
  • Custom directives package application-specific behavior into reusable units.
  • Directive inputs make reusable behaviors configurable.
  • Modern Angular supports signal-based inputs through input().
  • The element carrying a directive is its host element.
  • Host bindings connect directive state to host properties, attributes, classes, and styles.
  • Host listeners respond to host events.
  • Existing projects may use @HostBinding and @HostListener, but modern Angular recommends the host metadata property for new code.
  • The Directive Composition API allows multiple reusable directive behaviors to be attached to components or other directives.
  • hostDirectives can explicitly expose or alias selected directive inputs and outputs.
  • Good Angular directive design favors small, focused, reusable behaviors instead of large directives that try to manage unrelated responsibilities.

Directives become most useful when they remove meaningful duplication and make application behavior easier to understand, reuse, test, and maintain.

Question Hint