Skip to content

feat(angular): angular adapter and utils#368

Open
riccardoperra wants to merge 22 commits intoTanStack:mainfrom
riccardoperra:feat/angular-adapter
Open

feat(angular): angular adapter and utils#368
riccardoperra wants to merge 22 commits intoTanStack:mainfrom
riccardoperra:feat/angular-adapter

Conversation

@riccardoperra
Copy link

@riccardoperra riccardoperra commented Mar 4, 2026

The following PR continues the work of #364 to improve and fix angular integration:

  • devtool component can now render component, lazy components (by dynamic imports) or custom functions
  • devtools utils will now use a non-decoration approach to render components

Added some examples:

  • Basic: just a copy of "custom plugin" example from react
  • Panel: example using createAngularPlugin with a "vanilla" plugin example
  • with-devtools: example using sub package @tanstack/angular-devtools/provider for those who prefers injecting devtools with Dependency Injection (and automatically lazy-load devtool component)

Summary by CodeRabbit

  • New Features

    • Full Angular framework support and a new Angular DevTools package, including Angular-ready devtools panels and A11y integration.
  • Documentation

    • Comprehensive Angular docs: setup, adapter reference, custom-plugin guide, installation and quick-start updates.
  • Examples

    • Several complete Angular example projects (basic, panel, with-devtools, a11y) demonstrating integration and custom panels.
  • Chores

    • Packaging, workspace, and build config updates to publish and consume Angular artifacts.

@changeset-bot
Copy link

changeset-bot bot commented Mar 4, 2026

⚠️ No Changeset found

Latest commit: 4ebeb9e

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@riccardoperra riccardoperra force-pushed the feat/angular-adapter branch from a9d3f16 to 3349a19 Compare March 4, 2026 22:10
@nx-cloud
Copy link

nx-cloud bot commented Mar 5, 2026

🤖 Nx Cloud AI Fix Eligible

An automatically generated fix could have helped fix failing tasks for this run, but Self-healing CI is disabled for this workspace. Visit workspace settings to enable it and get automatic fixes in future runs.

To disable these notifications, a workspace admin can disable them in workspace settings.


View your CI Pipeline Execution ↗ for commit dc62a0d

Command Status Duration Result
nx affected --targets=test:eslint,test:sherif,t... ❌ Failed 2m 55s View ↗

☁️ Nx Cloud last updated this comment at 2026-03-11 12:22:30 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 5, 2026

More templates

@tanstack/angular-devtools

npm i https://pkg.pr.new/TanStack/devtools/@tanstack/angular-devtools@368

@tanstack/devtools

npm i https://pkg.pr.new/TanStack/devtools/@tanstack/devtools@368

@tanstack/devtools-client

npm i https://pkg.pr.new/TanStack/devtools/@tanstack/devtools-client@368

@tanstack/devtools-ui

npm i https://pkg.pr.new/TanStack/devtools/@tanstack/devtools-ui@368

@tanstack/devtools-utils

npm i https://pkg.pr.new/TanStack/devtools/@tanstack/devtools-utils@368

@tanstack/devtools-vite

npm i https://pkg.pr.new/TanStack/devtools/@tanstack/devtools-vite@368

@tanstack/devtools-event-bus

npm i https://pkg.pr.new/TanStack/devtools/@tanstack/devtools-event-bus@368

@tanstack/devtools-event-client

npm i https://pkg.pr.new/TanStack/devtools/@tanstack/devtools-event-client@368

@tanstack/preact-devtools

npm i https://pkg.pr.new/TanStack/devtools/@tanstack/preact-devtools@368

@tanstack/react-devtools

npm i https://pkg.pr.new/TanStack/devtools/@tanstack/react-devtools@368

@tanstack/solid-devtools

npm i https://pkg.pr.new/TanStack/devtools/@tanstack/solid-devtools@368

@tanstack/vue-devtools

npm i https://pkg.pr.new/TanStack/devtools/@tanstack/vue-devtools@368

commit: a1d4364

@riccardoperra riccardoperra force-pushed the feat/angular-adapter branch from d1cafc8 to 3f27799 Compare March 8, 2026 08:45
@riccardoperra riccardoperra changed the base branch from worktree-kind-orbiting-dolphin to main March 8, 2026 12:26
@riccardoperra riccardoperra force-pushed the feat/angular-adapter branch from d82d6a2 to efa3afd Compare March 8, 2026 12:31
@riccardoperra riccardoperra marked this pull request as ready for review March 8, 2026 13:06
@riccardoperra riccardoperra changed the title feat(angular): angular adapter, add utils and some examples, fix build feat(angular): angular adapter and utils Mar 8, 2026
@riccardoperra riccardoperra force-pushed the feat/angular-adapter branch from 5abbcba to e5e5ec1 Compare March 8, 2026 14:05
@riccardoperra
Copy link
Author

riccardoperra commented Mar 8, 2026

@AlemTuzlak integration is done. I probably need pipelines so I can check with pkg.pr.new if everything works with some other devtools like table which is not published yet (unfortunately I always have a lot of issues running pnpm link with angular 😄)

@AlemTuzlak
Copy link
Collaborator

@riccardoperra mind just fixing conflicts and we can get this merged

AlemTuzlak and others added 13 commits March 11, 2026 12:37
Add the @tanstack/angular-devtools package with:
- TanStackDevtoolsComponent: standalone Angular component that wraps TanStackDevtoolsCore
- Plugin system supporting Angular components rendered into devtools containers
- Type definitions for Angular-specific plugin and init configuration
- Build config using @analogjs/vite-plugin-angular for AOT compilation
- Remove @analogjs/vite-plugin-angular from build (produces empty chunks
  for library builds). Use plain esbuild via Vite instead - Angular AOT
  compilation happens at consumer's build time.
- Upgrade Angular dev deps from ^19 to ^20 for TypeScript 5.9 compat.
- Remove @angular/build dep (only needed by analogjs plugin).
- Add @tanstack/angular-devtools workspace override to root.
- Fix array-type ESLint rule in angular-devtools
- Fix import/consistent-type-specifier-style in devtools-utils angular
- Sort peerDependencies alphabetically (sherif)
- Remove unused devDependencies flagged by knip
- Remove angularCompilerOptions from tsconfig (not using ngc)
Since angular-devtools already run the code within afterNextRender in browser env, we don't like to wrap the panel returned from utils
@riccardoperra riccardoperra force-pushed the feat/angular-adapter branch 2 times, most recently from c2ffbb9 to 084d4b1 Compare March 11, 2026 11:46
@riccardoperra
Copy link
Author

@AlemTuzlak done!

@AlemTuzlak
Copy link
Collaborator

Seems some stuff is failing

@riccardoperra
Copy link
Author

@AlemTuzlak fixed, i tested in my local env and all 28 tasks succeded

pnpm nx affected --targets=test:eslint,test:sherif,test:knip,test:lib,test:types,test:build,build

Anyway, I noticed while testing my examples that warning. I saw "sideEffects" false in core but the generated dist contains some import './chunkname'

Screenshot 2026-03-11 at 13 50 19

@coderabbitai
Copy link

coderabbitai bot commented Mar 14, 2026

📝 Walkthrough

Walkthrough

Adds first-class Angular support: docs, three Angular examples, an @tanstack/angular-devtools package (runtime component, provider, types), Angular helpers in devtools-utils, build/config updates, and workspace/package manifest entries.

Changes

