Skip to content

injectors: fixes and improvements for the DI inspector #345

Closed

Description

@erkamyaman

Context

Fixes and improvements from a code audit of the di inspector, done in October 2026. Roadmap work lives in #340, #341, #342 and #343. Items marked (to confirm) were not reproduced; check them before fixing.

Problems

Medium

  • Element entries never refresh: readElement caches providers, dependencies and providedBy in elementEntries for the life of the element, so a dependency resolved later (lazy injector.get, or recorded after the profiler caught up) never shows up. Store a cheap version key (provider record count and dependency count) with the entry and re-read when it changes. Update the test "reads element providers and dependencies once per element" (packages/devtools/src/injector-tree.ts:308, :326-327, :377; packages/devtools/src/__tests__/injector-tree.test.ts:146).
  • Environment injectors off the rendered path are missing: noteEnvironment is only fed from entry.environments, so route injectors for routes not shown, createEnvironmentInjector() injectors and injectors kept alive after navigation never appear. Also discover them from the Router's loaded route configs (_injector / _loadedInjector) and label them "inactive" (packages/devtools/src/injector-tree.ts:390-415, :435).

Low

  • Service dependencies skip InjectionTokens: environmentDependencies only handles class tokens (typeof token !== 'function'), so useFactory InjectionToken providers never list what they inject. Handle created non-multi token records with a factory too, keeping the isCreated guard (packages/devtools/src/injector-tree.ts:291-292).
  • No validation on push-injector-tree: the handler casts roots and environment to InjectorTreeNode[] with no shape or size check, then fans them out to the panel and agents. Validate with a valibot schema, cap node count and string lengths, and drop malformed nodes (packages/devtools/src/devframe.ts:1492-1520).
  • Angular-native overlay keeps a failed push as sent: pushInjectorTree sets lastInjectorJson before push-injector-tree and has no try/catch, so a failed push is only retried after the keepalive. Reset it on failure as overlay.ts does, ideally through one shared helper for the three overlays (packages/devtools/src/overlay-angular-native.ts:280-282, packages/devtools/src/overlay.ts:319-323).
  • NativeScript overlay resends the whole tree on keepalive: it skips pingKnows('ping-injector-tree') and pushes the full report instead (packages/devtools/src/overlay-nativescript.ts:237, compare packages/devtools/src/overlay.ts:315).
  • Zone mode never shown: the collector reports zone and the server stores it, but the panel never displays it. Add a badge on the Root node or the toolbar (zoneless / zone.js / zone.js loaded but unused) (app/src/pages/di-inspector.ts, packages/devtools/src/devframe.ts:356).
  • Panel types drift from the package: the panel's own DependencyInfo has no providedByName and InjectorPage has no zone. Import the shared types so type checking catches drift (app/src/pages/di-inspector.ts:21-79, packages/devtools/src/types.ts:224, :241).
  • Hardcoded caps: the 2000 element cap is hardcoded in the panel text, and "Provided by" chips stop at 12 with no "+N more". Send the cap in the report and add a "+N more" chip (app/src/pages/di-inspector.ts:193, :1533).
  • Search results re-announced on every keystroke: the "Provided by" and no-match rows are role="status". Drop the role from the chip row and announce a debounced summary ("N injectors provide X") through the existing announcement signal (app/src/pages/di-inspector.ts:201, :212).
  • Tokens matched by name only: two tokens with the same class name or InjectionToken description (two Store classes, minified _a) are merged in token queries and panel search. Give tokens a stable WeakMap id and group or disambiguate by it (packages/devtools/src/injector-tree.ts:56-65, packages/devtools/src/rpc/injector-tools.ts:161-165).
  • Live view hides source data: once a live tree exists, the get-providers file:line data is no longer shown. Join live tokens to sourceProviders by name and show a file:line link in the Provides and Injected here lists (app/src/pages/di-inspector.ts:123, :477-560).
  • Unknown provide*() helpers are dropped: source mode maps helpers through the fixed PROVIDE_FN_TO_TOKEN table, so provideStore, provideFirestore and custom helpers are invisible. List unknown provide*() calls in providers arrays and appConfig as "provider function" entries (packages/devtools/src/rpc/get-providers.ts:60-76, :173).

Improvements

  • Draw the selected element's resolution path (element injectors up to environment injectors) as a vertical graph, highlighting where the queried token is found or where it would throw NullInjectorError (app/src/pages/di-inspector.ts).
  • Show orphaned providers: declared in source (get-providers) but never created at runtime, and services created in Root that nothing on the page injects (packages/devtools/src/rpc/get-providers.ts, packages/devtools/src/injector-tree.ts, app/src/pages/di-inspector.ts).
  • Show a warning count badge on the Injectors dock when an optional dependency resolves to "not provided anywhere" (packages/devtools/src/devframe.ts, app/src/pages/di-inspector.ts).

Already tracked elsewhere


Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or shows wrong datafeatureA feature request or a pull request that adds one

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions