Skillshub angular-di
Implement dependency injection in Angular v20+ using inject(), injection tokens, and provider configuration. Use for service architecture, providing dependencies at different levels, creating injectable tokens, and managing singleton vs scoped services. Triggers on service creation, configuring providers, using injection tokens, or understanding DI hierarchy.
install
source · Clone the upstream repo
git clone https://github.com/ComeOnOliver/skillshub
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/analogjs/angular-skills/angular-di" ~/.claude/skills/comeonoliver-skillshub-angular-di && rm -rf "$T"
manifest:
skills/analogjs/angular-skills/angular-di/SKILL.mdsource content
Angular Dependency Injection
Configure and use dependency injection in Angular v20+ with
inject() and providers.
Basic Injection
Using inject()
Prefer
inject() over constructor injection:
import { Component, inject } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { User } from './user.service'; @Component({ selector: 'app-user-list', template: `...`, }) export class UserList { // Inject dependencies private http = inject(HttpClient); private userService = inject(User); // Can use immediately users = this.userService.getUsers(); }
Injectable Services
import { Injectable, inject, signal } from '@angular/core'; import { HttpClient } from '@angular/common/http'; @Injectable({ providedIn: 'root', // Singleton at root level }) export class User { private http = inject(HttpClient); private users = signal<User[]>([]); readonly users$ = this.users.asReadonly(); async loadUsers() { const users = await firstValueFrom( this.http.get<User[]>('/api/users') ); this.users.set(users); } }
Provider Scopes
Root Level (Singleton)
// Recommended: providedIn @Injectable({ providedIn: 'root', }) export class Auth {} // Alternative: in app.config.ts export const appConfig: ApplicationConfig = { providers: [ Auth, ], };
Component Level (Instance per Component)
@Component({ selector: 'app-editor', providers: [EditorState], // New instance for each component template: `...`, }) export class Editor { private editorState = inject(EditorState); }
Route Level
export const routes: Routes = [ { path: 'admin', providers: [Admin], // Shared within this route tree children: [ { path: '', component: AdminDashboard }, { path: 'users', component: AdminUsers }, ], }, ];
Injection Tokens
Creating Tokens
import { InjectionToken } from '@angular/core'; // Simple value token export const API_URL = new InjectionToken<string>('API_URL'); // Object token export interface AppConfig { apiUrl: string; features: { darkMode: boolean; analytics: boolean; }; } export const APP_CONFIG = new InjectionToken<AppConfig>('APP_CONFIG'); // Token with factory (self-providing) export const WINDOW = new InjectionToken<Window>('Window', { providedIn: 'root', factory: () => window, }); export const LOCAL_STORAGE = new InjectionToken<Storage>('LocalStorage', { providedIn: 'root', factory: () => localStorage, });
Providing Token Values
// app.config.ts export const appConfig: ApplicationConfig = { providers: [ { provide: API_URL, useValue: 'https://api.example.com' }, { provide: APP_CONFIG, useValue: { apiUrl: 'https://api.example.com', features: { darkMode: true, analytics: true }, }, }, ], };
Injecting Tokens
@Injectable({ providedIn: 'root' }) export class Api { private apiUrl = inject(API_URL); private config = inject(APP_CONFIG); private window = inject(WINDOW); getBaseUrl(): string { return this.apiUrl; } }
Provider Types
useClass
// Provide implementation { provide: Logger, useClass: ConsoleLogger } // Conditional implementation { provide: Logger, useClass: environment.production ? ProductionLogger : ConsoleLogger, }
useValue
// Static values { provide: API_URL, useValue: 'https://api.example.com' } // Configuration objects { provide: APP_CONFIG, useValue: { theme: 'dark', language: 'en' } }
useFactory
// Factory with dependencies { provide: User, useFactory: (http: HttpClient, config: AppConfig) => { return new User(http, config.apiUrl); }, deps: [HttpClient, APP_CONFIG], } // Async factory (not recommended - use provideAppInitializer) { provide: CONFIG, useFactory: () => fetch('/config.json').then(r => r.json()), }
useExisting
// Alias to existing provider { provide: AbstractLogger, useExisting: ConsoleLogger } // Multiple tokens pointing to same instance providers: [ ConsoleLogger, { provide: Logger, useExisting: ConsoleLogger }, { provide: ErrorLogger, useExisting: ConsoleLogger }, ]
Injection Options
Optional Injection
@Component({...}) export class My { // Returns null if not provided private analytics = inject(Analytics, { optional: true }); trackEvent(name: string) { this.analytics?.track(name); } }
Self, SkipSelf, Host
@Component({ providers: [Local], }) export class Parent { // Only look in this component's injector private local = inject(Local, { self: true }); } @Component({...}) export class Child { // Skip this component, look in parent private parentService = inject(ParentSvc, { skipSelf: true }); // Only look up to host component private hostService = inject(Host, { host: true }); }
Multi Providers
Collect multiple values for same token:
// Token for multiple validators export const VALIDATORS = new InjectionToken<Validator[]>('Validators'); // Provide multiple values providers: [ { provide: VALIDATORS, useClass: RequiredValidator, multi: true }, { provide: VALIDATORS, useClass: EmailValidator, multi: true }, { provide: VALIDATORS, useClass: MinLengthValidator, multi: true }, ] // Inject as array @Injectable() export class Validation { private validators = inject(VALIDATORS); // Validator[] validate(value: string): ValidationError[] { return this.validators .map(v => v.validate(value)) .filter(Boolean); } }
HTTP Interceptors (Multi Provider)
// Interceptors use multi providers internally export const appConfig: ApplicationConfig = { providers: [ provideHttpClient( withInterceptors([ authInterceptor, loggingInterceptor, errorInterceptor, ]) ), ], };
App Initializers
Run async code before app starts using
provideAppInitializer:
import { provideAppInitializer, inject } from '@angular/core'; export const appConfig: ApplicationConfig = { providers: [ Config, provideAppInitializer(() => { const configService = inject(Config); return configService.loadConfig(); }), ], };
Multiple Initializers
providers: [ provideAppInitializer(() => { const config = inject(Config); return config.load(); }), provideAppInitializer(() => { const auth = inject(Auth); return auth.checkSession(); }), ]
Environment Injector
Create injectors programmatically:
import { createEnvironmentInjector, EnvironmentInjector, inject } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class Plugin { private parentInjector = inject(EnvironmentInjector); loadPlugin(providers: Provider[]): EnvironmentInjector { return createEnvironmentInjector(providers, this.parentInjector); } }
runInInjectionContext
Run code with injection context:
import { runInInjectionContext, EnvironmentInjector, inject } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class Utility { private injector = inject(EnvironmentInjector); executeWithDI<T>(fn: () => T): T { return runInInjectionContext(this.injector, fn); } } // Usage utilityService.executeWithDI(() => { const http = inject(HttpClient); // Use http... });
For advanced patterns, see references/di-patterns.md.