Marketplace
angular-dependency-injection
Use when building modular Angular applications requiring dependency injection with providers, injectors, and services.
allowed_tools: Bash, Read
$ Installieren
git clone https://github.com/TheBushidoCollective/han /tmp/han && cp -r /tmp/han/jutsu/jutsu-angular/skills/angular-dependency-injection ~/.claude/skills/han// tip: Run this command in your terminal to install the skill
SKILL.md
name: angular-dependency-injection description: Use when building modular Angular applications requiring dependency injection with providers, injectors, and services. allowed-tools:
- Bash
- Read
Angular Dependency Injection
Master Angular's dependency injection system for building modular, testable applications with proper service architecture.
DI Fundamentals
Angular's DI is hierarchical and uses decorators and providers:
import { Injectable } from '@angular/core';
// Service injectable at root level
@Injectable({
providedIn: 'root'
})
export class UserService {
private users: User[] = [];
getUsers(): User[] {
return this.users;
}
addUser(user: User): void {
this.users.push(user);
}
}
// Component injection
import { Component } from '@angular/core';
@Component({
selector: 'app-user-list',
template: `
<div *ngFor="let user of users">
{{ user.name }}
</div>
`
})
export class UserListComponent {
users: User[];
constructor(private userService: UserService) {
this.users = this.userService.getUsers();
}
}
Provider Types
useClass - Class Provider
import { Injectable, Provider } from '@angular/core';
// Interface
interface Logger {
log(message: string): void;
}
// Implementations
@Injectable()
export class ConsoleLogger implements Logger {
log(message: string): void {
console.log(message);
}
}
@Injectable()
export class FileLogger implements Logger {
log(message: string): void {
// Write to file
}
}
// Provider configuration
const loggerProvider: Provider = {
provide: Logger,
useClass: ConsoleLogger // or FileLogger based on env
};
// In module
@NgModule({
providers: [loggerProvider]
})
export class AppModule {}
// Usage
export class MyComponent {
constructor(private logger: Logger) {
this.logger.log('Component initialized');
}
}
useValue - Value Provider
import { InjectionToken } from '@angular/core';
// Configuration object
export interface AppConfig {
apiUrl: string;
timeout: number;
retries: number;
}
export const APP_CONFIG = new InjectionToken<AppConfig>('app.config');
// Provider
const configProvider: Provider = {
provide: APP_CONFIG,
useValue: {
apiUrl: 'https://api.example.com',
timeout: 5000,
retries: 3
}
};
// Module
@NgModule({
providers: [configProvider]
})
export class AppModule {}
// Usage
export class ApiService {
constructor(@Inject(APP_CONFIG) private config: AppConfig) {
console.log(this.config.apiUrl);
}
}
useFactory - Factory Provider
import { Injectable, InjectionToken } from '@angular/core';
export const API_URL = new InjectionToken<string>('api.url');
// Factory function
export function apiUrlFactory(config: AppConfig): string {
return config.production
? 'https://api.prod.example.com'
: 'https://api.dev.example.com';
}
// Provider
const apiUrlProvider: Provider = {
provide: API_URL,
useFactory: apiUrlFactory,
deps: [AppConfig] // Dependencies for factory
};
// Complex factory with multiple deps
export function httpClientFactory(
handler: HttpHandler,
config: AppConfig,
logger: Logger
): HttpClient {
logger.log('Creating HTTP client');
return new HttpClient(handler);
}
const httpClientProvider: Provider = {
provide: HttpClient,
useFactory: httpClientFactory,
deps: [HttpHandler, AppConfig, Logger]
};
useExisting - Alias Provider
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class NewLogger {
log(message: string): void {
console.log('[NEW]', message);
}
}
// Alias old logger to new logger
const oldLoggerProvider: Provider = {
provide: 'OldLogger',
useExisting: NewLogger
};
// Usage
export class MyComponent {
constructor(
@Inject('OldLogger') private logger: NewLogger
) {
this.logger.log('Using aliased logger');
}
}
Injection Tokens
InjectionToken - Type-Safe Tokens
import { InjectionToken } from '@angular/core';
// Primitive token
export const MAX_RETRIES = new InjectionToken<number>('max.retries', {
providedIn: 'root',
factory: () => 3 // Default value
});
// Object token
export interface FeatureFlags {
enableNewUI: boolean;
enableBeta: boolean;
}
export const FEATURE_FLAGS = new InjectionToken<FeatureFlags>(
'feature.flags',
{
providedIn: 'root',
factory: () => ({
enableNewUI: false,
enableBeta: false
})
}
);
// Usage
@Injectable()
export class ApiService {
constructor(
@Inject(MAX_RETRIES) private maxRetries: number,
@Inject(FEATURE_FLAGS) private flags: FeatureFlags
) {}
}
String Tokens (Legacy)
// Not type-safe, avoid when possible
const providers: Provider[] = [
{ provide: 'API_URL', useValue: 'https://api.example.com' },
{ provide: 'TIMEOUT', useValue: 5000 }
];
// Usage
export class MyService {
constructor(
@Inject('API_URL') private apiUrl: string,
@Inject('TIMEOUT') private timeout: number
) {}
}
Hierarchical Injectors
Root Injector
// Singleton across entire app
@Injectable({
providedIn: 'root'
})
export class GlobalService {
private state = {};
}
// Same instance everywhere
Module Injector
@Injectable()
export class ModuleService {
// Service specific to module
}
@NgModule({
providers: [ModuleService]
})
export class FeatureModule {}
// Different instance per module
Component Injector
@Injectable()
export class ComponentService {
private data = [];
}
@Component({
selector: 'app-my-component',
template: '...',
providers: [ComponentService] // New instance per component
})
export class MyComponent {
constructor(private service: ComponentService) {}
}
// Each component instance gets its own service instance
Element Injector
@Directive({
selector: '[appHighlight]',
providers: [DirectiveService]
})
export class HighlightDirective {
constructor(private service: DirectiveService) {}
}
// Each directive instance gets its own service
ProvidedIn Options
// Root - singleton
@Injectable({
providedIn: 'root'
})
export class RootService {}
// Platform - shared across multiple apps
@Injectable({
providedIn: 'platform'
})
export class PlatformService {}
// Any - new instance per module
@Injectable({
providedIn: 'any'
})
export class AnyService {}
// Module - specific module
@Injectable({
providedIn: FeatureModule
})
export class FeatureService {}
Multi-Providers
import { InjectionToken } from '@angular/core';
// Token for multiple providers
export const HTTP_INTERCEPTORS =
new InjectionToken<HttpInterceptor[]>('http.interceptors');
// Multiple implementations
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler) {
// Add auth header
return next.handle(req);
}
}
@Injectable()
export class LoggingInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler) {
// Log request
return next.handle(req);
}
}
// Register as multi-providers
const providers: Provider[] = [
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
},
{
provide: HTTP_INTERCEPTORS,
useClass: LoggingInterceptor,
multi: true
}
];
// Inject as array
export class HttpService {
constructor(
@Inject(HTTP_INTERCEPTORS) private interceptors: HttpInterceptor[]
) {
// interceptors is array of all registered interceptors
}
}
Optional and Self Decorators
@Optional - Allow Missing Dependencies
import { Optional } from '@angular/core';
@Injectable()
export class MyService {
constructor(
@Optional() private logger?: Logger
) {
// logger might be undefined
this.logger?.log('Service created');
}
}
@Self - Only Current Injector
import { Self } from '@angular/core';
@Component({
selector: 'app-my-component',
providers: [LocalService]
})
export class MyComponent {
constructor(
@Self() private local: LocalService // Only from this component
) {}
}
@SkipSelf - Skip Current Injector
import { SkipSelf } from '@angular/core';
@Component({
selector: 'app-child',
providers: [SharedService]
})
export class ChildComponent {
constructor(
@SkipSelf() private parent: SharedService // From parent, not self
) {}
}
@Host - Host Element Injector
import { Host } from '@angular/core';
@Directive({
selector: '[appChild]'
})
export class ChildDirective {
constructor(
@Host() private parent: ParentComponent // From host component
) {}
}
ForRoot and ForChild Patterns
import { NgModule, ModuleWithProviders } from '@angular/core';
@NgModule({})
export class SharedModule {
// For root module - configures services
static forRoot(config: SharedConfig): ModuleWithProviders<SharedModule> {
return {
ngModule: SharedModule,
providers: [
SharedService,
{
provide: SHARED_CONFIG,
useValue: config
}
]
};
}
// For feature modules - no service providers
static forChild(): ModuleWithProviders<SharedModule> {
return {
ngModule: SharedModule,
providers: [] // No providers, use root services
};
}
}
// Usage in AppModule
@NgModule({
imports: [
SharedModule.forRoot({ apiUrl: 'https://api.example.com' })
]
})
export class AppModule {}
// Usage in feature module
@NgModule({
imports: [
SharedModule.forChild()
]
})
export class FeatureModule {}
Tree-Shakable Providers
// Traditional (not tree-shakable)
@Injectable()
export class OldService {}
@NgModule({
providers: [OldService]
})
export class AppModule {}
// Tree-shakable (preferred)
@Injectable({
providedIn: 'root'
})
export class NewService {}
// No need to register in module
// Service is removed if never injected
Testing with DI
TestBed Provider Overrides
import { TestBed } from '@angular/core/testing';
describe('MyComponent', () => {
let mockUserService: jasmine.SpyObj<UserService>;
beforeEach(() => {
mockUserService = jasmine.createSpyObj('UserService', ['getUsers']);
TestBed.configureTestingModule({
declarations: [MyComponent],
providers: [
{ provide: UserService, useValue: mockUserService }
]
});
});
it('should get users', () => {
mockUserService.getUsers.and.returnValue([]);
const fixture = TestBed.createComponent(MyComponent);
// Test component with mock
});
});
Spy on Dependencies
import { TestBed } from '@angular/core/testing';
describe('UserService', () => {
let service: UserService;
let httpMock: jasmine.SpyObj<HttpClient>;
beforeEach(() => {
httpMock = jasmine.createSpyObj('HttpClient', ['get', 'post']);
TestBed.configureTestingModule({
providers: [
UserService,
{ provide: HttpClient, useValue: httpMock }
]
});
service = TestBed.inject(UserService);
});
it('should fetch users', () => {
httpMock.get.and.returnValue(of([]));
service.getUsers().subscribe();
expect(httpMock.get).toHaveBeenCalled();
});
});
When to Use This Skill
Use angular-dependency-injection when building modern, production-ready applications that require:
- Modular service architecture
- Testable components and services
- Configuration management
- Plugin/extension systems
- Multi-provider patterns (interceptors, validators)
- Complex service hierarchies
- Lazy-loaded module isolation
- Tree-shakable code
Angular DI Best Practices
- Use
providedIn: 'root'- Tree-shakable and singleton - Use InjectionToken - Type-safe over string tokens
- Favor composition - Inject small, focused services
- Use factories for complex creation - useFactory for dynamic values
- Test with mocks - Override providers in TestBed
- Follow forRoot/forChild pattern - For shared modules
- Use @Optional sparingly - Prefer defaults or required deps
- Document multi-providers - Clear contract for extensions
- Avoid circular dependencies - Refactor to common service
- Use providedIn module - For module-specific services
DI Pitfalls and Gotchas
- Circular dependencies - A depends on B, B depends on A
- Providing in component - Creates new instance per component
- Missing providers - Runtime error if not provided
- Wrong injector level - Service not found in hierarchy
- Forgetting multi: true - Overrides instead of adding
- String token collisions - Use InjectionToken instead
- Not using forRoot - Multiple service instances
- Providing eagerly - Use providedIn for tree-shaking
- Testing without mocks - Real dependencies in tests
- Complex factory deps - Hard to test and maintain
Advanced DI Patterns
Service with Configuration
import { Injectable, Inject, InjectionToken } from '@angular/core';
export interface ApiConfig {
baseUrl: string;
timeout: number;
}
export const API_CONFIG = new InjectionToken<ApiConfig>('api.config');
@Injectable({
providedIn: 'root'
})
export class ApiService {
constructor(@Inject(API_CONFIG) private config: ApiConfig) {}
get(endpoint: string) {
return fetch(`${this.config.baseUrl}/${endpoint}`, {
signal: AbortSignal.timeout(this.config.timeout)
});
}
}
// Module
@NgModule({
providers: [
{
provide: API_CONFIG,
useValue: {
baseUrl: 'https://api.example.com',
timeout: 5000
}
}
]
})
export class AppModule {}
Abstract Class Provider
import { Injectable } from '@angular/core';
// Abstract class
export abstract class DataService<T> {
abstract get(id: string): Observable<T>;
abstract save(item: T): Observable<T>;
}
// Implementation
@Injectable()
export class UserDataService implements DataService<User> {
get(id: string): Observable<User> {
// Implementation
}
save(user: User): Observable<User> {
// Implementation
}
}
// Provider
@NgModule({
providers: [
{ provide: DataService, useClass: UserDataService }
]
})
export class FeatureModule {}
// Usage
export class MyComponent {
constructor(private dataService: DataService<User>) {}
}
Conditional Provider
import { Injectable, InjectionToken } from '@angular/core';
import { environment } from './environments/environment';
@Injectable()
export class DevLogger {
log(message: string) {
console.log('[DEV]', message);
}
}
@Injectable()
export class ProdLogger {
log(message: string) {
// Send to logging service
}
}
// Factory chooses implementation
export function loggerFactory(): Logger {
return environment.production
? new ProdLogger()
: new DevLogger();
}
const loggerProvider: Provider = {
provide: Logger,
useFactory: loggerFactory
};
Scope Isolation
// Parent service
@Injectable({
providedIn: 'root'
})
export class GlobalState {
count = 0;
}
// Child service (isolated)
@Injectable()
export class LocalState {
count = 0; // Independent per component
}
@Component({
selector: 'app-counter',
providers: [LocalState] // New instance per component
})
export class CounterComponent {
constructor(
public global: GlobalState,
public local: LocalState
) {}
incrementGlobal() {
this.global.count++; // Affects all components
}
incrementLocal() {
this.local.count++; // Only this component
}
}
Resources
Repository

TheBushidoCollective
Author
TheBushidoCollective/han/jutsu/jutsu-angular/skills/angular-dependency-injection
47
Stars
5
Forks
Updated4d ago
Added5d ago