Skip to content
| Marketplace
Sign in
Visual Studio Code>Formatters>Angular Team HTML FormatterNew to Visual Studio Code? Get it now.
Angular Team HTML Formatter

Angular Team HTML Formatter

Fenma

|
3 installs
| (1) | Free
Line-preserving HTML formatter for Angular templates with project-based tag rules.
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

Angular Team HTML Formatter

Line-preserving VS Code formatter for Angular HTML templates and standard HTML.

This extension is designed for teams that want predictable indentation everywhere, while only applying stronger formatting rules to tags that are explicitly configured in the repository. It is especially useful for Angular component templates with custom elements such as PrimeNG components like p-select, but it remains safe for ordinary HTML because unknown tags get indent-only behavior by default.

Architecture Summary

The formatter has two main steps:

  • known tags: apply configured tag-specific formatting rules
  • all lines: apply safe indentation without reflow

This keeps unknown tags indentation-only and makes configured component formatting predictable.

Features

  • VS Code formatter for HTML and Angular templates
  • project-based shared formatting config
  • indentation-only behavior for unknown tags
  • configurable tag-specific formatting for known tags
  • Angular-friendly attribute matching
  • no reflow or automatic line wrapping

Behavior Rules

Unknown tags

If a tag is not configured in the project config:

  • only indentation is adjusted
  • attribute text stays exactly as written
  • attribute order is untouched
  • closing style is untouched

Known tags

If a tag is configured:

  • attributes are matched and reordered deterministically
  • unknown attributes stay at the top or bottom depending on config
  • Angular binding syntax is preserved exactly
  • empty elements can be normalized to self-closing or explicit form

Config Reference

Create html-formatter.config.jsonc in your project root:

{
  "indent": {
    "size": 2,
    "useTabs": false
  },
  "defaultBehavior": {
    "unknownTags": "indent-only" // indent-only
  },
  "knownTagDefaults": {
    "attributeOrder": [],
    "attributeLayout": "preserve", // preserve | multiline
    "unknownAttributesPosition": "bottom", // top | bottom
    "sortUnknownAttributes": "preserve", // preserve | alphabetical
    "closingStyle": "explicit", // preserve | self-closing | explicit
    "closingBracketPosition": "new-line", // preserve | same-line | new-line
    "closingTagPosition": "same-line" // preserve | same-line | new-line
  },
  "tags": {
    "p-select": {
      "attributeOrder": [
        "inputId",
        "class",
        "options",
        "placeholder",
        "showClear",
        "optionLabel",
        "optionValue",
        "formControlName"
      ],
      "attributeLayout": "multiline",
      "unknownAttributesPosition": "bottom",
      "sortUnknownAttributes": "preserve",
      "closingStyle": "explicit",
      "closingBracketPosition": "new-line",
      "closingTagPosition": "same-line"
    }
  }
}

Top-level settings

indent

Controls indentation for the whole document, including normal HTML nesting and Angular control-flow blocks such as @if {} and @for {}.

{
  "indent": {
    "size": 4,
    "useTabs": false
  }
}

defaultBehavior

Defines the fallback behavior for tags that are not explicitly listed in tags.

{
  "defaultBehavior": {
    "unknownTags": "indent-only"
  }
}

knownTagDefaults

Provides default rule values for tags listed in tags.

If a tag defines its own rule, that tag-specific rule takes priority. If a tag does not define a rule, the value from knownTagDefaults is used.

Supported fields:

  • attributeOrder
  • attributeLayout
  • unknownAttributesPosition
  • sortUnknownAttributes
  • closingStyle
  • closingBracketPosition
  • closingTagPosition

Example:

{
  "knownTagDefaults": {
    "attributeLayout": "preserve",
    "unknownAttributesPosition": "bottom",
    "sortUnknownAttributes": "preserve",
    "closingStyle": "explicit",
    "closingBracketPosition": "new-line",
    "closingTagPosition": "same-line"
  }
}

tags

Contains per-tag formatting rules. The key is the tag name, for example p-select, p-inputText or my-component.

Only tags listed here get custom formatting behavior. All other tags remain indent-only.

Example:

{
  "tags": {
    "p-select": {
      "attributeOrder": ["inputId", "class", "options"],
      "closingStyle": "self-closing"
    }
  }
}

Tag rule settings

Each tag under tags can use the following options.

attributeOrder

Defines the preferred attribute order for a known tag.

Supported forms:

  • string entries, for example "optionLabel"
  • object entries, for example { "name": "ngModel", "kinds": ["two-way"] }

Behavior:

  • attributes listed here are moved into this exact order
  • attributes not listed here are treated as unknown attributes
  • unknown attributes are placed according to unknownAttributesPosition
  • the formatter preserves the original attribute text and value

Important Angular behavior:

  • "optionLabel" matches both optionLabel and [optionLabel]
  • "options" matches both options and [options]
  • "ngModel" can match [(ngModel)]

Simple example:

{
  "attributeOrder": [
    "inputId",
    "class",
    "options",
    "optionLabel"
  ]
}

Advanced example:

{
  "attributeOrder": [
    { "name": "ngModel", "kinds": ["two-way"] },
    { "name": "options", "kinds": ["property"] },
    { "name": "onChange", "kinds": ["event"] },
    { "name": "ngIf", "kinds": ["structural"] },
    { "name": "picker", "kinds": ["template-ref"] }
  ]
}

Supported kinds values:

  • plain
  • property
  • event
  • two-way
  • structural
  • template-ref

unknownAttributesPosition

Controls where attributes go that are not listed in attributeOrder.