Cohort / File(s) Summary
Docs - Navigation & Guides
docs/config.json, docs/installation.md, docs/quick-start.md, docs/devtools-utils.md
Inserted Angular entries into docs navigation and added Angular-specific installation, quick-start, and devtools-utils documentation sections.
Docs - Framework Pages
docs/framework/angular/basic-setup.md, docs/framework/angular/adapter.md, docs/framework/angular/guides/custom-plugins.md
New Angular framework pages: basic setup, adapter details, and a custom-plugins guide (EventClient patterns and examples).
Examples - basic
examples/angular/basic/*
New basic Angular example: CLI/project configs, bootstrap, counter logic, Devtools integration, custom panel, EventClient, and editor/IDE configs.
Examples - panel
examples/angular/panel/*
New Angular panel example: custom panel implementation, event client, counter, app scaffolding, and project configs.
Examples - with-devtools
examples/angular/with-devtools/*
Example showing provider-based Devtools integration using provideTanStackDevtools, dynamic plugin management, custom plugins, and app config.
Examples - a11y-devtools
examples/angular/a11y-devtools/*
Accessibility-focused example with a11y plugin integration, demo app, panel, event client, and project configs.
Package - angular-devtools runtime
packages/angular-devtools/src/devtools.ts, packages/angular-devtools/src/types.ts, packages/angular-devtools/src/index.ts
New runtime component/class TanStackDevtools plus Angular-specific types and public exports for plugin rendering, input binding, lifecycle, and component mounting.
Package - angular-devtools provider
packages/angular-devtools/provider/with-devtools.ts, packages/angular-devtools/provider/index.ts
New provider API: DI tokens, injectDevtoolsPlugins(), provideTanStackDevtools(), signals, and environment initializer that mounts Devtools via ApplicationRef.
Package - angular-devtools packaging & config
packages/angular-devtools/package.json, packages/angular-devtools/ng-package.json, packages/angular-devtools/*tsconfig*, packages/angular-devtools/vite.config.ts, packages/angular-devtools/eslint.config.js
Build/packaging config for the Angular package (ng-packagr, tsconfigs, vite test config, eslint shim).
Package - devtools-utils Angular helpers
packages/devtools-utils/src/angular/panel.ts, packages/devtools-utils/src/angular/plugin.ts, packages/devtools-utils/src/angular/index.ts, packages/devtools-utils/package.json, packages/devtools-utils/vite.config.angular.ts, packages/devtools-utils/tsconfig.json
Added Angular helpers for creating panels and plugins, new export path ./angular, peer dep on @angular/core, and Angular-specific vite config and build entries.
Package - devtools-a11y Angular support
packages/devtools-a11y/package.json, packages/devtools-a11y/src/angular/*, packages/devtools-a11y/vite.config.ts
Added Angular exports and peerDepend on @angular/core; new Angular panel and plugin re-exports for a11y integration.
Workspace & Root manifests
package.json, knip.json
Added workspace/overrides for @tanstack/angular-devtools and knip workspace entry.
Misc - example tooling & configs
examples/.../.vscode/*, examples/.../.prettierrc, examples/.../.editorconfig, examples/.../tsconfig*, examples/.../package.json
Numerous new editor, formatter, and TypeScript config files across examples; example package manifests and project scaffolding.

Sequence Diagram(s)

sequenceDiagram
    participant App as Angular App
    participant Provider as provideTanStackDevtools
    participant CoreComp as TanStackDevtools (Angular)
    participant CoreLib as TanStackDevtoolsCore
    participant Plugins as Plugins Signal
    participant EventBus as EventClient
    participant Renderer as Component Renderer

    App->>Provider: call provideTanStackDevtools(withDevtoolsFn)
    Provider->>Plugins: initialize TANSTACK_DEVTOOLS_PLUGINS signal
    Provider->>CoreComp: dynamic import & attach TanStackDevtools component

    CoreComp->>CoreComp: after first render
    CoreComp->>Plugins: read plugins signal
    CoreComp->>Renderer: convertPlugin / resolve render (sync or async)
    Renderer->>CoreComp: mount Angular component or functional render
    CoreComp->>CoreLib: create TanStackDevtoolsCore with converted plugins
    CoreLib->>CoreLib: mount core into host element

    App->>EventBus: emit "counter-state"
    EventBus->>CoreComp: deliver event to plugin subscribers
    CoreComp->>Renderer: update component inputs / props
    Renderer->>CoreComp: reflect UI changes in plugin panels

    App->>Plugins: update plugins signal (add/remove)
    CoreComp->>Renderer: destroyComponentsInContainer(old)
    CoreComp->>Renderer: render new plugins
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 I hopped in code and sowed a seed,

Angular panels sprang up with speed,
Signals hummed and plugins danced,
Devtools found their new expanse,
A rabbit cheered: "Inspect with glee!"

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely describes the main change: adding Angular adapter and utilities support to the devtools package.
Description check ✅ Passed The PR description provides a clear overview of the changes made, including the continuation of previous work, key improvements (component rendering flexibility, non-decoration approach), and descriptions of three new examples with their purposes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

Migrating from UI to YAML configuration.

Use the @coderabbitai configuration command in a PR comment to get a dump of all your UI settings in YAML format. You can then edit this YAML file and upload it to the root of your repository to configure CodeRabbit programmatically.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (19)
examples/angular/with-devtools/src/index.html-5-5 (1)

5-5: ⚠️ Potential issue | 🟡 Minor

Title does not match the example name.

The title is "Basic" but this example is "with-devtools". This appears to be a copy-paste artifact from another example.

📝 Suggested fix
-    <title>Basic</title>
+    <title>With Devtools</title>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/with-devtools/src/index.html` at line 5, The page title in
index.html is incorrect ("Basic"); update the <title> element in the file so it
matches the example name ("with-devtools" or a human-friendly variant like "With
Devtools") to avoid the copy-paste artifact and ensure the example
metadata/title reflects this example.
examples/angular/with-devtools/.editorconfig-11-17 (1)

11-17: ⚠️ Potential issue | 🟡 Minor

Remove the redundant IntelliJ-specific TypeScript setting.

The ij_typescript_use_double_quotes = false setting at line 13 is redundant with the standard quote_type = single setting above it. Since this .editorconfig sets root = true, it's independent of the parent config and doesn't need to maintain consistency with it. However, the duplicate setting should be removed for clarity:

Remove redundant IntelliJ setting
 [*.ts]
 quote_type = single
-ij_typescript_use_double_quotes = false
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/with-devtools/.editorconfig` around lines 11 - 17, Remove
the redundant IntelliJ-specific TypeScript setting by deleting the line
"ij_typescript_use_double_quotes = false" from the file; keep the standard
"[*.ts]" block with "quote_type = single" as the single source of truth for
quote style and remove only the "ij_typescript_use_double_quotes" entry so there
is no duplicate configuration.
examples/angular/panel/src/index.html-5-5 (1)

5-5: ⚠️ Potential issue | 🟡 Minor

Update the page title to match the panel example.

<title>Basic</title> looks like a copy/paste mismatch for this panel app and can be confusing in browser tabs.

Suggested fix
-    <title>Basic</title>
+    <title>Panel</title>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/panel/src/index.html` at line 5, Update the HTML title in
examples/angular/panel/src/index.html: replace the current <title>Basic</title>
with a more accurate title reflecting the app (for example "Angular Panel
Example" or "Panel") so the browser tab and metadata correctly identify this
panel demo; modify the <title> tag content only to match the panel example name.
examples/angular/panel/.vscode/launch.json-9-16 (1)

9-16: ⚠️ Potential issue | 🟡 Minor

preLaunchTask references npm but project uses pnpm.

The preLaunchTask values use "npm: start" and "npm: test", but the package.json specifies pnpm@10.24.0 as the package manager. VS Code may not recognize these tasks correctly. Consider updating to match the project's package manager or ensure VS Code's task auto-detection handles this.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/panel/.vscode/launch.json` around lines 9 - 16, Update the
VS Code launch configuration so the preLaunchTask matches the project's package
manager: replace the "preLaunchTask" values referencing "npm: start" and "npm:
test" with the corresponding "pnpm: start" and "pnpm: test" (or ensure tasks
with those names exist), targeting the entries that define the Chrome launches
("name": "ng serve"/implicit block and "name": "ng test") so VS Code will run
the correct pnpm tasks before launching.
examples/angular/with-devtools/angular.json-12-13 (1)

12-13: ⚠️ Potential issue | 🟡 Minor

Project name should match the example folder.

The project is named "basic" but this configuration is in the with-devtools example directory. This appears to be a copy-paste oversight from another example. Consider renaming to "with-devtools" for consistency and to avoid confusion.

📝 Proposed fix
   "projects": {
-    "basic": {
+    "with-devtools": {
       "projectType": "application",

Also update build targets on lines 61 and 64:

-              "buildTarget": "basic:build:production"
+              "buildTarget": "with-devtools:build:production"
             },
             "development": {
-              "buildTarget": "basic:build:development"
+              "buildTarget": "with-devtools:build:development"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/with-devtools/angular.json` around lines 12 - 13, The
project key in the angular.json "projects" object is incorrectly named "basic"
but should match the example folder; rename the project key from "basic" to
"with-devtools" and update every reference to that key inside the file (notably
the build and serve targets currently referencing "basic") so they point to
"with-devtools" instead; ensure target entries such as the "build" and "serve"
(and any "architect" or "targets" blocks) are updated to use the new project
name so configuration and CLI commands remain consistent.
examples/angular/panel/README.md-1-3 (1)

1-3: ⚠️ Potential issue | 🟡 Minor

Update README title to match the panel example directory.

The README title says "# Basic" but this file is in the panel example directory. The title should be "# Panel" for consistency with the directory name.

📝 Proposed fix
-# Basic
+# Panel
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/panel/README.md` around lines 1 - 3, The README title
currently reads "Basic" but should match the example directory name; update the
top-level heading in the README.md by replacing the heading text "Basic" with
"Panel" so the file title reads "# Panel" for consistency with the panel example
directory.
docs/framework/angular/guides/custom-plugins.md-178-178 (1)

178-178: ⚠️ Potential issue | 🟡 Minor

Minor grammar: "built in" should be hyphenated.

📝 Suggested fix
-Both the `TanStackDevtools` and the TanStack `EventClient` come with built in debug mode which will log to the console the emitted event as well as the EventClient status.
+Both the `TanStackDevtools` and the TanStack `EventClient` come with built-in debug mode which will log to the console the emitted event as well as the EventClient status.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/framework/angular/guides/custom-plugins.md` at line 178, The phrase
"built in" in the sentence referencing TanStackDevtools and the TanStack
EventClient should be hyphenated; update the sentence so it reads "built-in
debug mode" where "built in" appears (affecting the line mentioning
TanStackDevtools and EventClient) to correct the grammar.
examples/angular/with-devtools/README.md-1-3 (1)

1-3: ⚠️ Potential issue | 🟡 Minor

README title mismatch with project directory.

The title says "Basic" but this file is in the with-devtools example directory. This appears to be a copy-paste oversight from the basic example.

📝 Suggested fix
-# Basic
+# With Devtools

-This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.2.0.
+This project was generated using [Angular CLI](https://github.com/angular/angular-cli).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/with-devtools/README.md` around lines 1 - 3, The README
title currently reads "Basic" which doesn't match this example's directory;
update the top-level heading in examples/angular/with-devtools/README.md (the
line containing "# Basic") to a correct, descriptive title such as "#
with-devtools" or "# Angular with DevTools" so the file name and content reflect
the example's purpose.
docs/framework/angular/guides/custom-plugins.md-197-197 (1)

197-197: ⚠️ Potential issue | 🟡 Minor

Minor spelling: "Where as" should be "Whereas".

📝 Suggested fix
-Where as the EventClient's debug mode can be activated by:
+Whereas the EventClient's debug mode can be activated by:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/framework/angular/guides/custom-plugins.md` at line 197, Replace the
incorrect two-word phrase "Where as" with the correct single-word "Whereas" in
the docs/framework/angular/guides/custom-plugins.md content to fix the minor
spelling error.
docs/framework/angular/guides/custom-plugins.md-213-216 (1)

213-216: ⚠️ Potential issue | 🟡 Minor

Add language specifier to fenced code block.

The code block showing debug output should have a language specifier for consistent formatting.

📝 Suggested fix
-```
+```text
 [tanstack-devtools:client-bus] Initializing client event bus

 [tanstack-devtools:custom-devtools-plugin] Registered event to bus custom-devtools:counter-state
 ```
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/framework/angular/guides/custom-plugins.md` around lines 213 - 216, The
fenced code block showing debug output lacks a language specifier; update the
block in docs/framework/angular/guides/custom-plugins.md around the debug output
containing "[tanstack-devtools:client-bus] Initializing client event bus" and
"[tanstack-devtools:custom-devtools-plugin] Registered event to bus
custom-devtools:counter-state" by adding a language tag (e.g., ```text) after
the opening backticks so the block becomes a fenced code block with the "text"
specifier for consistent formatting.
docs/framework/angular/guides/custom-plugins.md-122-135 (1)

122-135: ⚠️ Potential issue | 🟡 Minor

Template incorrectly calls getter with parentheses.

In the template at line 125, stateHistory() is called with parentheses, but stateHistory is defined as a getter (line 133-135), not a method. Getters should be accessed without parentheses.

📝 Suggested fix
   template: `
     <div>
       <div>{{ state()?.count }}</div>
-      <div>{{ stateHistory() }}</div>
+      <div>{{ stateHistory }}</div>
     </div>
   `,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/framework/angular/guides/custom-plugins.md` around lines 122 - 135, The
template incorrectly invokes the getter stateHistory as a method; update the
template to reference the getter without parentheses (use {{ stateHistory }}
instead of {{ stateHistory() }}) so it matches the DevtoolPanel get
stateHistory() accessor and returns the computed JSON string properly.
docs/framework/angular/basic-setup.md-18-32 (1)

18-32: ⚠️ Potential issue | 🟡 Minor

Fix import name mismatch in code example.

The import statement on line 20 imports TanStackDevtoolsComponent, but the imports array on line 25 references TanStackDevtools. This inconsistency will cause a compilation error when users follow this documentation.

📝 Proposed fix
 ```typescript
 import { Component } from '@angular/core'
-import { TanStackDevtoolsComponent } from '@tanstack/angular-devtools'
+import { TanStackDevtools } from '@tanstack/angular-devtools'

 `@Component`({
   selector: 'app-root',
   standalone: true,
   imports: [TanStackDevtools],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/framework/angular/basic-setup.md` around lines 18 - 32, The import name
in the example is inconsistent: the file imports TanStackDevtoolsComponent but
the App component's standalone imports array references TanStackDevtools. Fix by
making the imported symbol match the one used in the App component — either
change the import to TanStackDevtools (recommended) or change the imports array
to use TanStackDevtoolsComponent so the imported identifier and the imports
entry are the same; update the import statement near the top that currently
names TanStackDevtoolsComponent accordingly.
docs/framework/angular/adapter.md-46-48 (1)

46-48: ⚠️ Potential issue | 🟡 Minor

Misleading text references non-existent component field.

Line 48 states the adapter "uses component ... instead of render", but the actual field name shown in the type definition and code examples is render. This appears to be outdated or incorrect.

📝 Proposed fix
-The Angular adapter uses `component` (an Angular component class reference) instead of `render` (a JSX element) in plugin definitions. Inputs are provided through the `inputs` field and bound to the component with `setInput()`, rather than being embedded directly in a JSX expression or passed via `v-bind`.
+The Angular adapter uses `render` with an Angular component class reference (rather than a JSX element) in plugin definitions. Inputs are provided through the `inputs` field and bound to the component with `setInput()`, rather than being embedded directly in a JSX expression or passed via `v-bind`.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/framework/angular/adapter.md` around lines 46 - 48, The sentence
incorrectly refers to a non-existent `component` field; update the documentation
text to reference the actual `render` field used in the adapter type and
examples (e.g., change "uses `component` (an Angular component class reference)
instead of `render`" to indicate that the adapter uses `render` and explain that
inputs are provided via the `inputs` field and bound with `setInput()`), or if
the implementation should instead use `component`, update the type/examples to
match—ensure consistency between the docs and the symbols `render`, `inputs`,
and `setInput()`.
docs/framework/angular/adapter.md-38-45 (1)

38-45: ⚠️ Potential issue | 🟡 Minor

Documentation table has incorrect type for render field.

The table states render has type Type<any>, but the type definition above (line 31) shows it should be TanStackDevtoolsAngularPluginRender. The description should align with the actual type definition for accuracy.

📝 Proposed fix
-| `render`      | `Type<any>` | The Angular component class to render as the plugin panel content. |
+| `render`      | `TanStackDevtoolsAngularPluginRender` | The Angular component class, lazy component (via dynamic import), or custom function to render as the plugin panel content. |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/framework/angular/adapter.md` around lines 38 - 45, The docs table
incorrectly types the render field as Type<any>; update the table entry for the
`render` row to use the actual type name `TanStackDevtoolsAngularPluginRender`
and adjust the description to reference that type (e.g., "The render property is
a TanStackDevtoolsAngularPluginRender that defines the Angular component/class
used for plugin panel content"); ensure the `render` column now matches the type
alias `TanStackDevtoolsAngularPluginRender` used in the code.
packages/angular-devtools/src/types.ts-44-62 (1)

44-62: ⚠️ Potential issue | 🟡 Minor

JSDoc example references component but type uses render.

The JSDoc comment states "Each plugin should have a component" and the example shows component: MyPluginComponent, but the actual TanStackDevtoolsAngularPlugin type at line 38 defines render, not component. This mismatch will confuse users.

📝 Proposed fix to align JSDoc with actual type
   /**
    * Array of plugins to be used in the devtools.
-   * Each plugin should have a `component` that is an Angular component class.
+   * Each plugin should have a `render` property that specifies an Angular component class or render function.
    *
    * Example:
    * ```typescript
    * `@Component`({
    *   template: `<tanstack-devtools [plugins]="plugins" />`,
    *   imports: [TanStackDevtoolsComponent],
    * })
    * class AppComponent {
    *   plugins: TanStackDevtoolsAngularPlugin[] = [
-   *     { name: 'My Plugin', component: MyPluginComponent }
+   *     { name: 'My Plugin', render: MyPluginComponent }
    *   ];
    * }
    * ```
    */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/angular-devtools/src/types.ts` around lines 44 - 62, The JSDoc for
TanStackDevtoolsAngularInit and its example incorrectly refer to a `component`
property while the actual type TanStackDevtoolsAngularPlugin defines `render`;
update the JSDoc/example to use `render` (e.g., replace `{ name: 'My Plugin',
component: MyPluginComponent }` with `{ name: 'My Plugin', render:
MyPluginComponent }`) so the documentation matches the
TanStackDevtoolsAngularPlugin type and avoids confusion.
packages/devtools-utils/src/angular/panel.ts-53-57 (1)

53-57: ⚠️ Potential issue | 🟡 Minor

Add error handling for the lazy-loaded panel.

When CoreClass is a function returning a Promise (lazy loading), there's no .catch() handler. If the dynamic import fails, the error will be unhandled.

Proposed fix
         if (isConstructor) {
           mount(new CoreClass(inputs()))
         } else {
           CoreClass()
             .then((ResolvedCoreClass) => new ResolvedCoreClass(inputs()))
             .then(mount)
+            .catch((error) => {
+              console.error('[createAngularPanel] Failed to load panel:', error)
+            })
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools-utils/src/angular/panel.ts` around lines 53 - 57, The
lazy-load branch calling CoreClass() lacks error handling; wrap the promise
chain starting at CoreClass() so rejections are caught and handled—add a .catch
handler to the CoreClass().then(...).then(mount) chain that logs the error
and/or invokes the existing mount/error handling flow (reference CoreClass,
inputs(), and mount) to avoid unhandled promise rejections and surface failures
during dynamic import.
packages/angular-devtools/provider/with-devtools.ts-75-80 (1)

