Skip to content

feat(language-service): add Document Symbols support for Angular templates#66690

Merged
mattrbeck merged 2 commits intoangular:mainfrom
kbrilla:feat/document-symbols-templates
Mar 18, 2026
Merged

feat(language-service): add Document Symbols support for Angular templates#66690
mattrbeck merged 2 commits intoangular:mainfrom
kbrilla:feat/document-symbols-templates

Conversation

@kbrilla
Copy link
Copy Markdown
Contributor

@kbrilla kbrilla commented Jan 21, 2026

PR: feat(language-service): add Document Symbols support for Angular templates

Description

Adds comprehensive Document Symbols support for Angular templates, enabling the Outline panel, breadcrumbs navigation, and "Go to Symbol" (Cmd+Shift+O / Ctrl+Shift+O) features to work with Angular template syntax.

Features

Block Syntax Support

  • @if, @else, @else if with expression and alias display
  • @for with track expression and context variables
  • @switch, @case, @default blocks
  • @defer, @placeholder, @loading, @error blocks with triggers
  • @let declarations

Structural Directive Support

  • *ngIf, *ngFor, *ngSwitch, *ngSwitchCase, *ngSwitchDefault
  • *ngTemplateOutlet, *ngComponentOutlet, *ngPlural, *ngPluralCase
  • Custom structural directives (shown with Class icon)

Template File Support

  • TypeScript files: Template symbols nested under (template) node in component class
  • External HTML templates: Template symbols at root level (works with templateUrl)

Multi-Component Support

  • Multiple components per file: Correctly merges template symbols into each component class
  • Non-standard naming: Works with components without "Component" suffix

Variables and References

  • Loop item variables (let item)
  • Template reference variables (#ref)
  • Context variable aliases (let i = $index)
  • Expression aliases (as alias)

SymbolKind Mappings

Template Element SymbolKind Icon
@if, @else, @switch, @case Struct 🔶
@for, @empty Array 📦
@defer, @placeholder, @loading, @error Event
HTML elements Object 🟡
Variables Variable
Structural directives Class

Configuration

angular.documentSymbols.enabled (default: true)

Enables Angular-specific document symbols.

angular.documentSymbols.showImplicitForVariables (default: false)

Shows all implicit @for loop variables ($index, $count, $first, $last, $even, $odd).

Example

Template:

@for (item of items; track item.id; let i = $index) {
  @if (item.visible; as isVisible) {
    <div #container (click)="onClick()">
      {{ item.name }}
    </div>
  }
}

Outline:

@for (item of items)  📦
├── let item
├── let i
├── @if (item.visible; as isVisible)  🔶
│   ├── as isVisible
│   └── <div>  🟡
│       └── #container

Breaking Changes

None

Related Issues

Closes #66691


AI Disclosure

This PR was developed using Claude Opus 4.6 and GPT 5.3 Max AI assistants under human orchestration and review by @kbrilla.

@pullapprove pullapprove Bot requested a review from devversion January 21, 2026 19:57
@angular-robot angular-robot Bot added detected: feature PR contains a feature commit area: language-service Issues related to Angular's VS Code language service labels Jan 21, 2026
@ngbot ngbot Bot modified the milestone: Backlog Jan 21, 2026
@JeanMeche JeanMeche requested a review from atscott January 21, 2026 20:40
Comment on lines +33 to +36
// Get the navigation tree from TypeScript's language service.
// This includes classes, functions, variables, and for Angular files,
// also includes template symbols via the Angular language service.
const navigationTree = languageService.getNavigationTree(scriptInfo.fileName);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this only happen for TS files? What do we do about symbols for external templates (html files)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've refactored the code to make the two paths clearer:

// For HTML template files, we only need Angular template symbols (no TS symbols)
if (isHtmlFile) {
  // Returns template symbols at root level
  return convertTemplateSymbols(templateSymbols, scriptInfo);
}

// For TypeScript files, get navigation tree + merge template symbols into classes
// ... TS symbols with (template) node nested under class

Test at ivy_spec.ts:411 verifies external HTML templates work.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see in the screenshots that the document symbols are separated by the provider. In that case, should we even include the TS document symbols in TS files?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, yes it is duplicating some positions, but so is html template one doing - should it also skip positions handled by native html? or make it configurable mayby?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For typescript, I think we should only include the parents of the inline template but should not provide other things from typescript (e.g. ngOnInit random various functions in the file, etc). For HTML, we can leave it as-is to keep it consistent for inline and external templates.

Comment thread packages/language-service/src/document_symbols.ts Outdated
Comment thread packages/language-service/src/document_symbols.ts Outdated
args.push('--suppressAngularDiagnosticCodes', suppressAngularDiagnosticCodes);
}

