Angular directives: structural vs attribute vs components — and what the * syntax really does

LowIntermediateAngular
Preparing for interviews?

Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.

Quick Answer

A directive is a class that Angular attaches to an element (or an <ng-template>) to add behavior, change styling, or control whether a chunk of template exists at all. Interview focus: the 3 directive categories, how structural directives desugar from * syntax into <ng-template>, and how to build safe custom directives (HostBinding/HostListener, Renderer2, TemplateRef/ViewContainerRef). Framework focus: Angular templates + standalone components, @Input/@Output, and RxJS/async pipe patterns.

Answer

Core idea

A directive is a class that Angular instantiates when it matches a selector in a template. The directive can:
• add behavior (listen to events, update host properties)
• change styling (classes/styles/attributes)
control the DOM tree by creating/destroying embedded views (structural directives).

Category

What it does

How it shows up

Typical examples

Component

A directive with its own template.

Used as an element/tag.

<app-user-card>

Attribute directive

Changes host element behavior/appearance without changing DOM structure.

Used as an attribute selector.

[ngClass], [ngStyle], formControlName

Structural directive

Adds/removes a block of template by creating/destroying an embedded view.

Usually appears with * microsyntax.

*ngIf, *ngFor, *ngSwitchCase

The 3 directive types (components are also directives)

What the * really means (microsyntax)

* is just syntax sugar. Angular rewrites it into an <ng-template> and binds inputs to the directive on that template.

HTML
<!-- Shorthand -->
<div *ngIf="isAdmin">Admin</div>

<!-- Rough desugaring -->
<ng-template [ngIf]="isAdmin">
  <div>Admin</div>
</ng-template>

<!-- With else -->
<div *ngIf="isAdmin; else notAdmin">Admin</div>
<ng-template #notAdmin>Not admin</ng-template>
                  

Structural directives are special because…

Concrete consequence

They operate on TemplateRef + ViewContainerRef

They can create/destroy DOM, component instances, listeners, and run ngOnDestroy on teardown.

Only one * per host element

Because the host becomes an <ng-template>. Use <ng-container> to combine.

Interview nuance: * directives change the component tree, not just visibility

Custom attribute directive (typical interview example)

Use HostBinding/HostListener (or Renderer2) to affect the host. Avoid direct DOM where possible (SSR/testing safety).

TYPESCRIPT
import { Directive, HostBinding, HostListener, Input } from '@angular/core';

@Directive({
  selector: '[appHighlight]',
  standalone: true
})
export class HighlightDirective {
  @Input() appHighlight = 'yellow';

  @HostBinding('style.backgroundColor') private bg = '';

  @HostListener('mouseenter') onEnter() {
    this.bg = this.appHighlight;
  }

  @HostListener('mouseleave') onLeave() {
    this.bg = '';
  }
}
                  
HTML
<p [appHighlight]="'gold'">Hover me</p>
                  

Custom structural directive (the “real” senior signal)

Structural directives control a template block. They inject TemplateRef (what to render) and ViewContainerRef (where to render).

TYPESCRIPT
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';

@Directive({
  selector: '[appIf]',
  standalone: true
})
export class AppIfDirective {
  private hasView = false;

  constructor(
    private tpl: TemplateRef<unknown>,
    private vcr: ViewContainerRef
  ) {}

  @Input() set appIf(condition: boolean) {
    if (condition && !this.hasView) {
      this.vcr.createEmbeddedView(this.tpl);
      this.hasView = true;
      return;
    }

    if (!condition && this.hasView) {
      this.vcr.clear();
      this.hasView = false;
    }
  }
}
                  
HTML
<div *appIf="isVisible">Visible</div>

<!-- desugars to -->
<ng-template [appIf]="isVisible">
  <div>Visible</div>
</ng-template>
                  

Pitfall

What goes wrong

Better approach

Direct DOM mutation in directives

Breaks SSR/testing; can bypass Angular rendering assumptions

Prefer HostBinding/HostListener or Renderer2; keep DOM work minimal

Using structural directive when you need state preserved

It destroys/recreates component instances (state resets, subscriptions re-run)

Use [hidden]/CSS when you must keep the instance alive

Multiple * on one element

Template rewrite conflict

Wrap with <ng-container> or split elements

Heavy work in directives without OnPush-friendly patterns

Unnecessary change detection / jank in large views

Keep directives small; push heavy logic to services/facades; use async/signal patterns

Common directive mistakes interviewers see

Interview one-liners to say

“Components are directives with templates; attribute directives modify the host; structural directives create/destroy embedded views.”

“The * is syntax sugar for <ng-template> + directive inputs.”

“Structural directives use TemplateRef and ViewContainerRef to manage embedded views.”

“Prefer HostBinding/HostListener (or Renderer2) over direct DOM access.”

Summary

Directives are Angular’s way to attach behavior to templates. Attribute directives change an existing element; structural directives change the rendered tree by creating/destroying embedded views; components are directives with templates. The key senior detail: * desugars into <ng-template>, and structural directives are basically TemplateRef + ViewContainerRef orchestration.

Similar questions
Guides
11 / 37