Structural directives change the rendered template tree by creating/destroying embedded views (real DOM + component instances). Attribute directives do not change the tree; they modify an existing element’s properties/classes/styles/behavior. Key interview nuance: the * syntax is just sugar for <ng-template> + directive inputs. Structural directives change the DOM tree, so test lifecycle, performance, and accessibility side effects.
Use this Angular interview question to rehearse a quick answer, common mistake, follow-up, and production pitfall.
Structural vs Attribute Directives in Angular: what changes the DOM tree (and what does * actually mean)?Frontend interview answer
This Angular interview question tests whether you can explain Structural vs attribute directives: what is the difference, connect it to production trade-offs, and handle common follow-up questions.
- Structural vs attribute directives: what is the difference explanation without falling back to memorized docs wording
- Directives and Structural reasoning, edge cases, and production failure modes
- How you would answer the most likely Angular interview follow-up
Core idea
Structural directives decide whether a block of template exists at all (create/destroy an embedded view). Attribute directives keep the element in place and only change the host element’s behavior/appearance (classes/styles/attrs/listeners).
Aspect | Structural directives | Attribute directives |
|---|---|---|
What they change | The template structure (create/destroy/move embedded views). | The host element (styles/classes/attributes/behavior). |
DOM impact | ✅ Real DOM nodes and component/directive instances are created/destroyed. | ❌ No tree changes; the same element stays in the DOM. |
Typical syntax |
|
|
How it works internally | Uses | Uses |
Lifecycle/state implication | Destroying the view runs | State is preserved because the instance isn’t destroyed. |
The * is syntax sugar
Angular rewrites * into an <ng-template> and attaches the directive there. That’s why structural directives operate on templates (not “just visibility”).
<!-- Shorthand -->
<div *ngIf="isAdmin">Admin</div>
<!-- Rough desugaring -->
<ng-template [ngIf]="isAdmin">
<div>Admin</div>
</ng-template>
<!-- Combining multiple conditions: use ng-container -->
<ng-container *ngIf="isAdmin">
<div *ngFor="let item of items">{{ item }}</div>
</ng-container>
Attribute directive example (preferred pattern)
Attribute directives typically bind to the host element instead of imperatively touching the DOM.
import { Directive, HostBinding, HostListener, Input } from '@angular/core';
@Directive({
selector: '[appHighlight]',
standalone: true
})
export class HighlightDirective {
@Input() appHighlight = 'gold';
@HostBinding('style.backgroundColor') private bg = '';
@HostListener('mouseenter') onEnter() { this.bg = this.appHighlight; }
@HostListener('mouseleave') onLeave() { this.bg = ''; }
}
Structural directive example (what seniors mention)
Structural directives render a template block by creating/destroying an embedded view via ViewContainerRef.
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;
}
}
}
// usage: <div *appIf="isVisible">...</div>
Gotcha | What happens | What to do instead |
|---|---|---|
“*ngIf just hides it” | False: it destroys/creates the view (state resets, subscriptions re-run). | Use |
Multiple | Only one structural directive can own the host rewrite to | Wrap with |
Direct DOM access in directives | Can break SSR/testing and bypass Angular abstractions. | Prefer |
<!-- Structural: the panel and its child state are destroyed -->
<app-filter-panel *ngIf="open"></app-filter-panel>
<!-- Attribute-style change: the same instance stays alive -->
<app-filter-panel
[class.is-hidden]="!open"
appHighlightPanel
></app-filter-panel>
<!-- Need ngIf and ngFor together? Move one * to ng-container -->
<ng-container *ngIf="open">
<li *ngFor="let item of items">{{ item.name }}</li>
</ng-container>
Debug rule of thumb
Choose a structural directive when the requirement is whether the subtree exists. Choose an attribute directive when the requirement is keep the same host element, but change styling, attributes, or listeners. That is why *ngIf can reset child state and rerun lifecycle hooks, while an attribute-style solution keeps the same instance alive. Only one structural shorthand can own a host element; reach for ng-container when you need multiple structural layers.
Structural directives change the rendered tree by creating/destroying embedded views (real lifecycle + state effects). Attribute directives keep the tree and modify the host element’s behavior/appearance. The * is just sugar for <ng-template> + directive inputs—knowing that is the senior signal.
Use this as one explanation rep, then continue with the Angular interview questions cluster or a guided prep path.