Skip to content

formatter: Angular templates not detected under Angular v20+ file naming (*.html, no .component suffix) #25869

Description

@sujeetjaiswara

Summary

oxfmt selects the Angular formatter for HTML purely by filename suffix — *.component.html, as documented in language support ("Angular → *.component.html").

Angular v20 dropped the .component suffix from CLI-generated files. A template generated by the modern CLI is attendance.html, not attendance.component.html. Those files miss the suffix check, silently fall through to the plain HTML formatter, and every Angular control-flow block (@if / @for / @switch / @defer / @let) is treated as a plain text node and mangled.

The effect is that no Angular v20+ project can use oxfmt on its templates at all, and there is no configuration escape hatch.

Reproduction

Byte-identical input, only the filename differs:

IN='<div>
  @switch (s()) {
    @case (1) {
      <b>one</b>
    }
    @default {
      <i>none</i>
    }
  }
  @defer (on viewport) {
    <app-chart />
  } @placeholder {
    <span>...</span>
  }
</div>'

echo "$IN" | oxfmt --stdin-filepath=foo.component.html
echo "$IN" | oxfmt --stdin-filepath=foo.html

foo.component.html — correct (Angular formatter)

<div>
  @switch (s()) {
    @case (1) {
      <b>one</b>
    }
    @default {
      <i>none</i>
    }
  }
  @defer (on viewport) {
    <app-chart />
  } @placeholder {
    <span>...</span>
  }
</div>

foo.html — mangled (HTML formatter)

<div>
  @switch (s()) { @case (1) {
  <b>one</b>
  } @default {
  <i>none</i>
  } } @defer (on viewport) {
  <app-chart />
  } @placeholder {
  <span>...</span>
  }
</div>

@case is hoisted onto the @switch line, closing braces merge into } }, and all block bodies lose their indentation level.

Detection probe

Same input, varying only --stdin-filepath:

--stdin-filepath Formatter used
foo.component.html Angular ✅
foo.html HTML ❌
attendance.html (Angular v20+ CLI default) HTML ❌
src/app/features/attendance/attendance.html HTML ❌
foo.ng.html HTML ❌
foo.template.html HTML ❌
component.html HTML ❌
foo-component.html HTML ❌
foo.COMPONENT.html HTML ❌

Further mangling on *.html

All of these are idempotent — re-running oxfmt reproduces the same bad output, so this is a formatting-model mismatch rather than an instability bug.

1. @if / @else bodies de-indented

<!-- in -->
<div class="wrap">
  @if (user(); as u) {
    <p>{{ u.name }}</p>
  } @else {
    <p>none</p>
  }
</div>

<!-- out -->
<div class="wrap">
  @if (user(); as u) {
  <p>{{ u.name }}</p>
  } @else {
  <p>none</p>
  }
</div>

2. Nested blocks flatten completely

<!-- in -->
<div>
  @if (a()) {
    @for (x of xs(); track x) {
      <span>{{ x }}</span>
    }
  }
</div>

<!-- out -->
<div>
  @if (a()) { @for (x of xs(); track x) {
  <span>{{ x }}</span>
  } }
</div>

3. Block headers and @let prose-wrapped mid-expression

Because blocks are text nodes, the fill algorithm wraps inside @if (...) conditions and joins statements:

  @let records = filteredRecords() | search: searchText : ["remark", "employeeName", "status"]; @if (records.length > 0
  && !isLoading() && hasPermission("Attendance") && selectedDate() !== null) {

At printWidth: 80 it also breaks inside interpolations:

    {{ employee.designationName }} &middot; {{
    employee.departmentName }}

Why there is no workaround

  • .oxfmtrc.json has no parser option — confirmed against the shipped node_modules/oxfmt/configuration_schema.json, whose top-level keys include svelte and vueIndentScriptAndStyle but nothing for Angular. Prettier users solve exactly this with overrides: [{ files: "*.html", options: { parser: "angular" } }]; oxfmt has no equivalent.
  • Renaming every template back to *.component.html contradicts the Angular v20+ style guide and the CLI defaults.
  • overrides in .oxfmtrc.json do apply to these files — the options (printWidth, singleAttributePerLine, bracketSameLine) take effect. It is only the parser/formatter selection that cannot be influenced.

Suggested fix

Widen the Angular detection so it does not depend on a naming convention Angular has abandoned. Options, roughly in order of preference:

  1. Treat *.html inside an Angular workspace as an Angular template (e.g. presence of angular.json, or @angular/core in package.json).
  2. Add an angular config key alongside the existing svelte key so a project can opt in explicitly.
  3. At minimum, extend the suffix list beyond *.component.html.

Note that the Angular formatter is a strict superset here — it round-trips plain HTML unchanged — so widening detection is low-risk for non-Angular .html in an Angular repo.

Related

Environment

oxfmt 0.63.0 (current npm latest)
Angular 22.1.2
Package manager [email protected]
OS Windows 11

.oxfmtrc.json (relevant part):

{
  "printWidth": 100,
  "singleQuote": true,
  "htmlWhitespaceSensitivity": "ignore",
  "overrides": [
    {
      "files": ["*.html"],
      "options": {
        "printWidth": 120,
        "singleAttributePerLine": true,
        "bracketSameLine": true,
        "htmlWhitespaceSensitivity": "ignore"
      }
    }
  ]
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Fields

    Priority

    None yet

    Effort

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions