Angular Formatter
Format and beautify Angular TypeScript components, templates, and HTML with configurable indentation, attribute sorting, and directive handling.
About
Angular codebases deteriorate fast. A single misindented decorator or an unbroken chain of template bindings ([input], (output), *ngIf) can obscure logic, introduce merge conflicts, and slow code reviews. Prettier covers general TypeScript but often mangles Angular-specific constructs: inline templates inside @Component metadata, control-flow blocks (@if, @for, @switch), and two-way binding syntax [(ngModel)]. This formatter parses decorator metadata, structural directives, interpolation expressions {{ }}, and pipe chains as first-class tokens. It applies deterministic indentation so the output is identical regardless of the inputβs original whitespace state. The algorithm operates entirely in your browser with zero server round-trips.
Limitations: this tool approximates formatting heuristics. It does not perform full AST parsing of TypeScript generics or complex type intersections. Deeply nested ternary expressions inside template interpolations may require manual adjustment. For monorepo-scale formatting, integrate a CI-level tool such as ng lint --fix.
Formulas
The formatter applies a deterministic indentation algorithm. For each line of input, the engine computes an indentation level L based on cumulative brace depth:
where Ξ΄open = count of unmatched opening tokens ({, (, [, <tag>) on the previous line, and Ξ΄close = count of leading closing tokens on the current line. The output indentation string is:
where char is either a space or tab character and size is the configured indent width (default 2). Tokens inside string literals (delimited by ', ", or `) and comments (//, /* */) are excluded from depth counting. The attribute-per-line threshold T triggers multi-line attribute formatting when an HTML element has more than T attributes (default T = 3).
Reference Data
| Angular Syntax | Category | Example | Formatter Handling |
|---|---|---|---|
| @Component | Class Decorator | @Component({ selector: "app-root" }) | Multi-line expansion of metadata keys |
| @Injectable | Class Decorator | @Injectable({ providedIn: "root" }) | Single-line if β€ threshold, else expanded |
| @Input() | Property Decorator | @Input() title: string | Preserved on same line as property |
| @Output() | Property Decorator | @Output() clicked = new EventEmitter() | Preserved on same line as property |
| *ngIf | Structural Directive | *ngIf="condition; else tmpl" | Kept as attribute, indented with element |
| *ngFor | Structural Directive | *ngFor="let item of items; trackBy: fn" | Kept as attribute, indented with element |
| @if | Control Flow (v17+) | @if (cond) { ... } | Block indentation like braces |
| @for | Control Flow (v17+) | @for (item of list; track item.id) { } | Block indentation like braces |
| @switch / @case | Control Flow (v17+) | @switch (val) { @case (1) { } } | Nested block indentation |
| [property] | Property Binding | [disabled]="isOff" | Attribute-per-line if > threshold |
| (event) | Event Binding | (click)="handler()" | Attribute-per-line if > threshold |
| [(ngModel)] | Two-way Binding | [(ngModel)]="value" | Treated as single attribute token |
| {{ expr }} | Interpolation | {{ user.name | uppercase }} | Spaces normalized inside braces |
| | pipe | Pipe Expression | {{ val | date:'short' }} | Spaces around pipe operator preserved |
| import | ES Module | import { X } from "@angular/core"; | Sorted specifiers, aligned braces |
| constructor() | Class Method | constructor(private svc: Service) | Parameter-per-line if > threshold |
| ngOnInit() | Lifecycle Hook | ngOnInit(): void { } | Standard method indentation |
| ngOnDestroy() | Lifecycle Hook | ngOnDestroy(): void { } | Standard method indentation |
| <ng-template> | Template Element | <ng-template #ref>...</ng-template> | Block-level indentation |
| <ng-container> | Template Element | <ng-container *ngIf="x"> | Block-level indentation |
| <ng-content> | Content Projection | <ng-content select=".header"> | Self-closing or block-level |
| template: | Inline Template | template: `<div>...</div>` | Extracted, formatted as HTML, re-embedded |
| styles: | Inline Styles | styles: [`:host { display: block; }`] | Extracted, formatted as CSS, re-embedded |
| standalone: true | Component Flag (v14+) | standalone: true | Kept in decorator metadata block |
| signals | Reactive Primitive (v16+) | count = signal(0) | Standard assignment formatting |
| computed() | Derived Signal | double = computed(() => this.count() * 2) | Arrow function formatting rules |
| effect() | Side-effect Signal | effect(() => console.log(this.count())) | Arrow function formatting rules |