angular-overlay-devtools
v0.1.8
Published
A Vue-DevTools-style component inspector for Angular — hover the page to highlight components, click to jump to their source. Built on Angular's dev-mode debug API (window.ng).
Maintainers
Readme
angular-overlay-devtools
A Vue-DevTools-style component inspector for Angular. Drop one component into your app, click the floating button (or press
Alt + Shift + C), then hover the page — every Angular component lights up with its box model, name and size. Click one to jump straight to its source in your editor.
- 🔍 Inspect, not overwhelm — no giant panel or tree, just the answer you reach for most: “which component is this, and where does it live?”
- 📦 Box-model overlay — margin / border / padding / content layers, just like Chrome and Vue DevTools.
- ⌨️ Keyboard navigation — walk the component tree with
↑/↓, freeze the highlight withSpace, cancel withEsc. - 🅰️ Angular-native — identifies components through Angular’s own dev-mode
debug API (
window.ng), not guesswork. - 🎯 Click to open in editor — jumps to the clicked element’s exact
template line (VS Code, Cursor, WebStorm…) via a tiny sidecar you start with
ng servein one command. - 🛡️ Shadow-DOM isolated — the UI lives in its own shadow root, so your app’s CSS can’t touch it and vice-versa.
- ⚡ Signals API — read
inspecting()/lastPick()as signals, or arm inspect mode from your own dev menu. - 🪶 Dev-only — enables itself only under
isDevMode(), safe to leave in.
Install
npm install -D angular-overlay-devtools
# or
pnpm add -D angular-overlay-devtools
# or
yarn add -D angular-overlay-devtoolsRequires Angular ≥ 17.3 (standalone components + the signal input() /
output() APIs) and a development build — window.ng is stripped from
production bundles, which is exactly when you don’t want the inspector anyway.
Quick start
Add the standalone component once, near your app root:
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { NgInspectorComponent } from 'angular-overlay-devtools';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, NgInspectorComponent],
template: `
<router-outlet />
<ng-inspector />
`,
})
export class App {}That’s it. A draggable reticle button appears (bottom-right). Click it or press
Alt + Shift + C, then hover the page. <ng-inspector /> renders nothing
into your app’s DOM and disables itself in production, so it is safe to leave
mounted.
Keyboard
| Key | Action |
| --- | --- |
| Alt + Shift + C | Toggle inspect mode |
| hover | Highlight the component under the cursor |
| click | Open the picked component in your editor |
| ↑ / ↓ | Walk to the parent / first child component |
| Space | Freeze / unfreeze the current highlight |
| Esc / right-click | Cancel inspect mode |
Inputs & outputs
<ng-inspector
[accent]="'#dd0031'"
[enabled]="true"
[openOnPick]="true"
(pick)="onPick($event)"
/>| Name | Type | Default | Description |
| --- | --- | --- | --- |
| accent | string | #dd0031 | Highlight & launcher colour. |
| enabled | boolean \| undefined | isDevMode() | Force the inspector on/off. |
| openOnPick | boolean | true | Open the source on click. |
| pick | EventEmitter<PickResult> | — | Fires with the picked component. |
Open in editor
Two tiny bin tools, both shipped with the package:
ng-inspector-manifest— scans your@Componentclasses into a component → file map (public/ng-inspector-manifest.json). Handles both external templates (templateUrl: './x.html') and inline ones (template: \…`): either way it records the file the clicked elements live in, so the jump lands on the exact line in the.html*or* the.ts`.ng-inspector-editor— a zero-dependency sidecar that, on click, reads the picked component's template, greps the clicked element's line, and launches your editor at it.
Git-ignore the manifest. It's regenerated on every
npm startand holds absolute, machine-specific paths — never commit it:public/ng-inspector-manifest.json
Wire both into ng serve so one command starts everything:
{
"scripts": {
"start": "ng-inspector-manifest & ng-inspector-editor & ng serve"
}
}Now npm start (or pnpm start) runs your dev server, generates the manifest,
and starts the sidecar together. Click a component → your editor opens at the
clicked element's exact line (VS Code / Cursor / WebStorm, auto-detected;
override with NG_INSPECTOR_EDITOR=cursor).
Why a sidecar and not
ng serve's own endpoint?ng servedoes expose Vite's/__open-in-editor, but it only opens afile:lineyou already know — it can't find the clicked element's line. React/Vue get element-level jumps because their compilers stamp source locations at build time (JSX__source, Vue'sdata-v-inspector); Angular's build gives plugins no hook to do that (verified). The sidecar fills the gap and scales — it reads only the one clicked template per click, nothing pre-computed or embedded. If a build step ever stampsdata-source-locon elements, the overlay reads it and skips the sidecar.
Signals API
Inject InspectorService anywhere to observe or drive the inspector with
signals — perfect for a custom dev menu or debug HUD:
import { Component, inject } from '@angular/core';
import { InspectorService } from 'angular-overlay-devtools';
@Component({
selector: 'dev-hud',
standalone: true,
template: `
<button (click)="inspector.toggle()">
{{ inspector.inspecting() ? 'Stop' : 'Inspect' }}
</button>
@if (inspector.lastPick(); as pick) {
<code><{{ pick.componentName }}></code>
}
`,
})
export class DevHud {
readonly inspector = inject(InspectorService);
}| Member | Type | Description |
| --- | --- | --- |
| inspecting | Signal<boolean> | True while inspect mode is armed. |
| lastPick | Signal<PickResult \| null> | The most recently picked component. |
| available | Signal<boolean> | Whether window.ng is present. |
| toggle() | () => void | Arm / disarm inspect mode. |
How it works
The inspector is three independent layers. Only one is Angular-specific — the rest is framework-neutral DOM work, so the internals read like a small, well-separated library.
| Layer | Folder | Angular-specific? |
| --- | --- | --- |
| Box-model geometry | box-model/ | No — pure DOM |
| Highlight + hint overlay | highlight/ | No |
| Draggable launcher button | launcher/ | No |
| Component identification | identification/ | Yes — window.ng |
| Open-in-editor | editor/, tools/ | No |
| Orchestration + Angular API | inspector/ | Yes |
Where React DevTools reads a DOM node’s fiber (__reactFiber$), this reads
Angular’s dev-mode debug API — getComponent, getOwningComponent,
getHostElement — through one narrow TargetResolver seam. Everything else
(the overlay, the box math, the editor wiring) knows nothing about Angular.
This project is the Angular counterpart of
react-inspect-overlay; the overlay and open-in-editor concepts are shared, the component identification is rebuilt on Angular’s runtime.
Limitations
- Line accuracy is heuristic. The sidecar greps the template for the clicked
element (tag + static
class/id+ a distinctive attribute value likesvgIcon="eye"), which pins the exact line for almost every element. Bare, repeated elements with no distinctive marker may land on the first match. 100% precision would need a build-timedata-source-locstamp — which Angular’s@angular/buildgives plugins no hook to add (verified: esbuildonLoadnever sees templates, the Angular compiler emits JS before bundling). The resolver already reads such a stamp if one is ever present — seeROADMAP.md. - Needs the sidecar running for open-in-editor (start it with
ng serve, as above). Inspecting/highlighting works with no server at all. - Dev builds only —
window.ngandisDevMode()gate everything off in production.
Contributing / building
npm install
npm run type-check # strict tsc, including noPropertyAccessFromIndexSignature
npm run build # ng-packagr → dist/ (Angular Package Format)
npm run release # build + npm publish ./distLicense
MIT © Qobiljon Jumaboyev
