Angular Template Formatter

Paste a .component.html template and get clean indentation for elements and control flow blocks. Brace and tag mistakes are reported with Angular-specific messages and a position.

Input

Settings

History

Load from URL

Built for Angular syntax, not plain HTML

An Angular template looks like HTML, but a plain HTML formatter trips over it. Property bindings such as [class.vip], event bindings like (click), two-way [(ngModel)], structural directives such as *ngFor, the built-in control flow introduced in Angular 17 (@if, @else, @for with @empty, @switch and @case, @defer with @placeholder) and the later @let declarations all need a parser that knows the template language. This page uses Prettier with its dedicated angular parser rather than the generic HTML one.

Blocks are laid out like code: the body of each @if or @for is indented, and } @else { and } @empty { are joined onto one line. Elements follow the usual Prettier HTML rules, wrapping attributes when a tag is longer than 80 columns.

There is one limit to know about. Expressions inside {{ }} interpolations and binding values are kept exactly as you wrote them, so {{total|currency:'EUR'}} keeps its spacing and @if (orders.length>0) keeps its condition as typed. Block parameters get only light cleanup, such as a space after the semicolon before track in an @for. The markup and block structure are what get rearranged.

Whitespace and attribute settings

  • Whitespace sensitivity decides whether the formatter may add or remove spaces between inline elements. Respect CSS display (the default) treats <span> and <b> as inline and avoids changing the gaps between them, which sometimes leads to odd-looking breaks inside a closing tag. It is also why an <input> followed directly by a <button> can come out as /><button: putting a line break between two inline elements would add a visible space. Strict (never change rendering) treats every element as inline, so text spacing is never altered. Ignore formats freely: the output is the tidiest, but a space such as the one in <span> SGD</span> can be removed, which changes what users see.
  • One attribute per line puts each attribute on its own line whenever a tag has to wrap. Reviews of a template with many bindings get easier because each binding change is its own diff line.
  • Keep > on the last attribute line places the closing bracket of a wrapped tag after the last attribute, rather than alone on the following line.

Indent in the toolbar controls the indentation width, or tabs.

Formatting a template

Paste the template or drop a .component.html file. Detection kicks in when it sees Angular-only syntax: *ngIf, *ngFor, [(ngModel)], (click)=, @if ( or @for (. A template without any of these is read as plain HTML, so choose Angular from the format menu if bindings are mangled. Press Ctrl/Cmd+Enter to reformat. The template never leaves your computer, which matters for internal admin screens whose markup reveals routes and API shapes.

Inline templates written in a template: string inside a component’s TypeScript file are a separate case: paste the TypeScript into the TypeScript formatter instead.

Examples

Orders table with @if and *ngFor

The @if and @else bodies are indented as blocks and the table row wraps its bindings onto separate lines.

Input
<div class="orders"><h2>{{title | uppercase}}</h2>@if(orders.length>0){<table><tr *ngFor="let o of orders;trackBy:trackById" [class.vip]="o.customer.vip" (click)="select(o)"><td>{{o.id}}</td><td>{{o.total | currency:'USD'}}</td></tr></table>}@else{<p>No orders yet.</p>}<button (click)="reload()" [disabled]="loading">Reload</button></div>
Output
<div class="orders">
  <h2>{{title | uppercase}}</h2>
  @if (orders.length>0) {
    <table>
      <tr
        *ngFor="let o of orders;trackBy:trackById"
        [class.vip]="o.customer.vip"
        (click)="select(o)"
      >
        <td>{{o.id}}</td>
        <td>{{o.total | currency:'USD'}}</td>
      </tr>
    </table>
  } @else {
    <p>No orders yet.</p>
  }
  <button (click)="reload()" [disabled]="loading">Reload</button>
</div>
Open this example in the tool

Status badge with @switch

Each @case body moves onto its own indented lines, like a switch statement in code.

Input
@switch(order.status){@case('paid'){<span class="badge ok">Paid</span>}@case('refunded'){<span class="badge">Refunded</span>}@default{<span class="badge muted">Open</span>}}
Output
@switch (order.status) {
  @case ('paid') {
    <span class="badge ok">Paid</span>
  }
  @case ('refunded') {
    <span class="badge">Refunded</span>
  }
  @default {
    <span class="badge muted">Open</span>
  }
}
Open this example in the tool

Reactive form with one attribute per line

Every binding gets its own line and each closing > stays on the last attribute line.

Input
<form [formGroup]="form" (ngSubmit)="save()"><input formControlName="email" type="email" [class.is-invalid]="form.controls.email.invalid && form.controls.email.touched" placeholder="Email"><button type="submit" [disabled]="form.invalid">Save</button></form>
Output
<form
  [formGroup]="form"
  (ngSubmit)="save()">
  <input
    formControlName="email"
    type="email"
    [class.is-invalid]="form.controls.email.invalid && form.controls.email.touched"
    placeholder="Email" /><button
    type="submit"
    [disabled]="form.invalid">
    Save
  </button>
</form>
Open this example in the tool

Deferred loading with @for and @empty

Nested blocks are indented level by level, with } @empty { and } @placeholder { joined onto single lines.

Input
@defer(on viewport){<ul>@for(review of reviews;track review.id){<li><app-review [review]="review" (flag)="report(review.id)"/></li>}@empty{<li>No reviews yet</li>}</ul>}@placeholder{<p>Loading reviews</p>}
Output
@defer (on viewport) {
  <ul>
    @for (review of reviews; track review.id) {
      <li><app-review [review]="review" (flag)="report(review.id)" /></li>
    } @empty {
      <li>No reviews yet</li>
    }
  </ul>
} @placeholder {
  <p>Loading reviews</p>
}
Open this example in the tool

Common errors and how to fix them

ErrorCauseFix
Unexpected closing tag "div". It may happen when the tag has already been closed by another tag.
Explained
A block or element was closed out of order. Inside an @if block, a </div> that belongs to an element outside the block triggers this.Close the block with } before closing the element that contains it, or move the opening tag inside the block.
Unclosed block "if"An @if, @for, @switch or @defer block is missing its closing brace.Add } after the last element of the block body.
Unexpected closing block. The block may have been closed earlier. If you meant to write the `}` character, you should use the "&#125;" HTML entity instead.A literal } appears in text content. Since Angular 17, braces in templates belong to control flow blocks.Write } for the character, or check whether an extra } closes a block twice.
Unexpected character "EOF" (Do you have an unescaped "{" in your template? Use "{{ '{' }}") to escape it.)A literal { appears in text, as in <p>Price: {</p>. Angular reads an unescaped brace as the start of an ICU expression or block that never ends.Write the brace as {{ ‘{’ }} as the message suggests, or as the { entity. A literal @ before a word likewise needs @.
Unexpected character "EOF"An attribute value was opened with a quote and never closed, as in [ngClass]="{active: isActive.Close the binding expression and its quote.

Frequently asked questions

Does it support the new @if and @for control flow?

Yes. @if, @else if, @else, @for with track and @empty, @switch, @case, @default, @defer with @placeholder and @loading, and @let are all parsed and indented.

Why are my interpolations not reformatted?

This formatter rearranges markup and blocks only. Expressions in {{ }} and in binding values are left exactly as written, so tidy their spacing by hand or in your editor.

Can I still use *ngIf and *ngFor?

Yes. Structural directives are ordinary attributes to the formatter, so templates that mix them with the new block syntax format fine.

Which whitespace setting should I use?

Keep Respect CSS display unless you have a reason not to. Ignore gives the neatest output but can remove spaces that are visible on the page.

Is the Angular CLI required?

No. The formatter runs in the browser with nothing to install, and it does not need the component class or the rest of the project.

Related tools