const documentSymbolsEnabled = config.get<boolean>('angular.documentSymbols.enabled', true);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use middleware above to prevent the angular language server from processing files in projects that don't use Angular. Since the extension activates on all html and ts files, this is relatively important.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thx for the hint! Added the provideDocumentSymbols middleware with isInAngularProject guard.

@kbrilla kbrilla force-pushed the feat/document-symbols-templates branch from 9b9c431 to 9e2edd7 Compare January 21, 2026 22:34
@kbrilla
Copy link
Copy Markdown
Contributor Author

kbrilla commented Jan 21, 2026

Screenshots demonstrating Document Symbols feature

TypeScript file with inline template (NxWelcome - no "Component" suffix)

Shows the component class with the (template) node containing Angular template symbols:

  • @if (title) control flow block with proper icon
  • Nested <span> and <div> elements
  • Class members (title property)
image

External HTML template (app.html)

Shows Angular symbols at root level for external templates:

  • @if (title) control flow block
  • <app-nx-welcome> component element
  • Both Angular Language Service and HTML Language Features sections working together
image

Key features visible in screenshots:

  1. ✅ Component without "Component" suffix works correctly
  2. ✅ Inline template symbols nested under class
  3. ✅ External template symbols at root level
  4. ✅ Control flow blocks (@if) with proper icons
  5. ✅ Breadcrumb navigation shows Angular hierarchy

@kbrilla kbrilla force-pushed the feat/document-symbols-templates branch 2 times, most recently from 8b102aa to a0de8d6 Compare January 21, 2026 23:17
@kbrilla
Copy link
Copy Markdown
Contributor Author

kbrilla commented Jan 21, 2026

Additional Features/Fixes Added During Testing

While testing and addressing review feedback, I found and fixed two additional edge cases:

1. Components Without "Component" Suffix

Issue: Classes like NxWelcome (without "Component" suffix) didn't get template symbols merged correctly.

Root cause: The original merge logic relied on class name ending with "Component" as a heuristic.

Fix: Added className property to TemplateDocumentSymbol API. Template symbols are now explicitly tagged with their owning class name, enabling reliable merging regardless of naming conventions.

2. Multiple Components Per File

Issue: Files with multiple components (common in tests, storybooks, demo files) didn't work - all template symbols went into the first class.

Fix: Server now groups template symbols by className and merges each group into the correct component class.

Tests added:

  • 'provides document symbols for component without "Component" suffix'
  • 'provides document symbols for multiple components in one file'
image

Both scenarios are now documented in the PR description under "Multi-Component Support".

kbrilla added a commit to kbrilla/angular that referenced this pull request Jan 24, 2026
This commit adds infrastructure for features to request configuration from
the VS Code client using the LSP workspace/configuration protocol. This is
the preferred approach over CLI arguments because:

1. Configuration changes take effect immediately without restarting
2. Supports per-workspace and per-folder configuration
3. VS Code automatically merges settings from different scopes:
   - Default settings
   - User settings (global)
   - Workspace settings
   - Workspace folder settings
   - Language-specific settings

The new utilities include:
- getWorkspaceConfiguration: Request multiple config sections at once
- getConfigurationSection: Convenience wrapper for single sections
- flattenConfiguration: Flatten nested config to dot-notation keys

