Angular Directives
Read one Angular interview question at a time, then flip for the complete explanation, example, and code.
Read one Angular interview question at a time, then flip for the complete explanation, example, and code.
Angular Interview Questions · Angular Directives Companion Article
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.
A directive is a TypeScript class that Angular associates with elements in a template.
Directives can be used to:
A simple custom directive is created with the @Directive() decorator.
import { Directive } from '@angular/core';
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
}
The selector:
[appHighlight]
means Angular activates the directive whenever it finds an element containing the appHighlight attribute.
<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.
Angular directives can be understood through three major categories:
| Directive Type | Main Purpose | Example |
|---|---|---|
| Component directive | Creates UI with its own template | @Component() |
| Attribute directive | Changes appearance or behavior | NgClass, custom highlight directive |
| Structural directive | Controls template rendering | Custom *appPermission directive |
A useful way to remember the difference is:
Component → creates a UI block
Attribute directive → changes an existing element
Structural directive → controls whether/how template content exists
An Angular component is a specialized kind of directive.
The main difference is that a component has its own template.
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:
<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:
<p appHighlight>Hello</p>
Here the <p> already exists. appHighlight only adds behavior to it.
| Feature | Component | Directive |
|---|---|---|
| Has template | Yes | Normally no |
| Creates UI | Yes | Usually modifies existing UI |
| Uses selector | Yes | Yes |
| Can accept inputs | Yes | Yes |
| Can respond to events | Yes | Yes |
| Can use dependency injection | Yes | Yes |
| Typical use | Complete UI feature | Reusable behavior |
Use a component when the feature:
Use a directive when you already have an element or component and only want to add behavior.
Good directive examples include:
If reusable behavior needs its own markup and UI structure, a component is normally the better abstraction.
An attribute directive changes the appearance or behavior of an existing element or component.
It does not normally create a separate template.
Examples include:
NgClass
NgStyle
custom highlight directive
custom autofocus directive
custom tooltip behavior
A basic attribute directive could look like this:
import { Directive } from '@angular/core';
@Directive({
selector: '[appImportant]'
})
export class ImportantDirective {
}
Usage:
<p appImportant>This information is important.</p>
The square brackets inside the selector:
selector: '[appImportant]'
represent an attribute selector.
You do not write those selector brackets when using the directive:
<p appImportant>...</p>
Consider an application where many elements should highlight when a user moves the mouse over them.
Without a directive, you might repeat:
<p
(mouseenter)="highlight()"
(mouseleave)="removeHighlight()">
Product
</p>
in many templates.
A reusable directive lets you centralize that behavior:
<p appHighlight>Product</p>
<div appHighlight>Order</div>
<span appHighlight>Customer</span>
The directive becomes a reusable UI behavior.
Not every style or event needs a custom directive.
For a single element, Angular template binding is usually simpler.
For example:
<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:
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 control the structure of rendered content.
Instead of merely changing an existing element's color or class, a structural directive can:
Historically, common Angular examples included:
*ngIf
*ngFor
*ngSwitchCase
In modern Angular, everyday conditions and loops should normally use the built-in control-flow syntax:
@if (loggedIn) {
<p>Welcome back.</p>
}
@for (product of products; track product.id) {
<p>{{ product.name }}</p>
}
@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.
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:
<button *appPermission="'ADMIN'">
Delete User
</button>
Another example might be:
<section *appFeatureEnabled="'new-dashboard'">
New Dashboard
</section>
These rules are different from ordinary @if conditions because they represent reusable application behavior.
Traditional custom structural directives commonly use the * shorthand.
For example:
<p *appPermission="'ADMIN'">
Administrator content
</p>
Conceptually, Angular transforms structural-directive shorthand into an <ng-template>.
The idea is similar to:
<ng-template appPermission [appPermission]="'ADMIN'">
<p>Administrator content</p>
</ng-template>
A structural directive commonly works with:
TemplateRef
ViewContainerRef
Provides access to the template controlled by the directive.
Represents a location where Angular can insert or remove rendered views.
A simplified example:
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.
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:
<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.
Angular provides built-in directives for several common behaviors.
Important examples include:
NgClassNgStyleNgOptimizedImageNgComponentOutletNgTemplateOutletNgNonBindableNgIf, NgFor, and NgSwitchSome built-in directives come from:
@angular/common
For example:
import { NgClass, NgStyle } from '@angular/common';
Modern standalone components can import only the directives they require.
@Component({
imports: [NgClass]
})
export class ExampleComponent {
}
This makes dependencies visible directly in the component.
NgClass dynamically adds or removes CSS classes from an element.
Suppose we have:
.success {
color: green;
}
.error {
color: red;
}
.highlight {
font-weight: bold;
}
A simple conditional class can be written as:
<p [ngClass]="{ 'success': isSuccessful }">
Operation status
</p>
If:
isSuccessful = true;
Angular adds:
success
to the element.
<div
[ngClass]="{
'success': status === 'success',
'error': status === 'error',
'highlight': important
}">
Order Status
</div>
Each class is evaluated separately.
currentClasses = 'card selected';
<div [ngClass]="currentClasses">
Product
</div>
Both classes are applied.
currentClasses = ['card', 'selected'];
<div [ngClass]="currentClasses">
Product
</div>
currentClasses = {
active: true,
disabled: false,
premium: true
};
<div [ngClass]="currentClasses">
Account
</div>
The resulting classes include:
active
premium
but not:
disabled
NgClass supports string, array, set, and object-based class definitions.
No.
Modern Angular recommends normal class binding for simpler cases.
Instead of:
<div [ngClass]="{ 'active': isActive }">
User
</div>
prefer:
<div [class.active]="isActive">
User
</div>
You can also bind several classes:
<div
[class]="{
active: isActive,
premium: isPremium
}">
User
</div>
Direct class bindings are often:
Angular's current style guide recommends class bindings over NgClass when practical.
NgStyle dynamically assigns inline CSS styles.
Example:
<p [ngStyle]="{ 'color': textColor }">
Dynamic text
</p>
Component:
textColor = 'blue';
Angular produces an inline color style based on the value.
<div
[ngStyle]="{
'color': textColor,
'background-color': backgroundColor,
'font-size.px': fontSize
}">
Styled content
</div>
Component:
textColor = 'white';
backgroundColor = 'navy';
fontSize = 18;
Notice:
font-size.px
The .px suffix tells Angular that the numeric value should use pixels.
Other units can also be expressed where appropriate.
You can keep the styles in the component.
boxStyles = {
'padding': '16px',
'border-radius': '8px',
'font-weight': '600'
};
Template:
<div [ngStyle]="boxStyles">
Product card
</div>
This can be useful when a group of styles comes from application state.
No.
For a single style, direct style binding is clearer:
<p [style.color]="textColor">
Hello
</p>
For numeric values:
<div [style.width.px]="boxWidth">
Box
</div>
You can also bind a style object:
<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.
Although both modify presentation, they solve different problems.
NgClass | NgStyle |
|---|---|
| Adds/removes CSS classes | Adds/removes inline CSS styles |
| Better when styles already exist in CSS | Useful for truly dynamic style values |
Example: active class | Example: dynamic width |
| Encourages reusable CSS rules | Creates element-specific styles |
For maintainable applications, CSS classes are usually preferable when the visual state has a meaningful name.
For example:
<div [class.error]="hasError">
is usually more maintainable than repeatedly specifying:
<div [style.color]="hasError ? 'red' : 'black'">
when an application already has a reusable .error design.
Custom directives are useful when application-specific behavior should be reusable.
Consider a directive that highlights an element when the pointer enters it.
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:
<p appHighlight>
Move the pointer over this text.
</p>
The HTML element receiving the directive is known as its host element.
Here:
<p appHighlight>
the <p> is the host element.
Angular CLI can generate the basic directive files.
ng generate directive highlight
The shorter form is:
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.
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:
<p appHighlight>
we could allow:
<p [appHighlight]="'lightblue'">
Highlight me
</p>
Modern Angular provides the input() API for component and directive inputs.
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:
<p [appHighlight]="'lightgreen'">
Product available
</p>
Notice that:
appHighlight = input('');
has the same name as the directive selector.
Therefore:
[appHighlight]
both activates the directive and supplies its input value.
Angular's current directive documentation uses this pattern for configurable attribute directives.
A directive can expose more than one input.
Example:
defaultColor = input('yellow');
appHighlight = input('');
The template can provide both:
<p
[appHighlight]="selectedColor"
defaultColor="lightgray">
Highlighted content
</p>
The directive can then decide which value should be used.
const color =
this.appHighlight() ||
this.defaultColor();
Modern signal inputs can also be required.
role = input.required<string>();
Angular expects the consumer to provide the value.
For example:
<div appAccessControl role="ADMIN">
Admin section
</div>
Required inputs are useful when the directive cannot work correctly without configuration.
The internal TypeScript property name does not always need to match the public template name.
Example:
color = input('', {
alias: 'appHighlight'
});
Template:
<p [appHighlight]="'orange'">
Warning
</p>
Inside TypeScript you read:
this.color()
while consumers bind:
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.
Older and existing Angular applications frequently use:
@Input() color = '';
This API continues to exist.
Modern Angular also supports:
color = input('');
The signal-based input is read using:
this.color()
while a decorator-based input is normally read as:
this.color
Angular's signal input API is production-ready and is the modern input model, while @Input() remains supported for existing code.
The element on which a directive exists is called the host element.
Example:
<button appPermissionButton>
Delete
</button>
The host element is:
<button>
A directive may need to interact with properties, attributes, classes, styles, or events of that host.
Common host interactions include:
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
Angular can provide a reference to the host element using ElementRef.
import {
Directive,
ElementRef,
inject
} from '@angular/core';
@Directive({
selector: '[appFocus]'
})
export class FocusDirective {
private element = inject(ElementRef);
}
The underlying native element is available through:
this.element.nativeElement
For example:
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.
A host binding connects directive state to the host element.
Possible host bindings include:
Modern Angular allows these bindings through the host property of @Directive().
Example:
import { Directive, signal } from '@angular/core';
@Directive({
selector: '[appSelectable]',
host: {
'[class.selected]': 'selected()',
'[attr.aria-selected]': 'selected()'
}
})
export class SelectableDirective {
selected = signal(false);
}
Usage:
<div appSelectable>
Product
</div>
When:
selected()
returns true, Angular can apply:
selected
to the host element and update the ARIA attribute.
@Directive({
selector: '[appStatus]',
host: {
'[style.opacity]': 'disabled ? 0.5 : 1'
}
})
export class StatusDirective {
disabled = false;
}
The directive controls the host element's opacity.
Accessibility information can also be attached to the host.
@Directive({
selector: '[appExpandable]',
host: {
'role': 'button',
'[attr.aria-expanded]': 'expanded'
}
})
export class ExpandableDirective {
expanded = false;
}
Static host value:
'role': 'button'
Dynamic host value:
'[attr.aria-expanded]': 'expanded'
This is an important use case because reusable directives can package accessibility behavior together with interaction logic.
You may encounter this syntax in existing Angular applications:
import {
Directive,
HostBinding
} from '@angular/core';
@Directive({
selector: '[appActive]'
})
export class ActiveDirective {
@HostBinding('class.active')
active = true;
}
This binds:
active
to:
class.active
on the host element.
Another example:
@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:
@Directive({
selector: '[appActive]',
host: {
'[class.active]': 'active'
}
})
export class ActiveDirective {
active = true;
}
A directive often needs to react to events generated by its host element.
Examples:
click
mouseenter
mouseleave
focus
blur
keydown
keyup
input
Modern Angular host listeners can be defined through host.
@Directive({
selector: '[appClickable]',
host: {
'(click)': 'onClick()'
}
})
export class ClickableDirective {
onClick(): void {
console.log('Host clicked');
}
}
Usage:
<button appClickable>
Click
</button>
When the button emits a click event, Angular invokes:
onClick()
Use $event when the handler needs the actual browser event.
@Directive({
selector: '[appKeyLogger]',
host: {
'(keydown)': 'onKeyDown($event)'
}
})
export class KeyLoggerDirective {
onKeyDown(event: KeyboardEvent): void {
console.log(event.key);
}
}
Usage:
<input appKeyLogger>
Existing Angular applications may use:
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:
@HostListener('keydown', ['$event'])
onKeyDown(event: KeyboardEvent): void {
console.log(event.key);
}
This remains valid Angular syntax.
For new code, however, Angular currently recommends:
host: {
'(keydown)': 'onKeyDown($event)'
}
instead of the decorator-based form.
Host event bindings can also target certain global objects.
Examples include:
window:
document:
body:
For example:
host: {
'(document:keydown.escape)': 'close()'
}
This could be useful in reusable UI behavior such as:
Global event handlers should be used carefully because each directive instance can participate in the event.
The difference is straightforward.
Controls a value on the host.
Example:
'[class.active]': 'active'
Direction:
Directive state → Host element
Responds to an event from the host.
Example:
'(click)': 'toggle()'
Direction:
Host event → Directive method
A directive commonly uses both.
@Directive({
selector: '[appToggle]',
host: {
'[class.active]': 'active',
'(click)': 'toggle()'
}
})
export class ToggleDirective {
active = false;
toggle(): void {
this.active = !this.active;
}
}
Usage:
<button appToggle>
Toggle
</button>
This is a good example of a directive encapsulating both state-related presentation and reusable interaction behavior.
Large applications often develop several small reusable behaviors.
Imagine:
KeyboardBehavior
TooltipBehavior
MenuBehavior
FocusBehavior
AccessibilityBehavior
A component may need several of them.
Instead of requiring every consumer to manually apply every directive:
<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:
hostDirectives
Example:
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:
<app-admin-menu></app-admin-menu>
it also creates the configured host directive and applies its host behavior to the component's host element.
Without composition, components may duplicate behavior.
For example:
ButtonComponent
MenuComponent
DropdownComponent
ToolbarComponent
might all implement similar keyboard handling.
Instead, the application can create:
KeyboardNavigationDirective
and compose it into the components that need it.
Benefits include:
An important rule is that inputs and outputs from host directives are not automatically exposed through the component.
Suppose:
@Directive({
selector: '[appMenuBehavior]'
})
export class MenuBehaviorDirective {
menuId = input('');
}
If a component uses it:
@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:
@Component({
selector: 'app-menu',
hostDirectives: [
{
directive: MenuBehaviorDirective,
inputs: ['menuId']
}
],
template: `...`
})
export class MenuComponent {
}
Now a consumer can use:
<app-menu menuId="main-menu"></app-menu>
The public API can also use a different name.
hostDirectives: [
{
directive: MenuBehaviorDirective,
inputs: ['menuId: id']
}
]
Now consumers use:
<app-menu id="main-menu"></app-menu>
while the underlying directive still uses:
menuId
This allows a component to reuse behavior while presenting a cleaner public API.
Host directives have some important characteristics:
hostDirectivesAngular supports composing directives into both components and other directives, allowing reusable behavior to be built in layers.
Modern Angular applications commonly use standalone components and directives.
A custom directive can be imported directly into the component that requires it.
Example:
@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:
import {
NgClass,
NgStyle
} from '@angular/common';
you can similarly import only what the component needs.
Consider an application that displays many statuses:
ACTIVE
PENDING
ERROR
We can create reusable behavior rather than repeating class logic everywhere.
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:
<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:
orders
payments
users
shipments
notifications
Some applications need an element to receive focus automatically.
Instead of repeating focus logic across many components, it can be packaged in a directive.
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:
<input appAutofocus>
Possible real-world uses include:
Before automatically moving focus, however, consider accessibility and user expectations. Focus behavior should assist users rather than unexpectedly interrupt navigation.
A reusable interactive directive might combine:
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:
<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.
Custom directive selectors commonly use an application-specific prefix.
Good:
selector: '[appHighlight]'
selector: '[appPermission]'
selector: '[shopPrice]'
Generic selectors such as:
selector: '[highlight]'
can potentially clash with other libraries or future HTML-related names.
A project-specific prefix makes ownership clearer.
Directives are powerful, but unnecessary abstraction can make an Angular application harder to understand.
Do not create a directive simply to replace:
[class.active]="active"
with:
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:
DOM behavior + events
accessibility attributes + keyboard support
permission logic + rendering
validation state + presentation
tooltip logic + positioning behavior
reusable input formatting
drag/drop interaction
Unnecessary:
<div appRedText>
when this would be enough:
<div class="red-text">
Use directives for behavior, not simply to avoid writing CSS.
Instead of repeating:
[class.disabled]="..."
[attr.aria-disabled]="..."
(keydown)="..."
(click)="..."
across many elements, consider whether the behavior represents a reusable directive.
Attribute directive:
<p appHighlight>
changes the existing <p>.
Structural directive:
<p *appPermission="'ADMIN'">
can determine whether the template is instantiated.
For ordinary loops and conditions, avoid starting new code with:
*ngIf
*ngFor
[ngSwitch]
Prefer:
@if
@for
@switch
Custom structural directives remain appropriate for reusable rendering abstractions.
Instead of:
[ngClass]="{ active: active }"
prefer:
[class.active]="active"
when the requirement is simple.
Instead of:
[ngStyle]="{ color: color }"
prefer:
[style.color]="color"
for a straightforward style binding.
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.
Avoid one directive that handles:
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.
These Angular features solve different problems.
| Feature | Main Responsibility |
|---|---|
| Component | Own and render UI |
| Directive | Add reusable behavior |
| Pipe | Transform a value for display |
| Service | Share application logic or data |
Example requirements:
Use a:
Component
Use a:
Directive
Use a:
Pipe
Use a:
Service
Understanding these boundaries helps prevent Angular applications from becoming unnecessarily complex.
| Attribute Directive | Structural Directive |
|---|---|
| Changes an existing element | Controls template rendering |
| Normally keeps the element in the DOM | Can create/remove embedded views |
| Example: highlighting | Example: permission rendering |
| Commonly interacts with host | Commonly uses template/view APIs |
| Does not normally own a template | Operates on template content |
Think of it as:
Attribute directive:
"How should this element behave?"
Structural directive:
"Should/how should this template exist?"
For new Angular applications, a practical set of guidelines is:
@if, @for, and @switch for ordinary template control flow.input() for modern directive inputs.[class...] and [style...] bindings over NgClass and NgStyle for simple cases.host property for host properties and events in new code.@HostBinding and @HostListener because they remain common in existing Angular projects.Use this decision process when implementing a feature.
Use:
[class.active]="active"
Use:
[style.color]="color"
Create:
Attribute Directive
Use:
@if
Use:
@for
Consider:
Custom Structural Directive
Create:
Component
Consider:
Directive Composition API
Directives are frequently useful for application behaviors such as:
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.
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:
@if, @for, and @switch for normal template control flow.NgClass manages dynamic CSS classes.NgStyle manages dynamic inline styles.NgClass and NgStyle.input().@HostBinding and @HostListener, but modern Angular recommends the host metadata property for new code.hostDirectives can explicitly expose or alias selected directive inputs and outputs.Directives become most useful when they remove meaningful duplication and make application behavior easier to understand, reuse, test, and maintain.