Angular

Directives

15 Questions

Angular has three types of directives that let you manipulate the DOM in different ways. Component directives are components with templates and are the most common type. Structural directives change the DOM layout by adding or removing elements using the * prefix. Attribute directives change the appearance or behavior of an existing element.
<!-- Structural directive: adds/removes DOM elements -->
<div *ngIf="isVisible">Shown conditionally</div>

<!-- Attribute directive: modifies element behavior/appearance -->
<div [ngClass]="{'highlight': isActive}">Styled</div>

<!-- Component directive: component with a template -->
<app-header></app-header>
Structural directives are prefixed with an asterisk (*) which is syntactic sugar for ng-template. Attribute directives are applied like HTML attributes and can be freely combined on a single element.

Why it matters: Tests understanding of Angular's directive system and how each type manipulates the DOM. Shows you know when to use each directive type.

Real applications: Use *ngIf for conditional rendering, *ngFor for lists, [ngClass] for styling, [ngStyle] for dynamic styles, and custom directives for reusable UI behavior.

Common mistakes: Developers confuse structural and attribute directives. They don't realize structural directives remove elements while attribute directives only modify them. They try to combine structural directives improperly.

*ngIf is a structural directive that conditionally adds or removes an element from the DOM based on a truthy/falsy expression. When the condition is false, the element and all its children are completely removed from the DOM. You can use else and then blocks with ng-template for alternative content. This is different from hiding with CSS because the component is actually destroyed and recreated.
<!-- Basic usage -->
<div *ngIf="isLoggedIn">Welcome back!</div>

<!-- With else block -->
<div *ngIf="user; else noUser">
  Hello, {{ user.name }}
</div>
<ng-template #noUser><p>Please log in.</p></ng-template>

<!-- With then and else -->
<div *ngIf="loaded; then content; else loading"></div>
<ng-template #content>Data loaded</ng-template>
<ng-template #loading>Loading...</ng-template>
Unlike hiding with CSS, *ngIf completely removes the element and its subtree from the DOM when false. This saves resources for complex components and destroys any subscriptions or timers inside them.

Why it matters: Tests understanding of DOM manipulation and performance implications. Shows you know the difference between hiding and removing elements.

Real applications: Show/hide login buttons, display error messages conditionally, render loading spinners, show premium features only for logged-in users, display different UI based on user permissions.

Common mistakes: Developers use CSS display:none instead of *ngIf, not realizing the component is still running. They don't understand *ngIf destroys lifecycle hooks. They forget else blocks can reference templates.

*ngFor is a structural directive that repeats an element for each item in a collection like an array. It provides local variables like index, first, last, even, and odd for each iteration. The trackBy function improves performance by helping Angular identify which items changed instead of re-rendering the entire list. Without trackBy, Angular destroys and recreates all DOM elements on every change.
<!-- Basic iteration -->
<li *ngFor="let item of items; let i = index; let odd = odd">
  {{ i + 1 }}. {{ item.name }}
</li>

<!-- With trackBy for better performance -->
<li *ngFor="let user of users; trackBy: trackById">
  {{ user.name }}
</li>

// Component class
trackById(index: number, user: User): number {
  return user.id;
}
With trackBy, Angular only re-renders items whose tracked identity changed. This significantly improves list performance especially for large datasets fetched from APIs.

Why it matters: Tests knowledge of performance optimization and seeing array changes efficiently. Shows understanding of change detection for lists.

Real applications: Display product lists without re-rendering every item on filter, show user tables that stay responsive with thousands of rows, display paginated results smoothly without flickering.

Common mistakes: Developers forget trackBy entirely, causing performance degradation on large lists. They use array index as trackBy (wrong, should be unique identifier). They don't realize trackBy is crucial for performance when items are mutable.

*ngSwitch is a set of directives that switches between alternative views based on a matched value. It works like a JavaScript switch statement but in the template. [ngSwitch] binds the expression to evaluate, *ngSwitchCase matches specific values, and *ngSwitchDefault handles unmatched cases. Multiple cases can match the same element.
<div [ngSwitch]="userRole">
  <p *ngSwitchCase="'admin'">Admin Dashboard</p>
  <p *ngSwitchCase="'editor'">Editor Panel</p>
  <p *ngSwitchCase="'viewer'">View Only Mode</p>
  <p *ngSwitchDefault>Unknown Role</p>
</div>
[ngSwitch] is an attribute directive while *ngSwitchCase and *ngSwitchDefault are structural directives. Use ngSwitch when you have multiple conditions based on the same value instead of chaining *ngIf blocks.

Why it matters: Tests knowledge of conditional rendering patterns. Shows you choose switch over chained if-else when appropriate for cleaner code.

Real applications: Render different stepper components based on current step, display different content based on data status (loading/success/error), show role-based UI (admin/editor/viewer).

Common mistakes: Developers use chained *ngIf statements instead of ngSwitch for multiple conditions. They forget ngSwitch is more readable. They don't know ngSwitchCase uses triple equals (===) for comparison.

ngClass dynamically adds or removes CSS classes based on conditions. ngStyle dynamically sets inline styles on elements. Both directives accept objects, arrays, or expressions as input. They update automatically whenever the underlying data changes in the component.
<!-- ngClass with object syntax -->
<div [ngClass]="{ 'active': isActive, 'error': hasError, 'bold': isBold }">Text</div>

<!-- ngClass with array syntax -->
<div [ngClass]="['card', isSpecial ? 'featured' : 'normal']">Card</div>

<!-- ngStyle with object syntax -->
<div [ngStyle]="{
  'background-color': bgColor,
  'font-size.px': fontSize,
  'display': isVisible ? 'block' : 'none'
}">Styled</div>
Prefer single class/style bindings like [class.active]="isActive" for simple cases. Use ngClass/ngStyle when you need to manage multiple dynamic classes or styles at once.

Why it matters: Tests knowledge of dynamic styling patterns. Shows you can apply conditional CSS and styles efficiently.

Real applications: Toggle active tab classes, apply error styling on invalid forms, hide/show elements conditionally, set dynamic theme colors, highlight selected items in lists.

Common mistakes: Developers use ngClass for everything, even single toggles. They don't know [class.name] is simpler. They create complex, unreadable ngClass objects instead of using component helper methods.

A custom attribute directive modifies the behavior or appearance of a host element. You create it using the @Directive decorator with a selector in square brackets. Inject ElementRef to access the host element's DOM node directly. Use @HostListener to respond to events and @Input to accept configuration values.
@Directive({
  selector: '[appHighlight]'
})
export class HighlightDirective {
  @Input() appHighlight = 'yellow';

  constructor(private el: ElementRef) {}

  @HostListener('mouseenter') onMouseEnter() {
    this.setColor(this.appHighlight);
  }
  @HostListener('mouseleave') onMouseLeave() {
    this.setColor('');
  }
  private setColor(color: string) {
    this.el.nativeElement.style.backgroundColor = color;
  }
}

// Usage: <p appHighlight="cyan">Hover me</p>
Custom attribute directives are reusable across your application on any element. Declare them in a module's declarations array or mark them as standalone in Angular 14+.

Why it matters: Tests understanding of how to create reusable directive logic and manipulate the DOM. Shows you can extend Angular's built-in directives.

Real applications: Create highlight directive, format input directives, validation feedback directives, custom event handlers, dynamic styling logic reused across components.

Common mistakes: Developers access DOM directly instead of using proper component properties. They don't clean up event listeners properly. They forget to use @HostListener for automatic cleanup.

A custom structural directive manipulates the DOM by adding or removing elements based on a condition. It uses TemplateRef to access the template content and ViewContainerRef to control where it renders. The directive receives its condition through an @Input setter that shares the directive's selector name. This gives you full control over when and how the template is rendered.
@Directive({
  selector: '[appUnless]'
})
export class UnlessDirective {
  private hasView = false;

  constructor(
    private templateRef: TemplateRef<any>,
    private vcRef: ViewContainerRef
  ) {}

  @Input() set appUnless(condition: boolean) {
    if (!condition && !this.hasView) {
      this.vcRef.createEmbeddedView(this.templateRef);
      this.hasView = true;
    } else if (condition && this.hasView) {
      this.vcRef.clear();
      this.hasView = false;
    }
  }
}

// Usage: <p *appUnless="isHidden">Visible when not hidden</p>
Structural directives use the * prefix which is syntactic sugar for ng-template wrapping. Custom structural directives follow the same pattern as built-in ones like *ngIf and *ngFor.

Why it matters: Tests advanced understanding of DOM manipulation and template rendering control. Shows mastery of Angular's directive system.

Real applications: Create permission directives (show content only for certain roles), implement custom repeat logic, create conditional rendering based on app state, build reusable container patterns.

Common mistakes: Developers confuse TemplateRef and ViewContainerRef. They don't properly clean up embedded views. They forget to use the * syntax syntactic sugar correctly.

@HostListener is a decorator that subscribes to events on the host element of a directive or component. It replaces manual addEventListener calls and handles cleanup automatically when the directive is destroyed. You can listen to host element events, document events, or window events. Arguments from the event are passed using array notation in the second parameter.
@Directive({ selector: '[appClickTracker]' })
export class ClickTrackerDirective {

  @HostListener('click', ['$event'])
  onClick(event: MouseEvent) {
    console.log('Clicked at:', event.clientX, event.clientY);
  }

  @HostListener('document:keydown.escape')
  onEscape() {
    console.log('Escape pressed');
  }

  @HostListener('window:resize', ['$event'])
  onResize(event: Event) {
    console.log('Window resized');
  }
}
@HostListener is commonly used in directives to add interactive behavior to elements. It handles event subscription and cleanup automatically, preventing memory leaks.

Why it matters: Tests knowledge of event handling in directives. Shows you can respond to DOM events without manual event management.

Real applications: Close modals on escape key, track clicks for analytics, prevent default behavior on special elements, detect outside clicks, handle keyboard shortcuts globally.

Common mistakes: Developers manually manage addEventListener/removeEventListener instead of using @HostListener. They forget @HostListener cleans up automatically. They don't realize they can listen to global events like window resize.

@HostBinding binds a directive or component property to a property, attribute, or class of its host element. It lets you dynamically control the host element's appearance from inside the directive. You can bind to CSS classes, inline styles, HTML attributes, or DOM properties. When the bound property changes, the host element updates automatically.
@Directive({ selector: '[appCard]' })
export class CardDirective {
  @HostBinding('class.active') isActive = false;
  @HostBinding('style.border') border = '1px solid gray';
  @HostBinding('attr.role') role = 'article';

  @HostListener('click')
  toggle() {
    this.isActive = !this.isActive;
    this.border = this.isActive ? '2px solid blue' : '1px solid gray';
  }
}

// Usage: <div appCard>Click to toggle</div>
@HostBinding is commonly paired with @HostListener to create interactive directives. Together they let you respond to events and update the host element dynamically.

Why it matters: Tests understanding of how to modify host elements dynamically. Shows you can control appearance and properties from within a directive.

Real applications: Toggle active states visually, apply dynamic classes based on state, set accessibility attributes, control visibility, manage focus styling.

Common mistakes: Developers use ElementRef to set styles/classes instead of @HostBinding. They don't realize @HostBinding works two-ways (component property to host element). They forget @HostBinding is for properties not methods.

Structural directives change the DOM layout by adding, removing, or replacing elements from the page. Attribute directives change the appearance or behavior of an existing element without modifying the DOM structure. Structural directives use the * prefix which is syntactic sugar for ng-template wrapping. Attribute directives are applied like regular HTML attributes on elements.
<!-- Structural: changes DOM structure (prefixed with *) -->
<div *ngIf="show">Conditionally rendered</div>
<li *ngFor="let item of list">{{ item }}</li>

<!-- Attribute: changes element behavior/look -->
<div [ngClass]="{'active': isActive}">Styled div</div>
<input [ngModel]="value" />
<p appHighlight>Custom directive</p>
Key difference: You can only apply one structural directive per element, but you can combine multiple attribute directives freely. If you need multiple structural directives, wrap them in ng-container elements.

Why it matters: Tests understanding of fundamental directive differences. Shows you know when and how to use each type.

Real applications: Use *ngIf for conditional display, *ngFor for lists, [ngClass] for styling, custom attribute directives for formatting and validation, ng-container for grouping structural directives.

Common mistakes: Developers try to use two structural directives on one element. They don't understand the one per element rule. They use wrong directive types for their use case.

ng-template is an Angular element that defines a template but does not render it by default. It is used as a container for content that should only appear conditionally or be stamped out programmatically. Structural directives like *ngIf and *ngFor internally use ng-template behind the scenes. When you write *ngIf, Angular converts it to an ng-template with a [ngIf] attribute binding.
<!-- Used with *ngIf else -->
<div *ngIf="data; else loading">{{ data }}</div>
<ng-template #loading><p>Loading...</p></ng-template>

<!-- Manual rendering with ngTemplateOutlet -->
<ng-container *ngTemplateOutlet="myTemplate"></ng-container>
<ng-template #myTemplate><p>Reusable content</p></ng-template>
You can reference ng-template with a template variable (#ref) and render it using ngTemplateOutlet. This powerful pattern allows you to create reusable template blocks and configurable components.

Why it matters: Tests understanding of advanced template patterns. Shows you can build reusable and flexible components.

Real applications: Create reusable templates for lists, grids, modals, create configurable components where parents provide rendering logic, build template-based design systems.

Common mistakes: Developers don't realize ng-template isn't rendered by default. They forget ng-container is needed to use ngTemplateOutlet. They overcomplicate solutions without using template references.

ngTemplateOutlet is a directive that renders an ng-template at a specific location in the DOM. It allows you to reuse template blocks and pass context data to them dynamically. This is especially useful for creating configurable components where the parent provides custom templates. You can pass context using ngTemplateOutletContext, and the template receives data through let- variables.
<!-- Define reusable template -->
<ng-template #itemTemplate let-name="name" let-index="idx">
  <p>{{ index }}: {{ name }}</p>
</ng-template>

<!-- Render it with context -->
<ng-container *ngTemplateOutlet="itemTemplate; context: { name: 'Angular', idx: 1 }">
</ng-container>
ngTemplateOutlet is commonly used in design system components like tables, lists, and dropdowns. It lets consumers customize rendering while the component controls layout and logic.

Why it matters: Tests knowledge of template composition patterns. Shows you can build highly flexible and configurable components.

Real applications: Data tables with custom cell templates, dropdown menus with custom item rendering, modals with custom content, design systems allowing template customization.

Common mistakes: Developers don't pass context correctly to templates. They forget let-* variables for unpacking context. They don't realize ngTemplateOutlet is essential for component composition.

Angular 17 introduced built-in control flow syntax that replaces structural directives in templates. The new @if, @for, and @switch blocks are written directly in the template without importing CommonModule. They offer better readability, improved type narrowing, and better performance than equivalent structural directives. The @if block supports @else if and @else branches, while @for requires a track expression.
@if (user) {
  <p>Welcome, {{ user.name }}</p>
} @else if (loading) {
  <p>Loading...</p>
} @else {
  <p>Please log in</p>
}

@for (item of items; track item.id) {
  <li>{{ item.name }}</li>
} @empty {
  <li>No items found</li>
}
This new syntax is the recommended approach for Angular projects starting from version 17. It provides clearer template logic and allows Angular to optimize rendering more effectively.

Why it matters: Tests knowledge of Angular's modern control flow syntax. Shows you understand current best practices in Angular development.

Real applications: Conditional rendering in Angular 17+ apps, loops with conditional blocks, nested conditionals with clearer syntax, improved template readability in modern projects.

Common mistakes: Developers continue using old structural directives in Angular 17. They don't know @if, @for are now available. They forget track expressions are now required in @for.

No, Angular does not allow two structural directives on the same element. If you try to use *ngIf and *ngFor on the same element, Angular will throw an error. The solution is to wrap one of them in an ng-container element. Each structural directive needs its own template reference, and having two on the same element creates ambiguity.
<!-- This will throw an error -->
<!-- <div *ngIf="isVisible" *ngFor="let item of items">{{ item }}</div> -->

<!-- Correct: wrap in ng-container -->
<ng-container *ngIf="isVisible">
  <div *ngFor="let item of items">{{ item }}</div>
</ng-container>
Using ng-container adds no extra DOM element so it does not affect your layout. In Angular 17+, the new @if and @for control flow syntax handles nesting more naturally.

Why it matters: Tests understanding of structural directive constraints. Shows you know solutions for common template patterns.

Real applications: Show filtered lists conditionally, render paginated results with state, combine permission checking with loops, nest structural directives without extra DOM.

Common mistakes: Developers try to use two structural directives on same element anyway. They don't know ng-container solves this. They don't realize that causes cryptic Angular errors.

*ngIf completely adds or removes the element and its children from the DOM. When false, the element does not exist at all, so its component is destroyed and recreated each time. [hidden] only toggles the CSS display property — the element stays in the DOM but is visually hidden. Use *ngIf when the element is expensive to render, and [hidden] when you need the component to maintain its state.
<!-- *ngIf: element removed from DOM entirely -->
<div *ngIf="showPanel">This component is destroyed when hidden</div>

<!-- [hidden]: element stays in DOM, just hidden via CSS -->
<div [hidden]="!showPanel">This component keeps its state</div>
Use *ngIf for components with heavy initialization or subscriptions to save memory. Use [hidden] when toggling visibility frequently and you want to preserve the component's internal state.

Why it matters: Tests understanding of performance and memory implications. Shows you know when each approach is appropriate.

Real applications: Use *ngIf for complex components that initialize rarely (modals, sidebars), use [hidden] for frequently toggled UI (tooltips, dropdown menus), combine based on component complexity.

Common mistakes: Developers always use *ngIf without considering state preservation. They use [hidden] for everything without realizing memory cost. They don't understand component lifecycle destruction implications.