This infrastructure will be used by:
- Inlay hints feature (PR angular#66731)
- Document symbols feature (PR angular#66690)
@kbrilla kbrilla force-pushed the feat/document-symbols-templates branch from a0de8d6 to 4b96714 Compare January 24, 2026 09:08
@kbrilla
Copy link
Copy Markdown
Contributor Author

kbrilla commented Jan 24, 2026

🔗 Dependency Update

This PR now depends on #66734 (feat(language-server): add shared workspace/configuration utilities).

Changes in this update:

  • Removed CLI arguments: --disableDocumentSymbols and --showImplicitForVariables CLI args have been removed
  • Now uses workspace/configuration: Settings are fetched dynamically via LSP workspace/configuration request using the new shared utilities from feat(language-server): add shared workspace/configuration utilities #66734
  • Benefits: Users can now change document symbols settings without restarting the language server

Merge Order:

  1. First merge feat(language-server): add shared workspace/configuration utilities #66734 (workspace/configuration infrastructure)
  2. Then this PR can be merged

The PR has been rebased onto the feat/workspace-configuration branch.

@kbrilla kbrilla force-pushed the feat/document-symbols-templates branch from 4b96714 to 6fabcea Compare January 25, 2026 22:59
@kbrilla
Copy link
Copy Markdown
Contributor Author

kbrilla commented Jan 25, 2026

Update: Hybrid Approach Implemented

Following @atscott's feedback, I've implemented the hybrid approach (Option 1 + 2 from the analysis):

Changes:

  1. Default behavior (filtering): By default, only component classes with templates are shown in the outline - no TypeScript methods/properties
  2. New configuration: Added angular.documentSymbols.showTypescriptSymbols setting to opt-in to full TypeScript symbols

Default behavior (new):

MyComponent (class)
└── (template)
    ├── @if (condition)
    └── <button>

With showTypescriptSymbols: true:

MyComponent (class)
├── ngOnInit (method)
├── onClick (method)
├── someProperty (property)
└── (template)
    ├── @if (condition)
    └── <button>

Testing:

  • Updated existing test to verify default filtering behavior
  • Added test for showTypescriptSymbols: true configuration
  • Added test for multiple components in a single file
  • Added test for TypeScript files without Angular templates

All 53 specs pass.

Documentation:

  • Updated README with new setting and visual examples
  • Updated package.json with setting description

@atscott
Copy link
Copy Markdown
Contributor

atscott commented Jan 25, 2026

New configuration: Added angular.documentSymbols.showTypescriptSymbols setting to opt-in to full TypeScript symbols

sorry, that’s not really what I asked. Please remove all typescript symbols outside of the ancestors of the template property

* );
* ```
*/
export async function getConfigurationSection<T = unknown>(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see this used anywhere

@kbrilla kbrilla force-pushed the feat/document-symbols-templates branch from e57626d to c9b1442 Compare January 26, 2026 22:55
@kbrilla kbrilla force-pushed the feat/document-symbols-templates branch from c9b1442 to 5ca4d1d Compare February 23, 2026 22:35
@angular-robot angular-robot Bot added the area: vscode-extension Issues related to the Angular Language Service VsCode extension label Feb 23, 2026
@kbrilla kbrilla force-pushed the feat/document-symbols-templates branch from 5ca4d1d to ac699af Compare February 24, 2026 12:56
@kbrilla
Copy link
Copy Markdown
Contributor Author

kbrilla commented Feb 24, 2026

I'm leaving out inlay styles support: styles: [`test{}`] as it requires more changes unrelated to document symbols, so I will post a separate PR with it not to bloat this PR #67266

@kbrilla kbrilla force-pushed the feat/document-symbols-templates branch from 975ac56 to 8ac4109 Compare February 24, 2026 21:20
/**
* Gets the expression text for a template attribute by name.
*/
private getTemplateAttrExpression(template: TmplAstTemplate, attrName: string): string {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like this isn't used anywhere


const keyedBound = boundAttrs.find(
(attr) =>
attr.name.toLowerCase().startsWith(directiveNameLower) &&
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems suspicous. it might be safer to strictly verify the binding property matches the expected microsyntax key format.


let name: string;
let kind: ts.ScriptElementKind;
let lspKind: AngularSymbolKind | undefined;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks like it's always undefined

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the intent is for components to have the Class icon to differentiate them from HTML elements, it's better to make this explicit. Consider adding Class = 5 to the AngularSymbolKind enum and explicitly setting lspKind: AngularSymbolKind.Class.

Alternatively, if components should be grouped visually with standard HTML elements in the Outline view, you should explicitly set lspKind: AngularSymbolKind.Object for them as well.

@atscott
Copy link
Copy Markdown
Contributor

atscott commented Mar 4, 2026

Overall, this is looking good. Can you also add a note in the commit message of the document symbol one for "resolves #65488"

@kbrilla kbrilla force-pushed the feat/document-symbols-templates branch from 871e886 to c84dc34 Compare March 5, 2026 10:17
Copy link
Copy Markdown
Contributor

@atscott atscott left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AGENT: I have completed a thorough review of this PR. The implementation of document symbols using the new AngularSymbolKind is correct and provides excellent structural representation for Angular templates. The approach of nesting template symbols within the component class is the best path forward given VS Code's Outline behavior with multiple providers. The logic for identifying structural directives and variables is solid, and the integration tests provide great coverage. Great work!

@atscott
Copy link
Copy Markdown
Contributor

atscott commented Mar 13, 2026

Please rebase the PR but this otherwise LGTM

@kbrilla kbrilla force-pushed the feat/document-symbols-templates branch from c84dc34 to b4a3f73 Compare March 16, 2026 18:52
@kbrilla
Copy link
Copy Markdown
Contributor Author

kbrilla commented Mar 16, 2026

Please rebase the PR but this otherwise LGTM

@atscott sorry for the delay. Thank You for dedicating Your time!

@kbrilla kbrilla force-pushed the feat/document-symbols-templates branch from b4a3f73 to 4fa07c1 Compare March 16, 2026 19:04
@kbrilla
Copy link
Copy Markdown
Contributor Author

kbrilla commented Mar 16, 2026

last push cleaned up how changes were divided between commits

@kbrilla kbrilla force-pushed the feat/document-symbols-templates branch from 4fa07c1 to 941b308 Compare March 17, 2026 17:00
@kbrilla
Copy link
Copy Markdown
Contributor Author

kbrilla commented Mar 17, 2026

lint fixed

Copy link
Copy Markdown
Contributor

@atscott atscott left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work adding Document Symbol support for Angular templates! The implementation nicely addresses the previous feedback.

I reviewed the changes against the existing comments:

  1. External HTML template files are now properly handled.
  2. The special handling for router-outlet has been removed.
  3. For TypeScript files, the filter logic correctly hides generic TypeScript properties and functions from the Angular LS output, leaving only the component structure and its nested template symbols (avoiding clutter with duplicate TS symbols).
  4. Extracting microsyntax let/as keys is now robust and backed by solid test cases.

One minor nit in packages/language-service/src/document_symbols.ts:

  • TmplAstBoundEvent and ParseSourceSpan are imported at the top of the file but are unused.
  • Around line 144, the DocumentSymbolsOptions gets imported and re-exported redundantly, which could be collapsed:
import {AngularSymbolKind, DocumentSymbolsOptions, TemplateDocumentSymbol} from '../api';
export {AngularSymbolKind, DocumentSymbolsOptions, TemplateDocumentSymbol};

Aside from those small details, this looks fantastic and is ready to go!

Edit: You can ignore this. I was doing a final agent review pass.

@atscott atscott closed this Mar 17, 2026
@atscott atscott reopened this Mar 17, 2026
@kbrilla kbrilla force-pushed the feat/document-symbols-templates branch from 941b308 to 523a732 Compare March 17, 2026 17:31
@kbrilla
Copy link
Copy Markdown
Contributor Author

kbrilla commented Mar 17, 2026

@atscott

Edit: You can ignore this. I was doing a final agent review pass.

Fixed before realized not needed to do it :) Thank You again for your work put into this PR as well! Looking forward to merge next one :)

@kbrilla kbrilla force-pushed the feat/document-symbols-templates branch from 523a732 to 9c1e935 Compare March 17, 2026 17:42
@kbrilla
Copy link
Copy Markdown
Contributor Author

kbrilla commented Mar 17, 2026

commit messages now adhere to a minimum length

Comment thread packages/language-service/src/document_symbols.ts Outdated
kbrilla added 2 commits March 17, 2026 23:29
…lates

Add DocumentSymbol provider for Angular templates, surfacing structural
elements like components, directives, control flow blocks, and template variables
in the VS Code Outline and breadcrumbs.

resolves angular#65488
Add integration tests for microsyntax let/as variable extraction
in @for, @if, and structural directive template bindings.
@kbrilla kbrilla force-pushed the feat/document-symbols-templates branch from 9c1e935 to ba920ff Compare March 17, 2026 22:30
@atscott atscott added action: merge The PR is ready for merge by the caretaker target: minor This PR is targeted for the next minor release labels Mar 17, 2026
@mattrbeck mattrbeck merged commit 0b697f1 into angular:main Mar 18, 2026
22 of 23 checks passed
@mattrbeck
Copy link
Copy Markdown
Member

This PR was merged into the repository. The changes were merged into the following branches:

@angular-automatic-lock-bot
Copy link
Copy Markdown

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot Bot locked and limited conversation to collaborators Apr 18, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

action: merge The PR is ready for merge by the caretaker area: language-service Issues related to Angular's VS Code language service area: vscode-extension Issues related to the Angular Language Service VsCode extension detected: feature PR contains a feature commit target: minor This PR is targeted for the next minor release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(language-service): add Document Symbols support for Angular templates

3 participants