Prompt file imported from Techcrowd/copilot-angular-skillset (
.github/prompts/refactor-to-inject.prompt.md). Copyright stays with the author.
Refactor the selected file (or files in the selected folder) from constructor-based DI
to inject()-based DI.
Transformations
// BEFORE
export class ThingComponent {
constructor(
private readonly svc: ThingService,
private readonly router: Router,
@Inject(WINDOW) private readonly window: Window,
) {}
}
// AFTER
export class ThingComponent {
private readonly svc = inject(ThingService);
private readonly router = inject(Router);
private readonly window = inject(WINDOW);
}
Rules
- Field initializers must run in injection context (component/service/directive/pipe class body) — they do
- Mark all dependencies
private readonlyunless they're genuinely public - Remove the constructor entirely IF its only job was DI
- Keep the constructor IF it has additional logic (initialization, super() call, etc.) — but still move DI to fields
@Inject(TOKEN)becomesinject(TOKEN)@Optional() dep: Dep | nullbecomesinject(Dep, { optional: true })@Self(),@SkipSelf(),@Host()map to options:inject(Dep, { self: true })etc.- Order: alphabetical by token name for consistency
Edge cases
- Class-based guards/resolvers/interceptors: convert to functional (see
guards-resolvers.instructions.md) ErrorHandlersubclasses,APP_INITIALIZERfactories that need DI — can still useinject()in their function body- Tests using
TestBed.inject(...)are unaffected — only production class DI changes
Process
- List all classes with constructor DI in the target scope
- Convert one at a time
- Verify no behavior change (no extra logic moved/lost)
- Run TypeScript compile (or report what to run) to catch issues