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.
Structural vs Attribute Directives in Angular: what changes the DOM tree (and what does * actually mean)?
Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.
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 |
Practical scenario
Use *ngIf to conditionally render a panel and [ngClass] to style it.
Common pitfalls
- Assuming structural directives only hide elements (they remove them).
- Losing component state when DOM nodes are destroyed.
- Overusing
*ngIffor show/hide instead of[hidden].
Structural directives save work but reset state. Test with toggling and lifecycle hooks.
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.