75-80: ⚠️ Potential issue | 🟡 Minor

Use isPlatformBrowser() instead of string comparison for platform check.

The PLATFORM_ID value should be checked using Angular's isPlatformBrowser() utility from @angular/common rather than direct string comparison. The string 'browser' is not the documented API contract.

Proposed fix

Add to imports:

import { isPlatformBrowser } from '@angular/common'
-      if (inject(PLATFORM_ID) !== 'browser') {
+      if (!isPlatformBrowser(inject(PLATFORM_ID))) {
         return
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/angular-devtools/provider/with-devtools.ts` around lines 75 - 80,
Replace the direct string comparison of PLATFORM_ID with Angular's
isPlatformBrowser check: import isPlatformBrowser from '@angular/common' and
change the platform guard in the provideEnvironmentInitializer block (where
inject(PLATFORM_ID) is used) to use if (!isPlatformBrowser(inject(PLATFORM_ID)))
return; so the environment check uses the documented API rather than comparing
to the string 'browser'.
packages/angular-devtools/provider/with-devtools.ts-91-103 (1)

91-103: ⚠️ Potential issue | 🟡 Minor

Add error handling for the dynamic import.

The dynamic import of @tanstack/angular-devtools has no .catch() handler. If the import fails (e.g., network error, bundler misconfiguration), the error will be silently swallowed or result in an unhandled promise rejection.

Proposed fix
       import('@tanstack/angular-devtools').then(({ TanStackDevtools }) => {
         const componentRef = createComponent(TanStackDevtools, {
           environmentInjector: injector,
           bindings: [
             inputBinding('config', config),
             inputBinding('plugins', plugins),
             inputBinding('eventBusConfig', eventBusConfig),
           ],
         })

         applicationRef.attachView(componentRef.hostView)
         componentRef.changeDetectorRef.detectChanges()
-      })
+      }).catch((error) => {
+        console.error('[`@tanstack/angular-devtools`] Failed to load devtools:', error)
+      })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/angular-devtools/provider/with-devtools.ts` around lines 91 - 103,
The dynamic import of '@tanstack/angular-devtools' in with-devtools.ts lacks
error handling; update the import promise chain that calls
import('@tanstack/angular-devtools').then(...) to add a .catch handler that logs
the error and a contextual message (e.g., "Failed to load TanStackDevtools") and
avoids leaving an unhandled rejection; reference the import call and the
createComponent/TanStackDevtools block so the catch can report the error and
optionally perform a safe fallback (e.g., skip attaching the devtools) if the
module fails to load.
packages/devtools-utils/src/angular/panel.ts-15-20 (1)

15-20: ⚠️ Potential issue | 🟡 Minor

Type guard using !!o.prototype is fragile.

This check relies on the fact that arrow functions and async functions lack a prototype property, while class constructors have one. However, regular functions also have prototype, so if someone passes a non-arrow function that returns a Promise, it would incorrectly be identified as a constructor.

Consider a more robust check:

Proposed fix
 function isPanelClassConstructor<
   TComponentProps extends DevtoolsPanelProps,
   TCoreDevtoolsClass extends BaseCorePanelClass,
 >(o: any): o is CoreClassConstructor<TComponentProps, TCoreDevtoolsClass> {
-  return !!o.prototype
+  // Classes have a prototype with a constructor property pointing back to themselves
+  return typeof o === 'function' && o.prototype?.constructor === o && !o.prototype[Symbol.toStringTag]
 }

Alternatively, if the lazy factory is always expected to be an arrow function or async function (which is typical for dynamic imports), the current approach works in practice but deserves a comment explaining the assumption.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools-utils/src/angular/panel.ts` around lines 15 - 20, The
type-guard isPanelClassConstructor currently uses !!o.prototype which
misclassifies plain functions as class constructors; replace that heuristic with
a robust check that ensures o is an ES6 class constructor, e.g. verify typeof o
=== 'function' and use Function.prototype.toString.call(o) to test for a leading
"class" token (e.g. /^class\s/.test(...)); update the function
isPanelClassConstructor<TComponentProps, TCoreDevtoolsClass> to use this test
(or, if you intentionally only accept arrow/async factories, add a clear comment
explaining that assumption) so non-class functions are not treated as
constructors.
🧹 Nitpick comments (14)
examples/angular/with-devtools/.vscode/mcp.json (1)

6-6: Angular's official MCP documentation uses unpinned @angular/cli intentionally.

The unpinned version on line 6 aligns with Angular's documented setup for .vscode/mcp.json. Since the MCP server is experimental, Angular recommends tracking the latest CLI to pick up fixes and improvements rather than pinning.

While pinning to @angular/cli@20 would improve reproducibility across team members, it goes against Angular's official guidance for this feature. If your team prefers consistency and controlled upgrades, pinning is a valid choice, but it's not the documented approach.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/with-devtools/.vscode/mcp.json` at line 6, The current args
entry ["-y", "@angular/cli", "mcp"] intentionally uses an unpinned `@angular/cli`
per Angular's MCP docs—leave it as-is to track latest fixes; if you instead want
reproducible behavior across the team, change that args element to
"@angular/cli@20" (i.e., replace "@angular/cli" with "@angular/cli@20") so the
MCP launch uses the pinned CLI version.
examples/angular/panel/package.json (1)

22-22: Inconsistent version specifier for @tanstack/devtools-event-client.

This dependency uses an exact version (0.4.2) while other @tanstack/* packages use caret ranges (^0.0.1, ^0.10.13, ^0.3.3). Consider using a caret range for consistency unless pinning is intentional.

Suggested fix
-    "@tanstack/devtools-event-client": "0.4.2",
+    "@tanstack/devtools-event-client": "^0.4.2",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/panel/package.json` at line 22, The dependency
"@tanstack/devtools-event-client" is pinned to an exact version "0.4.2" while
other `@tanstack` packages in package.json use caret ranges; update the version
specifier for "@tanstack/devtools-event-client" to use a caret range (e.g.
"^0.4.2") to match the other `@tanstack/`* entries and maintain consistent semver
behavior across the project.
packages/devtools-utils/vite.config.angular.ts (1)

5-7: Drop the empty plugins field for clarity.

No behavior change, just removes noise in the config object.

♻️ Suggested cleanup
 const config = defineConfig({
-  plugins: [],
   test: {
     name: packageJson.name,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools-utils/vite.config.angular.ts` around lines 5 - 7, The
config object created via defineConfig currently includes an unnecessary empty
"plugins" property; remove the "plugins: []" entry from the defineConfig call
(the config constant) to clean up the Vite configuration without changing
behavior—locate the defineConfig({ ... }) block that assigns to config and
delete the plugins key.
examples/angular/basic/src/app/devtools/custom-devtools-panel.ts (1)

27-28: Clarify the "Automatically added" comment.

The comment // Automatically added on the theme input is unclear. Consider elaborating on what "automatically adds" this input or remove the comment if it's no longer relevant.

📝 Suggested clarification
-  // Automatically added
+  // Injected by the devtools plugin framework
   readonly theme = input<string>()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/basic/src/app/devtools/custom-devtools-panel.ts` around
lines 27 - 28, The "// Automatically added" comment above the readonly theme =
input<string>() declaration is ambiguous; either replace it with a clearer note
that states who/what adds this input and when (e.g., "Added by DevTools host at
runtime" or "Injected by framework build step") or remove the comment entirely
if it's stale; update the comment adjacent to the readonly theme =
input<string>() symbol to explicitly state its source and purpose so future
readers understand why the input exists.
examples/angular/panel/src/app/counter.ts (2)

10-28: Consider extracting duplicated emit logic.

The DevtoolsEventClient.emit('counter-state', ...) call is duplicated in both increment and decrement. Consider extracting this into a helper function for DRY code.

♻️ Proposed refactor
 export function createCounter() {
   const count = signal(0)
   const history: Array<number> = []
 
+  const emitState = () => {
+    DevtoolsEventClient.emit('counter-state', {
+      count: count(),
+      history,
+    })
+  }
+
   return {
     value: () => count(),
     increment: () => {
       count.update((n) => n + 1)
       history.push(count())
-
-      // The emit eventSuffix must match that of the EventMap defined in eventClient.ts
-      DevtoolsEventClient.emit('counter-state', {
-        count: count(),
-        history,
-      })
+      emitState()
     },
     decrement: () => {
       count.update((n) => n - 1)
       history.push(count())
-
-      DevtoolsEventClient.emit('counter-state', {
-        count: count(),
-        history,
-      })
+      emitState()
     },
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/panel/src/app/counter.ts` around lines 10 - 28, increment
and decrement both call DevtoolsEventClient.emit('counter-state', { count:
count(), history }) causing duplicated emit logic; extract that into a small
helper (e.g., emitCounterState or notifyCounterState) that reads count() and
history and calls DevtoolsEventClient.emit('counter-state', payload), then
replace the duplicated emit calls in the increment and decrement functions with
a single call to the new helper; ensure the helper is colocated with the
count/history variables and used after updating history so payload stays
correct.

4-9: Consider emitting initial state.

The panel won't display any state until the user clicks increment or decrement for the first time. Consider emitting the initial state when the counter is created, or document this behavior as intentional.

💡 Optional: Emit initial state
 export function createCounter() {
   const count = signal(0)
   const history: Array<number> = []
+
+  // Emit initial state
+  DevtoolsEventClient.emit('counter-state', {
+    count: count(),
+    history,
+  })
 
   return {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/panel/src/app/counter.ts` around lines 4 - 9, createCounter
currently never emits its initial value so the UI sees no state until the first
increment/decrement; fix it by emitting the initial state when the counter is
created: after initializing count and history in createCounter, record the
initial value (e.g., push count() into history) and invoke the same
notifier/emit function you use for subsequent updates (or expose/ call value()
once) so the panel receives the initial state; update references in
createCounter, count, history and the notifier/emit handler accordingly.
docs/framework/angular/basic-setup.md (1)

51-58: Class name inconsistency between examples.

The first example uses App as the class name (line 31), while this example uses AppComponent (line 51). For consistency within the same documentation page, consider using the same naming convention.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/framework/angular/basic-setup.md` around lines 51 - 58, The examples use
inconsistent class names — one declares class App and the later snippet declares
class AppComponent; update the later example so the class name matches the first
example (e.g., rename AppComponent to App) and ensure the export and any
internal references (plugins array, TanStackDevtoolsAngularPlugin,
AngularQueryDevtoolsPanel) are updated accordingly so both examples use the same
class identifier.
packages/angular-devtools/package.json (2)

55-63: Duplicate tslib in dependencies and devDependencies.

tslib is listed in both dependencies (line 57) and devDependencies (line 62). The runtime dependency listing is sufficient; the devDependencies entry is redundant.

♻️ Remove duplicate tslib
   "devDependencies": {
     "@angular/core": "^21.2.0",
-    "ng-packagr": "^21.2.0",
-    "tslib": "^2.3.0"
+    "ng-packagr": "^21.2.0"
   },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/angular-devtools/package.json` around lines 55 - 63, Remove the
duplicate tslib entry by keeping it under "dependencies" and deleting the
"tslib" entry from "devDependencies" in package.json; update the package.json
object so only the top-level "dependencies" contains "tslib" and ensure no other
references to tslib remain in dev tooling config (look for "tslib" in the
package.json dependencies/devDependencies blocks).

53-53: Build script uses Unix-specific commands.

The rm -rf commands in the build script are not cross-platform. This may cause issues for contributors on Windows.

♻️ Use cross-platform alternative

Consider using rimraf or the existing premove (used in the clean script) for cross-platform compatibility:

-    "build": "ng-packagr -p ng-package.json -c tsconfig.build.json && rm -rf ./dist/package.json && rm -rf ./dist/provider/package.json"
+    "build": "ng-packagr -p ng-package.json -c tsconfig.build.json && premove ./dist/package.json ./dist/provider/package.json"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/angular-devtools/package.json` at line 53, The build script in
package.json ("build") uses Unix-only rm -rf; replace those calls with a
cross-platform remover such as rimraf (or reuse the existing "premove" helper
used by "clean") so Windows contributors won't fail; update the "build" script
to call rimraf on ./dist/package.json and ./dist/provider/package.json (or call
the existing preremove command), and if using rimraf add it to devDependencies
and/or the scripts so CI and local installs have the tool available.
examples/angular/basic/src/app/app.ts (1)

19-21: Consider using a more meaningful defer trigger condition.

@defer (when true) is equivalent to an unconditional defer, which loads the content when the browser is idle. This works but the when true condition is redundant and potentially confusing.

♻️ Optional: Simplified defer syntax
-    `@defer` (when true) {
+    `@defer` {
       <tanstack-devtools [plugins]="plugins()" [eventBusConfig]="{ debug: true }" />
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/basic/src/app/app.ts` around lines 19 - 21, The `@defer` block
uses an explicit always-true condition (`@defer` (when true)) which is redundant;
change it to use the simpler unconditional defer syntax or a meaningful
predicate instead. Update the `@defer` usage wrapping <tanstack-devtools
[plugins]="plugins()" [eventBusConfig]="{ debug: true }" /> by removing "(when
true)" or replacing it with a real trigger (e.g., a boolean like isIdle or
isDevMode) so the intent is clear and the defer behavior for tanstack-devtools
and plugins() is preserved.
examples/angular/panel/src/app/devtools/custom-devtools-panel.ts (1)

12-17: Consider using textContent for safer DOM updates (optional for example code).

The static analysis flagged the innerHTML usage. While in this example the data is trusted internal state (numeric count and array history), for production patterns consider using DOM APIs like textContent or a sanitization library.

For this example demonstrating devtools integration, the current approach is acceptable.

♻️ Optional: Safer DOM update pattern
-    function render(count: number, history: string) {
-      plugin.innerHTML = `
-       <div>counter state: ${count}</div>
-       <div>counter history: ${history}</div>
-      `
-    }
+    function render(count: number, history: string) {
+      plugin.textContent = ''
+      const countDiv = element.ownerDocument.createElement('div')
+      countDiv.textContent = `counter state: ${count}`
+      const historyDiv = element.ownerDocument.createElement('div')
+      historyDiv.textContent = `counter history: ${history}`
+      plugin.appendChild(countDiv)
+      plugin.appendChild(historyDiv)
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/panel/src/app/devtools/custom-devtools-panel.ts` around
lines 12 - 17, The render(count: number, history: string) function writes
unescaped HTML into plugin via plugin.innerHTML which static analysis flagged;
change render to avoid innerHTML by creating or selecting child elements under
the plugin container and setting their textContent (or concatenating safe
strings) to display count and history, e.g., create two divs or reuse existing
elements and assign their textContent using the plugin reference instead of
assigning plugin.innerHTML.
packages/angular-devtools/provider/with-devtools.ts (2)

86-89: Plugins are set once and won't react to changes in devtoolsOptions().plugins.

config and eventBusConfig are wrapped in computed() to stay reactive, but plugins is set imperatively with plugins.set(...) only during initialization. If devtoolsOptions().plugins changes later, the devtools won't reflect those changes.

If this is intentional (plugins are expected to be static), consider adding a brief comment. Otherwise, consider using an effect() to keep plugins in sync:

Proposed fix if reactivity is needed
+      import { effect } from '@angular/core'
       // ...
-      plugins.set(devtoolsOptions().plugins ?? [])
+      effect(() => {
+        plugins.set(devtoolsOptions().plugins ?? [])
+      })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/angular-devtools/provider/with-devtools.ts` around lines 86 - 89,
The plugins array is only set once with plugins.set(devtoolsOptions().plugins ??
[]) and won't update when devtoolsOptions().plugins changes; change this to a
reactive effect that watches devtoolsOptions().plugins and calls
plugins.set(...) whenever it updates (or add a comment if immutability is
intentional). Specifically, replace the one-time call to plugins.set with an
effect/autorun that reads devtoolsOptions().plugins and updates plugins via
plugins.set(...) so it stays in sync with the computed config/eventBusConfig
used elsewhere.

32-34: Consider tightening the Array<any> types.

Both WithDevtoolsOptionsFn and WithDevtoolsOptions.deps use Array<any>, which loses type safety. If stronger typing isn't feasible due to Angular DI constraints, this is acceptable, but a brief JSDoc comment explaining why any is necessary would help future maintainers.

Also applies to: 56-58

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/angular-devtools/provider/with-devtools.ts` around lines 32 - 34,
Tighten the loose Array<any> usages by replacing them with a safer type (prefer
unknown[] or make WithDevtoolsOptionsFn generic like <Args extends
any[]>(...args: Args) => TanStackDevtoolsAllOptions and use Args for
WithDevtoolsOptions.deps) so callers retain type information; if Angular DI
constraints prevent stronger typing, add a short JSDoc above
WithDevtoolsOptionsFn and the WithDevtoolsOptions.deps field explaining why any
is necessary and referencing the DI limitation. Ensure you update the
declarations for WithDevtoolsOptionsFn and the WithDevtoolsOptions.deps usage
(the symbols named WithDevtoolsOptionsFn and WithDevtoolsOptions.deps)
consistently.
packages/devtools-utils/src/angular/panel.ts (1)

22-65: Consider adding JSDoc and explicit return type for public API clarity.

createAngularPanel is a public API but lacks documentation explaining its purpose, the tuple return structure, and why the second element is () => null. Adding a brief JSDoc (without @params/@returns per coding guidelines) and an explicit return type would improve discoverability.

Example documentation
+/**
+ * Creates an Angular panel adapter for TanStack Devtools.
+ * 
+ * Accepts either a CoreClass constructor directly or a lazy loader function
+ * that returns a Promise resolving to the constructor.
+ * 
+ * Returns a tuple where the first element is a factory for mounting the panel,
+ * and the second is a no-op placeholder for framework compatibility.
+ */
 export function createAngularPanel<
   TComponentProps extends DevtoolsPanelProps,
   TCoreDevtoolsClass extends BaseCorePanelClass,
 >(
   CoreClass:
     | CoreClassConstructor<TComponentProps, TCoreDevtoolsClass>
     | (() => Promise<
         CoreClassConstructor<TComponentProps, TCoreDevtoolsClass>
       >),
-) {
+): readonly [
+  () => (inputs: () => TComponentProps, host: HTMLElement) => () => void,
+  () => null,
+] {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools-utils/src/angular/panel.ts` around lines 22 - 65, Add JSDoc
and an explicit return type to the public function createAngularPanel to
document its purpose, the tuple structure, and why the second tuple entry is ()
=> null; update the function signature to include a clear return type (e.g.,
readonly [ (inputs: () => TComponentProps, host: HTMLElement) => () => void, ()
=> null ]) and add a brief JSDoc block above createAngularPanel describing what
the factory returns, what the first tuple function does (mount/unmount via
CoreClass), and why the second is a stub; reference the createAngularPanel
function, the returned tuple and the second element (() => null) when making
these edits.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/angular/with-devtools/src/app/devtools/custom-devtools-panel.ts`:
- Around line 13-23: The `@Component` metadata for the custom devtools panel
includes an imports array (JsonPipe) but is missing standalone: true, which is
required for using imports; update the Component decorator metadata (the
`@Component` applied to the custom devtool panel) to include standalone: true so
the imports property is valid and the Angular compiler will succeed.

In `@packages/angular-devtools/src/devtools.ts`:
- Around line 275-279: The current isClassConstructor wrongly treats plain
functions as classes; replace its heuristic with a reliable ES6-class check: if
o is a function, call Function.prototype.toString on it and test for a leading
"class" token (e.g. /^\s*class\b/), returning true only for that case. Update
the isClassConstructor<T> implementation (the function named isClassConstructor)
to use this toString/regex check so functional renderers are not misclassified
and therefore not handed to renderComponent().
- Around line 125-151: The spread usage ...(plugin.inputs ?? {}) drops
non-plain-object inputs (functions/signals); instead stop spreading and pass the
inputs value directly as a property so functions/signals are preserved. Update
the three call sites that build the options object — the calls to
this.renderComponent(plugin.name...), this.renderComponent(plugin.render...),
and this.#renderComponentFunction(...) — to construct options like { theme,
inputs: plugin.inputs ?? {} } (or { theme, inputs: plugin.inputs } if a default
empty object isn't desired) rather than spreading plugin.inputs.
- Around line 142-153: The async call to this.#renderComponentFunction with
plugin.render (typed as TanStackDevtoolsAngularPluginRender) can reject and
currently has no error handling; wrap the call in a try/catch (or attach a
.catch) to handle rejections, log the error (include plugin identity/context),
and optionally fall back or swallow to prevent unhandled promise
rejections—update the call site where this.#renderComponentFunction(...) is
invoked to catch and handle any thrown errors.

---

Minor comments:
In `@docs/framework/angular/adapter.md`:
- Around line 46-48: The sentence incorrectly refers to a non-existent
`component` field; update the documentation text to reference the actual
`render` field used in the adapter type and examples (e.g., change "uses
`component` (an Angular component class reference) instead of `render`" to
indicate that the adapter uses `render` and explain that inputs are provided via
the `inputs` field and bound with `setInput()`), or if the implementation should
instead use `component`, update the type/examples to match—ensure consistency
between the docs and the symbols `render`, `inputs`, and `setInput()`.
- Around line 38-45: The docs table incorrectly types the render field as
Type<any>; update the table entry for the `render` row to use the actual type
name `TanStackDevtoolsAngularPluginRender` and adjust the description to
reference that type (e.g., "The render property is a
TanStackDevtoolsAngularPluginRender that defines the Angular component/class
used for plugin panel content"); ensure the `render` column now matches the type
alias `TanStackDevtoolsAngularPluginRender` used in the code.

In `@docs/framework/angular/basic-setup.md`:
- Around line 18-32: The import name in the example is inconsistent: the file
imports TanStackDevtoolsComponent but the App component's standalone imports
array references TanStackDevtools. Fix by making the imported symbol match the
one used in the App component — either change the import to TanStackDevtools
(recommended) or change the imports array to use TanStackDevtoolsComponent so
the imported identifier and the imports entry are the same; update the import
statement near the top that currently names TanStackDevtoolsComponent
accordingly.

In `@docs/framework/angular/guides/custom-plugins.md`:
- Line 178: The phrase "built in" in the sentence referencing TanStackDevtools
and the TanStack EventClient should be hyphenated; update the sentence so it
reads "built-in debug mode" where "built in" appears (affecting the line
mentioning TanStackDevtools and EventClient) to correct the grammar.
- Line 197: Replace the incorrect two-word phrase "Where as" with the correct
single-word "Whereas" in the docs/framework/angular/guides/custom-plugins.md
content to fix the minor spelling error.
- Around line 213-216: The fenced code block showing debug output lacks a
language specifier; update the block in
docs/framework/angular/guides/custom-plugins.md around the debug output
containing "[tanstack-devtools:client-bus] Initializing client event bus" and
"[tanstack-devtools:custom-devtools-plugin] Registered event to bus
custom-devtools:counter-state" by adding a language tag (e.g., ```text) after
the opening backticks so the block becomes a fenced code block with the "text"
specifier for consistent formatting.
- Around line 122-135: The template incorrectly invokes the getter stateHistory
as a method; update the template to reference the getter without parentheses
(use {{ stateHistory }} instead of {{ stateHistory() }}) so it matches the
DevtoolPanel get stateHistory() accessor and returns the computed JSON string
properly.

In `@examples/angular/panel/.vscode/launch.json`:
- Around line 9-16: Update the VS Code launch configuration so the preLaunchTask
matches the project's package manager: replace the "preLaunchTask" values
referencing "npm: start" and "npm: test" with the corresponding "pnpm: start"
and "pnpm: test" (or ensure tasks with those names exist), targeting the entries
that define the Chrome launches ("name": "ng serve"/implicit block and "name":
"ng test") so VS Code will run the correct pnpm tasks before launching.

In `@examples/angular/panel/README.md`:
- Around line 1-3: The README title currently reads "Basic" but should match the
example directory name; update the top-level heading in the README.md by
replacing the heading text "Basic" with "Panel" so the file title reads "#
Panel" for consistency with the panel example directory.

In `@examples/angular/panel/src/index.html`:
- Line 5: Update the HTML title in examples/angular/panel/src/index.html:
replace the current <title>Basic</title> with a more accurate title reflecting
the app (for example "Angular Panel Example" or "Panel") so the browser tab and
metadata correctly identify this panel demo; modify the <title> tag content only
to match the panel example name.

In `@examples/angular/with-devtools/.editorconfig`:
- Around line 11-17: Remove the redundant IntelliJ-specific TypeScript setting
by deleting the line "ij_typescript_use_double_quotes = false" from the file;
keep the standard "[*.ts]" block with "quote_type = single" as the single source
of truth for quote style and remove only the "ij_typescript_use_double_quotes"
entry so there is no duplicate configuration.

In `@examples/angular/with-devtools/angular.json`:
- Around line 12-13: The project key in the angular.json "projects" object is
incorrectly named "basic" but should match the example folder; rename the
project key from "basic" to "with-devtools" and update every reference to that
key inside the file (notably the build and serve targets currently referencing
"basic") so they point to "with-devtools" instead; ensure target entries such as
the "build" and "serve" (and any "architect" or "targets" blocks) are updated to
use the new project name so configuration and CLI commands remain consistent.

In `@examples/angular/with-devtools/README.md`:
- Around line 1-3: The README title currently reads "Basic" which doesn't match
this example's directory; update the top-level heading in
examples/angular/with-devtools/README.md (the line containing "# Basic") to a
correct, descriptive title such as "# with-devtools" or "# Angular with
DevTools" so the file name and content reflect the example's purpose.

In `@examples/angular/with-devtools/src/index.html`:
- Line 5: The page title in index.html is incorrect ("Basic"); update the
<title> element in the file so it matches the example name ("with-devtools" or a
human-friendly variant like "With Devtools") to avoid the copy-paste artifact
and ensure the example metadata/title reflects this example.

In `@packages/angular-devtools/provider/with-devtools.ts`:
- Around line 75-80: Replace the direct string comparison of PLATFORM_ID with
Angular's isPlatformBrowser check: import isPlatformBrowser from
'@angular/common' and change the platform guard in the
provideEnvironmentInitializer block (where inject(PLATFORM_ID) is used) to use
if (!isPlatformBrowser(inject(PLATFORM_ID))) return; so the environment check
uses the documented API rather than comparing to the string 'browser'.
- Around line 91-103: The dynamic import of '@tanstack/angular-devtools' in
with-devtools.ts lacks error handling; update the import promise chain that
calls import('@tanstack/angular-devtools').then(...) to add a .catch handler
that logs the error and a contextual message (e.g., "Failed to load
TanStackDevtools") and avoids leaving an unhandled rejection; reference the
import call and the createComponent/TanStackDevtools block so the catch can
report the error and optionally perform a safe fallback (e.g., skip attaching
the devtools) if the module fails to load.

In `@packages/angular-devtools/src/types.ts`:
- Around line 44-62: The JSDoc for TanStackDevtoolsAngularInit and its example
incorrectly refer to a `component` property while the actual type
TanStackDevtoolsAngularPlugin defines `render`; update the JSDoc/example to use
`render` (e.g., replace `{ name: 'My Plugin', component: MyPluginComponent }`
with `{ name: 'My Plugin', render: MyPluginComponent }`) so the documentation
matches the TanStackDevtoolsAngularPlugin type and avoids confusion.

In `@packages/devtools-utils/src/angular/panel.ts`:
- Around line 53-57: The lazy-load branch calling CoreClass() lacks error
handling; wrap the promise chain starting at CoreClass() so rejections are
caught and handled—add a .catch handler to the CoreClass().then(...).then(mount)
chain that logs the error and/or invokes the existing mount/error handling flow
(reference CoreClass, inputs(), and mount) to avoid unhandled promise rejections
and surface failures during dynamic import.
- Around line 15-20: The type-guard isPanelClassConstructor currently uses
!!o.prototype which misclassifies plain functions as class constructors; replace
that heuristic with a robust check that ensures o is an ES6 class constructor,
e.g. verify typeof o === 'function' and use Function.prototype.toString.call(o)
to test for a leading "class" token (e.g. /^class\s/.test(...)); update the
function isPanelClassConstructor<TComponentProps, TCoreDevtoolsClass> to use
this test (or, if you intentionally only accept arrow/async factories, add a
clear comment explaining that assumption) so non-class functions are not treated
as constructors.

---

Nitpick comments:
In `@docs/framework/angular/basic-setup.md`:
- Around line 51-58: The examples use inconsistent class names — one declares
class App and the later snippet declares class AppComponent; update the later
example so the class name matches the first example (e.g., rename AppComponent
to App) and ensure the export and any internal references (plugins array,
TanStackDevtoolsAngularPlugin, AngularQueryDevtoolsPanel) are updated
accordingly so both examples use the same class identifier.

In `@examples/angular/basic/src/app/app.ts`:
- Around line 19-21: The `@defer` block uses an explicit always-true condition
(`@defer` (when true)) which is redundant; change it to use the simpler
unconditional defer syntax or a meaningful predicate instead. Update the `@defer`
usage wrapping <tanstack-devtools [plugins]="plugins()" [eventBusConfig]="{
debug: true }" /> by removing "(when true)" or replacing it with a real trigger
(e.g., a boolean like isIdle or isDevMode) so the intent is clear and the defer
behavior for tanstack-devtools and plugins() is preserved.

In `@examples/angular/basic/src/app/devtools/custom-devtools-panel.ts`:
- Around line 27-28: The "// Automatically added" comment above the readonly
theme = input<string>() declaration is ambiguous; either replace it with a
clearer note that states who/what adds this input and when (e.g., "Added by
DevTools host at runtime" or "Injected by framework build step") or remove the
comment entirely if it's stale; update the comment adjacent to the readonly
theme = input<string>() symbol to explicitly state its source and purpose so
future readers understand why the input exists.

In `@examples/angular/panel/package.json`:
- Line 22: The dependency "@tanstack/devtools-event-client" is pinned to an
exact version "0.4.2" while other `@tanstack` packages in package.json use caret
ranges; update the version specifier for "@tanstack/devtools-event-client" to
use a caret range (e.g. "^0.4.2") to match the other `@tanstack/`* entries and
maintain consistent semver behavior across the project.

In `@examples/angular/panel/src/app/counter.ts`:
- Around line 10-28: increment and decrement both call
DevtoolsEventClient.emit('counter-state', { count: count(), history }) causing
duplicated emit logic; extract that into a small helper (e.g., emitCounterState
or notifyCounterState) that reads count() and history and calls
DevtoolsEventClient.emit('counter-state', payload), then replace the duplicated
emit calls in the increment and decrement functions with a single call to the
new helper; ensure the helper is colocated with the count/history variables and
used after updating history so payload stays correct.
- Around line 4-9: createCounter currently never emits its initial value so the
UI sees no state until the first increment/decrement; fix it by emitting the
initial state when the counter is created: after initializing count and history
in createCounter, record the initial value (e.g., push count() into history) and
invoke the same notifier/emit function you use for subsequent updates (or
expose/ call value() once) so the panel receives the initial state; update
references in createCounter, count, history and the notifier/emit handler
accordingly.

In `@examples/angular/panel/src/app/devtools/custom-devtools-panel.ts`:
- Around line 12-17: The render(count: number, history: string) function writes
unescaped HTML into plugin via plugin.innerHTML which static analysis flagged;
change render to avoid innerHTML by creating or selecting child elements under
the plugin container and setting their textContent (or concatenating safe
strings) to display count and history, e.g., create two divs or reuse existing
elements and assign their textContent using the plugin reference instead of
assigning plugin.innerHTML.

In `@examples/angular/with-devtools/.vscode/mcp.json`:
- Line 6: The current args entry ["-y", "@angular/cli", "mcp"] intentionally
uses an unpinned `@angular/cli` per Angular's MCP docs—leave it as-is to track
latest fixes; if you instead want reproducible behavior across the team, change
that args element to "@angular/cli@20" (i.e., replace "@angular/cli" with
"@angular/cli@20") so the MCP launch uses the pinned CLI version.

In `@packages/angular-devtools/package.json`:
- Around line 55-63: Remove the duplicate tslib entry by keeping it under
"dependencies" and deleting the "tslib" entry from "devDependencies" in
package.json; update the package.json object so only the top-level
"dependencies" contains "tslib" and ensure no other references to tslib remain
in dev tooling config (look for "tslib" in the package.json
dependencies/devDependencies blocks).
- Line 53: The build script in package.json ("build") uses Unix-only rm -rf;
replace those calls with a cross-platform remover such as rimraf (or reuse the
existing "premove" helper used by "clean") so Windows contributors won't fail;
update the "build" script to call rimraf on ./dist/package.json and
./dist/provider/package.json (or call the existing preremove command), and if
using rimraf add it to devDependencies and/or the scripts so CI and local
installs have the tool available.

In `@packages/angular-devtools/provider/with-devtools.ts`:
- Around line 86-89: The plugins array is only set once with
plugins.set(devtoolsOptions().plugins ?? []) and won't update when
devtoolsOptions().plugins changes; change this to a reactive effect that watches
devtoolsOptions().plugins and calls plugins.set(...) whenever it updates (or add
a comment if immutability is intentional). Specifically, replace the one-time
call to plugins.set with an effect/autorun that reads devtoolsOptions().plugins
and updates plugins via plugins.set(...) so it stays in sync with the computed
config/eventBusConfig used elsewhere.
- Around line 32-34: Tighten the loose Array<any> usages by replacing them with
a safer type (prefer unknown[] or make WithDevtoolsOptionsFn generic like <Args
extends any[]>(...args: Args) => TanStackDevtoolsAllOptions and use Args for
WithDevtoolsOptions.deps) so callers retain type information; if Angular DI
constraints prevent stronger typing, add a short JSDoc above
WithDevtoolsOptionsFn and the WithDevtoolsOptions.deps field explaining why any
is necessary and referencing the DI limitation. Ensure you update the
declarations for WithDevtoolsOptionsFn and the WithDevtoolsOptions.deps usage
(the symbols named WithDevtoolsOptionsFn and WithDevtoolsOptions.deps)
consistently.

In `@packages/devtools-utils/src/angular/panel.ts`:
- Around line 22-65: Add JSDoc and an explicit return type to the public
function createAngularPanel to document its purpose, the tuple structure, and
why the second tuple entry is () => null; update the function signature to
include a clear return type (e.g., readonly [ (inputs: () => TComponentProps,
host: HTMLElement) => () => void, () => null ]) and add a brief JSDoc block
above createAngularPanel describing what the factory returns, what the first
tuple function does (mount/unmount via CoreClass), and why the second is a stub;
reference the createAngularPanel function, the returned tuple and the second
element (() => null) when making these edits.

In `@packages/devtools-utils/vite.config.angular.ts`:
- Around line 5-7: The config object created via defineConfig currently includes
an unnecessary empty "plugins" property; remove the "plugins: []" entry from the
defineConfig call (the config constant) to clean up the Vite configuration
without changing behavior—locate the defineConfig({ ... }) block that assigns to
config and delete the plugins key.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/devtools-utils/src/angular/panel.ts (1)

17-22: Type guard may produce false positives for arrow functions with prototype properties.

The check !!o.prototype distinguishes class constructors from arrow functions (which lack .prototype), but it could incorrectly classify a regular function as a constructor since non-arrow functions also have .prototype. Consider a more robust check.

🔧 Suggested improvement
 function isPanelClassConstructor<
   TComponentProps extends DevtoolsPanelProps,
   TCoreDevtoolsClass extends BaseCorePanelClass,
 >(o: any): o is CoreClassConstructor<TComponentProps, TCoreDevtoolsClass> {
-  return !!o.prototype
+  return typeof o === 'function' && !!o.prototype && o.prototype.constructor === o
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools-utils/src/angular/panel.ts` around lines 17 - 22, The
isPanelClassConstructor type guard currently returns true for any function with
a .prototype and can misclassify plain functions; update isPanelClassConstructor
to more robustly detect ES class constructors (e.g., first confirm typeof o ===
'function' and then verify it's an ES class by checking
Function.prototype.toString.call(o) starts with "class " or by ensuring
o.prototype && o.prototype.constructor === o and that the prototype is
non-enumerable), so replace the simple !!o.prototype check in
isPanelClassConstructor (affecting CoreClassConstructor<TComponentProps,
TCoreDevtoolsClass>) with one of these stronger checks to avoid false positives
for regular functions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/devtools-utils/src/angular/panel.ts`:
- Around line 56-58: The promise chain that resolves CoreClass
(CoreClass().then((ResolvedCoreClass) => new
ResolvedCoreClass(inputs())).then(mount)) lacks error handling; add a
.catch(...) after .then(mount) to handle rejection from CoreClass or
construction, logging the error (e.g., console.error or the project logger) and
cleaning up the appended panel div (remove or unmount the element) to avoid
leaving a dangling DOM node; ensure the catch references the same panel element
used when appending so it can be removed on failure.
- Around line 37-63: The async branch can append the panel to the DOM then
resolve after cleanup runs, leaving an orphaned element and later mounting into
it; update the logic around CoreClass, mount, and unmount to track cancellation:
when entering the async path (the CoreClass() promise) capture a cancelled flag
(or an AbortSignal) so that the cleanup returned by the outer function sets
cancelled = true and removes the panel (panel.remove()), and the promise
continuation checks the flag before calling mount; also ensure that when mount
runs it still assigns unmount so the usual unmount?.() will work. This uses the
existing identifiers panel, mount, unmount, CoreClass, isPanelClassConstructor,
and host.appendChild to locate where to add the flag, the panel.remove() call in
the cleanup, and the pre-mount cancellation check.

---

Nitpick comments:
In `@packages/devtools-utils/src/angular/panel.ts`:
- Around line 17-22: The isPanelClassConstructor type guard currently returns
true for any function with a .prototype and can misclassify plain functions;
update isPanelClassConstructor to more robustly detect ES class constructors
(e.g., first confirm typeof o === 'function' and then verify it's an ES class by
checking Function.prototype.toString.call(o) starts with "class " or by ensuring
o.prototype && o.prototype.constructor === o and that the prototype is
non-enumerable), so replace the simple !!o.prototype check in
isPanelClassConstructor (affecting CoreClassConstructor<TComponentProps,
TCoreDevtoolsClass>) with one of these stronger checks to avoid false positives
for regular functions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c12aa529-65fd-41c9-8f8b-6a7f9d292feb

📥 Commits

Reviewing files that changed from the base of the PR and between 4ebeb9e and dd5f9c7.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • package.json
  • packages/devtools-utils/package.json
  • packages/devtools-utils/src/angular/panel.ts
  • packages/devtools-utils/src/angular/plugin.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • package.json
  • packages/devtools-utils/src/angular/plugin.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

♻️ Duplicate comments (1)
packages/devtools-utils/src/angular/panel.ts (1)

62-64: ⚠️ Potential issue | 🟡 Minor

Cleanup function should remove the panel element.

Even in the synchronous path, the cleanup function only calls unmount?.() but doesn't remove the panel div from the DOM, potentially leaving orphaned elements.

🐛 Proposed fix
         return () => {
           unmount?.()
+          panel.remove()
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools-utils/src/angular/panel.ts` around lines 62 - 64, The
cleanup returned by the panel mounting function currently only calls unmount?.()
and never removes the panel DOM node, leaving orphaned elements; update the
returned function so after calling unmount?.() it also removes the panel element
(e.g., check the panel variable and call panel.remove() or
parent.removeChild(panel) as appropriate), and guard against panel being
null/already removed so this works both in synchronous and async paths
(referencing the unmount and panel variables in the mount/cleanup closure).
🧹 Nitpick comments (7)
packages/devtools-a11y/package.json (1)

109-109: Minor formatting inconsistency.

Missing space after the colon for consistency with other peer dependency entries.

🧹 Optional formatting fix
-    "@angular/core":">=19.0.0",
+    "@angular/core": ">=19.0.0",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools-a11y/package.json` at line 109, The peerDependencies entry
for "@angular/core" has a missing space after the colon; open package.json and
in the peerDependencies object adjust the "@angular/core" entry (the string key
"@angular/core") so the value is formatted with a space after the colon (make it
consistent with other entries by changing ":"">=19.0.0"," to ": ">=19.0.0",).
packages/angular-devtools/src/types.ts (1)

30-35: Consider using Type<unknown> instead of Type<any> for consistency.

Line 32 uses Type<any> while lines 26-27 and 40 use Type<unknown>. Using unknown is generally safer as it forces explicit type narrowing. Consider aligning for consistency.

♻️ Suggested change for type consistency
 export type TanStackDevtoolsAngularPluginRender =
   | null
-  | Type<any>
+  | Type<unknown>
   | (() =>
       | TanStackDevtoolsAngularPluginRenderFn
       | Promise<TanStackDevtoolsAngularPluginRenderFn>)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/angular-devtools/src/types.ts` around lines 30 - 35, Replace the
inconsistent Type<any> usage in the TanStackDevtoolsAngularPluginRender type
with Type<unknown> to match the other declarations; update the union member in
the TanStackDevtoolsAngularPluginRender type so it uses Type<unknown> (keeping
the rest of the union, including the function return types
TanStackDevtoolsAngularPluginRenderFn and Promise<...>, unchanged).
examples/angular/a11y-devtools/README.md (1)

1-59: Consider adding a11y-devtools specific documentation.

The README contains standard Angular CLI instructions but lacks any information about the a11y-devtools integration or TanStack Devtools usage. Adding a brief section about the purpose of this example and how to use the a11y-devtools would improve the developer experience.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/a11y-devtools/README.md` around lines 1 - 59, The README
currently only has generic Angular CLI instructions; add an "About
a11y-devtools" section that briefly explains the purpose of this example
(demonstrates integrating a11y-devtools with TanStack Devtools), a short "How to
use" with steps referencing existing commands (e.g., run ng serve, open
http://localhost:4200), notes on where to find demo components/pages in the
example, and a small "Devtools usage" subsection that explains how to
open/enable the TanStack Devtools panel and the a11y-devtools accessibility
panel (and any optional config flags or environment variables the example
supports). Include a pointer to relevant docs (TanStack Devtools/a11y-devtools)
and mention the example name "a11y-devtools" so contributors can quickly locate
the integration code.
examples/angular/a11y-devtools/package.json (1)

20-22: Inconsistent version specifier for @tanstack/devtools-event-client.

Line 21 uses an exact version "0.4.2" while adjacent TanStack packages use caret ranges (^0.0.1, ^0.1.2). This inconsistency may cause unexpected version drift when other packages update.

Suggested fix
     "@tanstack/angular-devtools": "^0.0.1",
-    "@tanstack/devtools-event-client": "0.4.2",
+    "@tanstack/devtools-event-client": "^0.4.2",
     "@tanstack/devtools-a11y": "^0.1.2",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/a11y-devtools/package.json` around lines 20 - 22, The
package.json dependency for `@tanstack/devtools-event-client` uses an exact
version "0.4.2" while neighboring TanStack deps use caret ranges; update the
dependency entry for `@tanstack/devtools-event-client` to use a caret range (e.g.,
^0.4.2) to match the others (`@tanstack/angular-devtools` and
`@tanstack/devtools-a11y`) and ensure consistent semver behavior.
examples/angular/a11y-devtools/angular.json (1)

12-14: Consider renaming the project to reflect its purpose.

The project is named "basic" but this is the a11y-devtools example. While Angular workspaces are isolated, a more descriptive name like "a11y-devtools" would improve clarity and consistency with the example's directory name.

Suggested changes
   "projects": {
-    "basic": {
+    "a11y-devtools": {
       "projectType": "application",

Note: Also update buildTarget references on lines 61 and 64 from "basic:build:..." to "a11y-devtools:build:...".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/a11y-devtools/angular.json` around lines 12 - 14, Rename the
Angular project entry named "basic" under "projects" to "a11y-devtools" and
update any build target references that still point to "basic:build:..." to
"a11y-devtools:build:..." so the project name matches the example directory;
ensure you update the project name string and all occurrences of the buildTarget
values (e.g., "basic:build:...") to the new "a11y-devtools:build:..."
identifier.
examples/angular/a11y-devtools/src/app/counter.ts (1)

15-18: Consider passing a copy of the history array.

The history array is passed by reference in the emit payload. If the event handler stores this reference, subsequent mutations will affect the stored value unexpectedly. For example code this is likely fine, but passing a copy is safer.

Suggested fix
       DevtoolsEventClient.emit('counter-state', {
         count: count(),
-        history,
+        history: [...history],
       })

Apply the same change to both increment and decrement emit calls.

Also applies to: 24-27

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/a11y-devtools/src/app/counter.ts` around lines 15 - 18, Pass
a shallow copy of the history array instead of the original when emitting events
to avoid downstream mutation: update the DevtoolsEventClient.emit calls (used in
the increment and decrement flows) to send history.slice() (or [...history]) in
the payload alongside count(), so the emitted object contains an immutable
snapshot rather than a reference to the live history array.
packages/devtools-utils/src/angular/panel.ts (1)

18-23: Type guard relies solely on .prototype existence.

The isPanelClassConstructor check using !!o.prototype may return true for arrow functions or objects that happen to have a prototype property but are not constructors. Consider a more robust check.

♻️ Suggested improvement
 function isPanelClassConstructor<
   TComponentProps extends DevtoolsPanelProps,
   TCoreDevtoolsClass extends BaseCorePanelClass,
 >(o: any): o is CoreClassConstructor<TComponentProps, TCoreDevtoolsClass> {
-  return !!o.prototype
+  return typeof o === 'function' && !!o.prototype && o.prototype.constructor === o
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools-utils/src/angular/panel.ts` around lines 18 - 23, Replace
the fragile check in isPanelClassConstructor with a robust constructor test:
verify o is a function (typeof o === 'function'), that o.prototype exists, and
that o.prototype.constructor === o (or that o.prototype implements expected
instance members from BaseCorePanelClass). Update the function
isPanelClassConstructor to use these checks (referencing
isPanelClassConstructor, CoreClassConstructor, and BaseCorePanelClass) so it
rejects plain objects with a prototype property and non-constructable values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/angular/a11y-devtools/README.md`:
- Line 1: Replace the top-level Markdown title "Basic" in the README with a
title that matches the example directory name (e.g., "a11y-devtools" or
"a11y-devtools — Accessibility DevTools Example") so the README heading aligns
with the example; locate the header string "Basic" in the README.md and update
it accordingly.
- Line 3: Update the README line that claims Vitest is the test runner to
correctly state the project uses Angular's standard unit test setup; replace the
Vitest reference with a note that tests run via the npm test script (runs "ng
test") and that the project uses the Angular unit test builder
(`@angular/build`:unit-test) as shown in package.json (no Vitest dependency
present). Ensure the corrected sentence explicitly mentions "ng test" and
"@angular/build:unit-test" so readers aren’t misled.

In `@examples/angular/a11y-devtools/src/index.html`:
- Line 5: The page title currently reads "Basic" and should be updated to
reflect the example name; change the <title> element in index.html to a
meaningful name like "a11y-devtools" (or "a11y-devtools — Accessibility DevTools
Example") so the document title matches the project; locate the <title> tag in
index.html and replace its content accordingly.

In `@examples/angular/basic/src/app/app.ts`:
- Around line 7-10: The `@Component` decorator currently includes an imports
property (imports: [TanStackDevtools]) but is missing standalone: true, which is
required when using imports in component metadata; update the `@Component`
metadata for this component to add standalone: true alongside the existing
imports so the decorator is valid (i.e., modify the `@Component`({...}) that
contains imports: [TanStackDevtools] to include standalone: true).

In `@packages/angular-devtools/src/types.ts`:
- Around line 46-62: The JSDoc example incorrectly references a non-existent
`component` property for TanStackDevtoolsAngularPlugin; update the example to
use the actual `render` property defined on TanStackDevtoolsAngularPlugin
(replace `{ name: 'My Plugin', component: MyPluginComponent }` with `{ name: 'My
Plugin', render: MyPluginComponent }`) so the docs match the
`TanStackDevtoolsAngularPlugin` type and `render` usage.

In `@packages/devtools-a11y/src/angular/index.ts`:
- Around line 7-13: The module-level conditionals using isDevMode() (in exports
A11yDevtoolsPanel and a11yDevtoolsPlugin) won't be tree-shaken; replace the
runtime isDevMode() call with a build-time boolean (e.g. a __DEV__ or PROD
constant provided via environment/file-replacement/DefinePlugin) or convert the
exports to lazy accessor functions so evaluation happens at call time; update
the two exports (A11yDevtoolsPanel and a11yDevtoolsPlugin) to use that
build-time constant (or wrap them in functions like
getA11yDevtoolsPanel()/getA11yDevtoolsPlugin) and configure your Angular
file-replacement/webpack define plugin to supply the constant so production
builds can dead-code eliminate the no-op branches.

---

Duplicate comments:
In `@packages/devtools-utils/src/angular/panel.ts`:
- Around line 62-64: The cleanup returned by the panel mounting function
currently only calls unmount?.() and never removes the panel DOM node, leaving
orphaned elements; update the returned function so after calling unmount?.() it
also removes the panel element (e.g., check the panel variable and call
panel.remove() or parent.removeChild(panel) as appropriate), and guard against
panel being null/already removed so this works both in synchronous and async
paths (referencing the unmount and panel variables in the mount/cleanup
closure).

---

Nitpick comments:
In `@examples/angular/a11y-devtools/angular.json`:
- Around line 12-14: Rename the Angular project entry named "basic" under
"projects" to "a11y-devtools" and update any build target references that still
point to "basic:build:..." to "a11y-devtools:build:..." so the project name
matches the example directory; ensure you update the project name string and all
occurrences of the buildTarget values (e.g., "basic:build:...") to the new
"a11y-devtools:build:..." identifier.

In `@examples/angular/a11y-devtools/package.json`:
- Around line 20-22: The package.json dependency for
`@tanstack/devtools-event-client` uses an exact version "0.4.2" while neighboring
TanStack deps use caret ranges; update the dependency entry for
`@tanstack/devtools-event-client` to use a caret range (e.g., ^0.4.2) to match the
others (`@tanstack/angular-devtools` and `@tanstack/devtools-a11y`) and ensure
consistent semver behavior.

In `@examples/angular/a11y-devtools/README.md`:
- Around line 1-59: The README currently only has generic Angular CLI
instructions; add an "About a11y-devtools" section that briefly explains the
purpose of this example (demonstrates integrating a11y-devtools with TanStack
Devtools), a short "How to use" with steps referencing existing commands (e.g.,
run ng serve, open http://localhost:4200), notes on where to find demo
components/pages in the example, and a small "Devtools usage" subsection that
explains how to open/enable the TanStack Devtools panel and the a11y-devtools
accessibility panel (and any optional config flags or environment variables the
example supports). Include a pointer to relevant docs (TanStack
Devtools/a11y-devtools) and mention the example name "a11y-devtools" so
contributors can quickly locate the integration code.

In `@examples/angular/a11y-devtools/src/app/counter.ts`:
- Around line 15-18: Pass a shallow copy of the history array instead of the
original when emitting events to avoid downstream mutation: update the
DevtoolsEventClient.emit calls (used in the increment and decrement flows) to
send history.slice() (or [...history]) in the payload alongside count(), so the
emitted object contains an immutable snapshot rather than a reference to the
live history array.

In `@packages/angular-devtools/src/types.ts`:
- Around line 30-35: Replace the inconsistent Type<any> usage in the
TanStackDevtoolsAngularPluginRender type with Type<unknown> to match the other
declarations; update the union member in the TanStackDevtoolsAngularPluginRender
type so it uses Type<unknown> (keeping the rest of the union, including the
function return types TanStackDevtoolsAngularPluginRenderFn and Promise<...>,
unchanged).

In `@packages/devtools-a11y/package.json`:
- Line 109: The peerDependencies entry for "@angular/core" has a missing space
after the colon; open package.json and in the peerDependencies object adjust the
"@angular/core" entry (the string key "@angular/core") so the value is formatted
with a space after the colon (make it consistent with other entries by changing
":"">=19.0.0"," to ": ">=19.0.0",).

In `@packages/devtools-utils/src/angular/panel.ts`:
- Around line 18-23: Replace the fragile check in isPanelClassConstructor with a
robust constructor test: verify o is a function (typeof o === 'function'), that
o.prototype exists, and that o.prototype.constructor === o (or that o.prototype
implements expected instance members from BaseCorePanelClass). Update the
function isPanelClassConstructor to use these checks (referencing
isPanelClassConstructor, CoreClassConstructor, and BaseCorePanelClass) so it
rejects plain objects with a prototype property and non-constructable values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0279b535-5010-483f-be85-6fc186968d18

📥 Commits

Reviewing files that changed from the base of the PR and between dd5f9c7 and 0a9ac06.

⛔ Files ignored due to path filters (2)
  • examples/angular/a11y-devtools/public/favicon.ico is excluded by !**/*.ico
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (33)
  • examples/angular/a11y-devtools/.editorconfig
  • examples/angular/a11y-devtools/.gitignore
  • examples/angular/a11y-devtools/.prettierrc
  • examples/angular/a11y-devtools/.vscode/extensions.json
  • examples/angular/a11y-devtools/.vscode/launch.json
  • examples/angular/a11y-devtools/.vscode/mcp.json
  • examples/angular/a11y-devtools/.vscode/tasks.json
  • examples/angular/a11y-devtools/README.md
  • examples/angular/a11y-devtools/angular.json
  • examples/angular/a11y-devtools/package.json
  • examples/angular/a11y-devtools/src/app/app.config.ts
  • examples/angular/a11y-devtools/src/app/app.ts
  • examples/angular/a11y-devtools/src/app/counter.ts
  • examples/angular/a11y-devtools/src/app/devtools/custom-devtools-panel.ts
  • examples/angular/a11y-devtools/src/app/devtools/eventClient.ts
  • examples/angular/a11y-devtools/src/index.html
  • examples/angular/a11y-devtools/src/main.ts
  • examples/angular/a11y-devtools/src/styles.css
  • examples/angular/a11y-devtools/tsconfig.app.json
  • examples/angular/a11y-devtools/tsconfig.json
  • examples/angular/a11y-devtools/tsconfig.spec.json
  • examples/angular/basic/src/app/app.ts
  • examples/angular/panel/src/app/app.ts
  • packages/angular-devtools/src/types.ts
  • packages/devtools-a11y/package.json
  • packages/devtools-a11y/src/angular/A11yDevtools.ts
  • packages/devtools-a11y/src/angular/index.ts
  • packages/devtools-a11y/src/angular/plugin.ts
  • packages/devtools-a11y/src/angular/production.ts
  • packages/devtools-a11y/src/angular/production/A11yDevtools.ts
  • packages/devtools-a11y/src/angular/production/plugin.ts
  • packages/devtools-a11y/vite.config.ts
  • packages/devtools-utils/src/angular/panel.ts
✅ Files skipped from review due to trivial changes (4)
  • examples/angular/a11y-devtools/tsconfig.spec.json
  • examples/angular/a11y-devtools/src/styles.css
  • examples/angular/a11y-devtools/tsconfig.app.json
  • examples/angular/a11y-devtools/.vscode/extensions.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • examples/angular/panel/src/app/app.ts

@@ -0,0 +1,59 @@
# Basic
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Update the title to match the example name.

The title "Basic" doesn't match the directory name a11y-devtools. This appears to have been copied from another example without updating the title.

📝 Proposed fix
-# Basic
+# A11y Devtools
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Basic
# A11y Devtools
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/a11y-devtools/README.md` at line 1, Replace the top-level
Markdown title "Basic" in the README with a title that matches the example
directory name (e.g., "a11y-devtools" or "a11y-devtools — Accessibility DevTools
Example") so the README heading aligns with the example; locate the header
string "Basic" in the README.md and update it accordingly.

@@ -0,0 +1,59 @@
# Basic

This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.2.0.
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check Angular CLI version and test runner configuration

# Check package.json for Angular CLI version and test dependencies
cat examples/angular/a11y-devtools/package.json | jq '{
  devDependencies: .devDependencies | with_entries(select(.key | contains("angular") or contains("vitest") or contains("karma") or contains("jasmine") or contains("jest"))),
  scripts: .scripts | with_entries(select(.key == "test"))
}'

# Check angular.json for test configuration
if [ -f examples/angular/a11y-devtools/angular.json ]; then
  cat examples/angular/a11y-devtools/angular.json | jq '.projects[].architect.test'
fi

Repository: TanStack/devtools

Length of output: 280


🏁 Script executed:

cat examples/angular/a11y-devtools/README.md | head -n 50 | cat -n

Repository: TanStack/devtools

Length of output: 1529


Fix README: Remove incorrect Vitest reference in test runner documentation.

Line 41 incorrectly claims Vitest is the test runner, but the project is actually configured to use Angular's standard test setup (@angular/build:unit-test). The package.json contains no Vitest dependency and the test script runs ng test. Update line 41 to accurately reflect the actual testing configuration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/a11y-devtools/README.md` at line 3, Update the README line
that claims Vitest is the test runner to correctly state the project uses
Angular's standard unit test setup; replace the Vitest reference with a note
that tests run via the npm test script (runs "ng test") and that the project
uses the Angular unit test builder (`@angular/build`:unit-test) as shown in
package.json (no Vitest dependency present). Ensure the corrected sentence
explicitly mentions "ng test" and "@angular/build:unit-test" so readers aren’t
misled.

<html lang="en">
<head>
<meta charset="utf-8" />
<title>Basic</title>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Update the page title to reflect the example name.

The title is set to "Basic" but this is the a11y-devtools example. This appears to be leftover from copying the template.

Suggested fix
-    <title>Basic</title>
+    <title>A11y Devtools</title>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<title>Basic</title>
<title>A11y Devtools</title>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/a11y-devtools/src/index.html` at line 5, The page title
currently reads "Basic" and should be updated to reflect the example name;
change the <title> element in index.html to a meaningful name like
"a11y-devtools" (or "a11y-devtools — Accessibility DevTools Example") so the
document title matches the project; locate the <title> tag in index.html and
replace its content accordingly.

Comment on lines +7 to +10
@Component({
selector: 'app-root',
imports: [TanStackDevtools],
template: `
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read-only check: detect `@Component` blocks that use `imports` without `standalone: true`
python - <<'PY'
import re
import pathlib
import sys

violations = []
for path in pathlib.Path(".").rglob("*.ts"):
    text = path.read_text(encoding="utf-8", errors="ignore")
    for m in re.finditer(r'@Component\s*\(\s*\{', text):
        i = m.end()
        depth = 1
        while i < len(text) and depth > 0:
            if text[i] == '{':
                depth += 1
            elif text[i] == '}':
                depth -= 1
            i += 1
        block = text[m.end():i-1]
        has_imports = re.search(r'\bimports\s*:', block) is not None
        has_standalone_true = re.search(r'\bstandalone\s*:\s*true\b', block) is not None
        if has_imports and not has_standalone_true:
            line = text.count('\n', 0, m.start()) + 1
            violations.append(f"{path}:{line}")

if violations:
    print("Found components using `imports` without `standalone: true`:")
    print("\n".join(violations))
    sys.exit(1)

print("No violations found.")
PY

Repository: TanStack/devtools

Length of output: 512


🏁 Script executed:

cat -n examples/angular/basic/src/app/app.ts | head -15

Repository: TanStack/devtools

Length of output: 772


🏁 Script executed:

cat -n examples/angular/basic/src/app/app.ts | head -40

Repository: TanStack/devtools

Length of output: 1535


Add standalone: true when using imports in @Component.

Line 9 defines imports, but the decorator metadata does not set standalone: true. In Angular, this makes the imports property invalid and causes a TypeScript compilation error.

✅ Proposed fix
 `@Component`({
   selector: 'app-root',
+  standalone: true,
   imports: [TanStackDevtools],
   template: `
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Component({
selector: 'app-root',
imports: [TanStackDevtools],
template: `
`@Component`({
selector: 'app-root',
standalone: true,
imports: [TanStackDevtools],
template: `
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/angular/basic/src/app/app.ts` around lines 7 - 10, The `@Component`
decorator currently includes an imports property (imports: [TanStackDevtools])
but is missing standalone: true, which is required when using imports in
component metadata; update the `@Component` metadata for this component to add
standalone: true alongside the existing imports so the decorator is valid (i.e.,
modify the `@Component`({...}) that contains imports: [TanStackDevtools] to
include standalone: true).

Comment on lines +46 to +62
/**
* Array of plugins to be used in the devtools.
* Each plugin should have a `component` that is an Angular component class.
*
* Example:
* ```typescript
* @Component({
* template: `<tanstack-devtools [plugins]="plugins" />`,
* imports: [TanStackDevtoolsComponent],
* })
* class AppComponent {
* plugins: TanStackDevtoolsAngularPlugin[] = [
* { name: 'My Plugin', component: MyPluginComponent }
* ];
* }
* ```
*/
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

JSDoc example uses non-existent component property.

The example shows { name: 'My Plugin', component: MyPluginComponent } but TanStackDevtoolsAngularPlugin (lines 37-43) defines render, not component. This will mislead users.

📝 Proposed fix to correct the documentation
   * Example:
   * ```typescript
   * `@Component`({
   *   template: `<tanstack-devtools [plugins]="plugins" />`,
   *   imports: [TanStackDevtoolsComponent],
   * })
   * class AppComponent {
   *   plugins: TanStackDevtoolsAngularPlugin[] = [
-  *     { name: 'My Plugin', component: MyPluginComponent }
+  *     { name: 'My Plugin', render: MyPluginComponent }
   *   ];
   * }
   * ```
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/angular-devtools/src/types.ts` around lines 46 - 62, The JSDoc
example incorrectly references a non-existent `component` property for
TanStackDevtoolsAngularPlugin; update the example to use the actual `render`
property defined on TanStackDevtoolsAngularPlugin (replace `{ name: 'My Plugin',
component: MyPluginComponent }` with `{ name: 'My Plugin', render:
MyPluginComponent }`) so the docs match the `TanStackDevtoolsAngularPlugin` type
and `render` usage.

Comment on lines +7 to +13
export const A11yDevtoolsPanel = !isDevMode()
? Devtools.A11yDevtoolsPanelNoOp
: Devtools.A11yDevtoolsPanel

export const a11yDevtoolsPlugin = !isDevMode()
? plugin.a11yDevtoolsNoOpPlugin
: plugin.a11yDevtoolsPlugin
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Angular isDevMode() module evaluation time vs runtime

💡 Result:

isDevMode() is runtime, not “Angular compile-time”.

  • When it runs (module evaluation time): If you call isDevMode() at the top level of an ES module (outside any function/class), it executes when that module is imported and evaluated by the JS runtime (i.e., during app startup / chunk load). That’s still runtime—just early runtime.

  • What it checks: isDevMode() returns true unless the global ngDevMode is false / unset in a way that makes it falsy. Concretely, it’s implemented as typeof ngDevMode === 'undefined' || !!ngDevMode. enableProdMode() sets global['ngDevMode'] = false (but must happen before Angular is initialized). [1], [2]

  • Why it usually won’t tree-shake: Minifiers/tree-shakers can only reliably remove branches when they see a compile-time constant. A call like if (isDevMode()) { ... } is a function call, so it’s generally treated as needing a runtime value and won’t be dropped just because you built with prod optimizations. This is why if (!environment.production) { ... } (which can be inlined to false) often does get removed. [3]

  • Related: ngDevMode and build optimizers: In Angular CLI prod builds, ngDevMode can be replaced via optimizer/minifier global definitions, but that happens after Angular’s compilation/transforms—so ngDevMode is not something Angular can statically evaluate inside decorators/metadata (e.g., in @NgModule({ imports: ... })). If you need decorator-level conditional inclusion with tree-shaking, use a DefinePlugin-style compile-time constant (or other build-time configuration), not isDevMode() / ngDevMode. [4]

Rule of thumb

  • Use isDevMode() for runtime behavior toggles (logging, dev-only checks).
  • Use a build-time constant (file replacements / define) when you need dead-code elimination / conditional provider/module wiring. [3], [4]

Sources

  1. Angular isDevMode() API docs (notes CLI optimization / enableProdMode). [1]
  2. Angular core source for isDevMode() / enableProdMode() implementation. [2]
  3. Explanation of why isDevMode() conditions aren’t tree-shaken (function call vs inlined constant). [3]
  4. Explanation that ngDevMode is only replaced by minifier globals and isn’t statically usable in decorators. [4]

[1] (angular.dev)
[2] (raw.githubusercontent.com)
[3] (stackoverflow.com)
[4] (ng-guru.io)

Citations:


isDevMode() won't be tree-shaken in production builds; use a build-time constant instead.

While isDevMode() does execute at module evaluation time (early runtime), it's a function call that won't be eliminated during tree-shaking in production builds. For module-level conditional exports, Angular recommends using build-time constants (file replacements or DefinePlugin) to achieve proper dead-code elimination.

Consider either:

  1. Using a build-time environment constant similar to the React pattern
  2. Making the exports functions that lazily evaluate at call time
  3. Using Angular's file replacement or optimizer strategies for conditional module wiring
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools-a11y/src/angular/index.ts` around lines 7 - 13, The
module-level conditionals using isDevMode() (in exports A11yDevtoolsPanel and
a11yDevtoolsPlugin) won't be tree-shaken; replace the runtime isDevMode() call
with a build-time boolean (e.g. a __DEV__ or PROD constant provided via
environment/file-replacement/DefinePlugin) or convert the exports to lazy
accessor functions so evaluation happens at call time; update the two exports
(A11yDevtoolsPanel and a11yDevtoolsPlugin) to use that build-time constant (or
wrap them in functions like getA11yDevtoolsPanel()/getA11yDevtoolsPlugin) and
configure your Angular file-replacement/webpack define plugin to supply the
constant so production builds can dead-code eliminate the no-op branches.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants