ng-unused
v0.3.0
Published
Detect unused Angular components, services, modules, pipes, directives, routes and assets with dependency-graph accuracy
Maintainers
Readme
ng-unused
Detect unused and orphaned code in Angular workspaces — components, services, modules, directives, pipes, routes and assets — with dependency-graph accuracy, not text search.
ng-unused parses your TypeScript with ts-morph and your templates with the
real Angular compiler (@angular/compiler), builds a typed dependency graph,
and computes reachability from your application's actual entry points
(bootstrapApplication / bootstrapModule and root router registrations).
It understands:
- Standalone components and NgModules (Angular 15 – 21, including the implicit-standalone default of v19+)
- Templates: selector matching with Angular's own
SelectorMatcher, structural directives,@if/@for/@switchcontrol flow, pipes,routerLink - Dependency injection: constructor parameters,
inject(),@Inject,providers,providedIn: 'root' | Module - Routing:
provideRouter,RouterModule.forRoot/forChild,loadChildren,loadComponent, nestedchildren,redirectTo,router.navigate() - Dynamic imports and dynamic component creation (
createComponent,ComponentPortal, …) - tsconfig
baseUrl/pathsaliases - Assets: SCSS
@use/@importchains,url()refs, images, JSON, environment files andfileReplacements
A key design decision: registration is not usage. A component declared in
an NgModule (or listed in a standalone imports array) but never rendered,
routed, or referenced is reported as unused — with a reason explaining where
its registration must be removed.
Installation
npm install --save-dev ng-unused
# or run without installing
npx ng-unused analyzeRequires Node.js >= 18.19.
Usage
ng-unused analyze # analyze the workspace containing cwd
ng-unused analyze src/app # restrict reported findings to a sub-path
ng-unused component # components only
ng-unused service # services only
ng-unused module # NgModules only
ng-unused directive # directives only
ng-unused pipe # pipes only
ng-unused route # routes (effective paths, broken routes)
ng-unused route --delete-plan campaign/dashboard # safe-delete report
ng-unused graph # dependency graph as JSON
ng-unused graph -o graph.json # ... written to a file
ng-unused report # write unused-report.json + unused-report.html
ng-unused explain CardComponent # why is this used/unused?
ng-unused fix --dry-run # preview what could be deleted automatically
ng-unused fix # delete everything SAFE TO DELETE (git-clean check)
ng-unused fix --unused # also remove registration-only unused entities
ng-unused baseline # freeze current findings; CI fails only on NEW ones
ng-unused analyze --watch # re-analyze on save (incremental, cached)Fix mode
ng-unused fix turns findings into changes:
- deletes every file whose entities are all SAFE TO DELETE — including dead clusters (a component whose only referencer is a dead module goes with it) and the templates/styles/assets they exclusively own;
- with
--unused, additionally removes entities that are only registered (NgModuledeclarations/imports/exports) but never rendered or injected — editing the registering module and dropping the import for you; - never touches HIGH-risk findings (possible dynamic usage / public API);
- refuses to run when the workspace has uncommitted changes (
--forceto override), so every fix is onegit checkoutaway from being undone.
Always start with --dry-run, and build + test after applying.
Baseline (adopting on a legacy codebase)
ng-unused baseline # writes .ng-unused-baseline.json
git add .ng-unused-baseline.json
ng-unused analyze --baseline --fail-on-unused # CI: fails only on NEW findingsExisting findings are suppressed; anything introduced afterwards fails the build. Stale entries (findings you fixed) are reported so you can re-capture.
Global options
| Option | Effect |
| ------------------ | ------------------------------------------------------------- |
| --tsconfig <p> | tsconfig for path-alias resolution (auto-detected by default) |
| --json [file] | also write a JSON report (default unused-report.json) |
| --html [file] | also write an HTML report (default unused-report.html) |
| --fail-on-unused | exit code 1 when anything unused is found (CI gate) |
| --baseline [file]| suppress findings recorded in the baseline (see above) |
| --cache | incremental parse cache: only changed files are re-parsed |
| --show-used | list used entities too |
| --no-color | disable ANSI colors |
| --verbose / --quiet | logging level |
Exit codes
0 clean · 1 unused findings with --fail-on-unused · 2 tool error.
Example output
ng-unused v0.1.0 /work/my-app
312 files scanned in 1840 ms
Components 214 total 198 used, 9 unused, 7 safe to delete
Services 88 total 80 used, 5 unused, 3 safe to delete
...
Components
[UNUSED] OldBannerComponent risk:MEDIUM src/app/old-banner/old-banner.component.ts:5
Registered (declarations/imports/exports) in AppModule but never rendered or injected; ...
[SAFE TO DELETE] GhostComponent risk:LOW src/app/ghost/ghost.component.ts:6
No references found anywhere in the workspace.Every finding is one of USED, UNUSED, or SAFE TO DELETE, with a
risk level (LOW / MEDIUM / HIGH) and human-readable reasons. SAFE TO
DELETE means: zero references anywhere and no risk signals (no dynamic
creation, no module exports, no unparseable templates).
Safe delete engine
ng-unused route --delete-plan campaign/dashboardSafe delete report for 'campaign/dashboard' [risk: HIGH]
'campaign/dashboard' is still reachable from the application entry points; ...
Files to delete (4):
- src/app/campaign/dashboard/dashboard.component.ts
- src/app/campaign/dashboard/dashboard.component.html
- src/app/campaign/dashboard/dashboard.component.scss
- src/app/campaign/dashboard/dashboard.service.ts
Shared files to keep (2):
- src/app/shared/chart/chart.component.ts (also used by ReportsComponent)
- src/app/shared/table/table.component.ts (also used by AdminComponent)The plan is computed on the dependency graph: everything the target uses exclusively is deletable; anything live code outside the deletion set still references is listed as shared and kept.
HTML report
ng-unused report produces a fully self-contained unused-report.html
(no CDNs, works offline): summary dashboard, risk analysis, delete
recommendations, per-kind findings tables, routes with effective full paths,
assets, and an interactive dependency-graph view.
Programmatic API
import { WorkspaceAnalyzer, JsonReporter } from 'ng-unused';
const session = new WorkspaceAnalyzer().analyze({ rootDir: '/work/my-app' });
console.log(session.result.summary.components);
const plan = session.safeDelete.plan('campaign/dashboard');
new JsonReporter().write(session.result, 'unused-report.json');See docs/API.md for the full surface.
Architecture
Scanner → TypeScript Parser → Angular Template Parser
→ Dependency Graph Builder → Reference Engine
→ Risk Engine → Report GeneratorMonorepo packages, each with a single responsibility:
| Package | Responsibility |
| --------------------- | ---------------------------------------------------------- |
| @ng-unused/shared | types, constants, errors, logging, path utilities |
| @ng-unused/parser | ts-morph TypeScript parser + @angular/compiler templates |
| @ng-unused/graph | typed bidirectional dependency graph + builder |
| @ng-unused/analyzer | scanner, reference engine, risk engine, analyzers, planner |
| @ng-unused/reporter | console, JSON and HTML report generators |
| ng-unused (cli) | commander CLI |
Details in docs/ARCHITECTURE.md.
Performance
Designed for large workspaces (2000+ components, 5000+ files, 100+ lazy modules): every file is read once (shared file cache), the TypeScript project is syntax-only (no type checker, no lib files), import resolution is memoized, and the graph indexes both edge directions for O(degree) traversal.
Accuracy notes
Static analysis is conservative by design:
- Entities passed to dynamic-creation APIs are never marked safe to delete.
- Providers of live modules count as used even if never injected (token
indirection like
{ provide: X, useClass: Y }cannot always be traced). - Templates that fail to parse raise the risk of every unused component/directive/pipe, since usage could hide there.
Contributing
See CONTRIBUTING.md. Run npm install, npm run build,
npm test, npm run lint at the repo root; example apps in examples/ are
the integration fixtures.
License
MIT
