<!-- 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.
<!-- 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.
<!-- 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.
<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 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.
@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.
@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.
@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.
@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: 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.
<!-- 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.
<!-- 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.
@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.
<!-- 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: 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.