Supported values:

  • "bottom": unknown attributes are placed after all configured attributes
  • "top": unknown attributes are placed before all configured attributes

Default:

  • "bottom"

Example:

{
  "unknownAttributesPosition": "bottom"
}

attributeLayout

Controls whether known-tag attributes stay in their current layout or are forced onto separate lines.

Supported values:

  • "preserve": keep the current single-line or multiline layout
  • "multiline": place each attribute on its own line under the tag name

Default:

  • "preserve"

Example:

{
  "attributeLayout": "multiline"
}

sortUnknownAttributes

Controls how unknown attributes are ordered relative to each other.

Supported values:

  • "preserve": keep their original order
  • "alphabetical": sort unknown attributes alphabetically by normalized attribute name

Default:

  • "preserve"

Example:

{
  "sortUnknownAttributes": "preserve"
}

closingStyle

Controls how the tag is closed.

Supported values:

  • "preserve": keep the current closing style when possible
  • "self-closing": format empty elements as <tag ... />
  • "explicit": format as <tag ...></tag>

Behavior notes:

  • self-closing only collapses a tag if the element is empty or contains whitespace only
  • if the tag contains content, the formatter stays safe and keeps explicit closing

Examples:

{
  "closingStyle": "self-closing"
}
{
  "closingStyle": "explicit"
}

closingBracketPosition

Controls where the closing > or /> is placed for a known tag.

Supported values:

  • "preserve": keep the existing style when possible, otherwise use the formatter's safe fallback
  • "same-line": put the closing > or /> on the same line as the final attribute
  • "new-line": put the closing > or /> on its own line

Default:

  • "preserve"

Example with "same-line":

<p-select
  inputId="accountName"
  class="w-full" />

Example with "new-line":

<p-select
  inputId="accountName"
  class="w-full"
/>

closingTagPosition

Controls where the explicit closing tag </tag> is placed when closingStyle is "explicit".

Supported values:

  • "preserve": keep the existing style when possible, otherwise use the formatter's safe fallback
  • "same-line": keep </tag> on the same line as the opening bracket line
  • "new-line": move </tag> to its own next line

Default:

  • "preserve"

Example with "same-line":

<p-select
  inputId="accountName"
  class="w-full"
></p-select>

Example with "new-line":

<p-select
  inputId="accountName"
  class="w-full" >
</p-select>

Legacy compatibility fields

The formatter also accepts these older shorthand fields at the top level:

  • indentSize
  • useTabs

These are normalized internally into:

{
  "indent": {
    "size": 2,
    "useTabs": false
  }
}

Invalid config behavior

If the config is missing or invalid:

  • the formatter does not crash
  • safe defaults are used
  • unknown tags remain indent-only
  • commands such as Validate HTML Formatter Config can help inspect the active config

Attribute Matching

Attribute matching uses the normalized attribute name.

  • inputId matches inputId
  • options matches options and [options]
  • placeholder matches placeholder and [placeholder]
  • ngModel can match [(ngModel)]

If you need more control, attributeOrder also supports objects:

{
  "name": "ngModel",
  "kinds": ["two-way"]
}

Supported kinds:

  • plain
  • property
  • event
  • two-way
  • structural
  • template-ref

PrimeNG p-select Example

With this rule:

{
  "attributeOrder": [
    "inputId",
    "class",
    "options",
    "placeholder",
    "showClear",
    "optionLabel",
    "optionValue",
    "formControlName"
  ]
}

this input:

<p-select [showClear]="true" optionValue="id" class="w-full" inputId="accountName" [options]="accountData" [placeholder]="dropdownPlaceholder" optionLabel="code" formControlName="accountName" />

becomes:

<p-select inputId="accountName" class="w-full" [options]="accountData" [placeholder]="dropdownPlaceholder" [showClear]="true" optionLabel="code" optionValue="id" formControlName="accountName" />

Commands

  • Format HTML with Team Formatter
  • Validate HTML Formatter Config
  • Show Active HTML Formatter Config

Make It The Default Formatter

Add this to your workspace settings:

{
  "[html]": {
    "editor.defaultFormatter": "Fenma.angular-team-html-formatter"
  },
  "[angular-html]": {
    "editor.defaultFormatter": "Fenma.angular-team-html-formatter"
  }
}

If your Angular templates are associated with html, this is enough. If your setup uses a separate language id, add the matching override as well.

Local Development

  1. Open this extension project in VS Code.
  2. Run npm install in the integrated terminal.
  3. Run npm test.
  4. Press F5 to start the Extension Development Host.
  5. Open an Angular project with html-formatter.config.jsonc.
  6. Run Format Document or Format Selection.

Build A VSIX

Build:

npm run package:vsix

This creates a .vsix in dist/, for example:

dist/angular-team-html-formatter-0.1.0.vsix

Install it in VS Code via:

  1. Extensions view
  2. ... menu
  3. Install from VSIX...
  4. Select the generated file from dist/

Debugging

  • Use the command Show Active HTML Formatter Config to inspect the resolved config.
  • Enable angularTeamHtmlFormatter.enableDebugLogs in VS Code settings to write debug output to the extension output channel.

Limitations

  • The formatter intentionally avoids full HTML pretty-printing and therefore does not try to normalize every edge case.
  • Range formatting works best when the selection contains complete tags.
  • Inline Angular templates are not implemented yet, but the formatter core is structured so that support can be added later.
  • For safety, self-closing conversion only collapses explicit elements when the content between start and end tags is empty or whitespace-only.

Future Work

  • Inline Angular template support in TypeScript files
  • More configurable multiline attribute layouts
  • Smarter range-format context handling
  • Extension host integration tests
  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
© 2026 Microsoft