1. Injection Context - Complete Overview
graph TD
A[Injection Context] --> B{Context Types}
B --> C[Constructor Context]
B --> D[Field Initializer Context]
B --> E[Factory Function Context]
B --> F[Guard/Resolver Context]
B --> T[runInInjectionContext]
C --> G[Component Constructor]
C --> H[Service Constructor]
C --> I[Directive Constructor]
D --> J[Class Fields]
D --> K[Property Declaration]
E --> L[useFactory Provider]
E --> M[InjectionToken Factory]
F --> N[Route Guards]
F --> O[Route Resolvers]
G --> P[inject Function Available]
H --> P
I --> P
J --> P
K --> P
L --> P
M --> P
N --> P
O --> P
T --> P
P --> Q{Valid Context?}
Q -->|Yes| R[Service Instance Returned]
Q -->|No| S[Error: inject requires context]
💡 Key Point: The inject() function can only be called in an injection context.
This includes constructors, field initializers, factory functions, and functional guards and resolvers. Lifecycle hooks such as ngOnInit are not injection contexts: call inject() in a field or the constructor, or use runInInjectionContext.
2. Service Creation & Registration Flow
graph TD
A[Define Service Class] --> B{Injectable Decorator}
B --> C[providedIn Option]
C --> D{Scope Type?}
D -->|root| E[Root Injector
Application-wide Singleton]
D -->|platform| F[Platform Injector
Shared across apps]
D -->|any| G[Deprecated
Do Not Use]
D -->|null| H[Must Provide Manually]
E --> I[Tree-shakeable]
F --> I
H --> J[Not Tree-shakeable]
G --> I
I --> K[Service Ready]
J --> K
K --> L{How to Provide?}
L -->|bootstrapApplication| M[Global Providers Array]
L -->|Component| N[Component Providers]
L -->|Route| O[Route Providers]
M --> P[Application Scope]
N --> Q[Component Tree Scope]
O --> R[Route Tree Scope]
P --> S[Service Instance Created on First Use]
Q --> S
R --> S
Service Definition Examples:
@Injectable({
providedIn: 'root'
})
export class UserService {
private users = signal<User[]>([]);
constructor() {
console.log('UserService created - singleton instance');
}
}
@Injectable()
export class FormStateService {
private formData = signal({});
}
@Component({
selector: 'app-user-form',
providers: [FormStateService],
template: '...'
})
export class UserFormComponent {}
export const userRoutes: Routes = [
{
path: 'users',
providers: [UserRouteService],
loadComponent: () => import('./user.component')
}
];
3. Service Resolution & Injector Hierarchy
graph TB
A[Component Requests Service
inject MyService] --> B{Check Component Injector}
B -->|Found| C[Return Instance]
B -->|Not Found| D{Check Parent Component}
D -->|Found| E[Return Instance]
D -->|Not Found| F{Check Route Injector}
F -->|Found| G[Return Instance]
F -->|Not Found| H{Check Root Injector}
H -->|Found| I[Return Instance]
H -->|Not Found| J{Check Platform Injector}
J -->|Found| K[Return Instance]
J -->|Not Found| L[NullInjectorError]
C --> M[Use Service]
E --> M
G --> M
I --> M
K --> M
M --> N{Service Exists?}
N -->|No| O[Create New Instance]
N -->|Yes| P[Return Cached Instance]
O --> Q[Call Constructor]
Q --> R[Resolve Dependencies]
R --> S[Cache Instance]
S --> T[Return to Component]
P --> T
⚠️ Important: The injector hierarchy determines service scope and lifetime.
Services are cached at the injector level where they're provided.
4. Component & Service Integration Flow
graph TD
A[Component Creation] --> B[Constructor Phase]
B --> C[Injection Context Active]
C --> D[inject Function Calls]
D --> E[Service 1: inject UserService]
D --> F[Service 2: inject HttpClient]
D --> G[Service 3: inject Custom]
E --> H[Resolve from Injector]
F --> H
G --> H
H --> I{Instance Exists?}
I -->|Yes| J[Return Cached]
I -->|No| K[Create New]
K --> L[Service Constructor]
L --> M[Service Dependencies]
M --> N[Service Ready]
J --> O[All Services Injected]
N --> O
O --> P[Component Fields Init]
P --> Q[Signals Setup]
Q --> R[Effects Setup]
R --> S[ngOnInit]
S --> T[Component Ready]
T --> U[Service Methods Available]
U --> V[Component Lifecycle]
Complete Integration Example:
@Component({
selector: 'app-user-list',
template: `
<div class="user-list">
@if (loading()) {
<div>Loading users...</div>
}
@for (user of users(); track user.id) {
<div class="user-card">
{{ user.name }} - {{ user.role }}
</div>
}
<button (click)="refreshUsers()">Refresh</button>
</div>
`
})
export class UserListComponent {
private userService = inject(UserService);
private http = inject(HttpClient);
private router = inject(Router);
users = signal<User[]>([]);
loading = signal(false);
totalUsers = computed(() => this.users().length);
constructor() {
const destroyRef = inject(DestroyRef);
effect(() => {
const serviceUsers = this.userService.getUsers();
this.users.set(serviceUsers);
});
destroyRef.onDestroy(() => {
console.log('Component cleanup');
});
}
async refreshUsers() {
this.loading.set(true);
try {
await this.userService.loadUsers();
} finally {
this.loading.set(false);
}
}
}
5. Advanced Injection Patterns
graph TD
A[Advanced DI Patterns] --> B[Factory Providers]
A --> C[InjectionToken]
A --> D[Multi Providers]
A --> E[Optional/Self/SkipSelf]
B --> F[useFactory Function]
F --> G[Dynamic Service Creation]
C --> H[Type-safe Config]
H --> I[APP_CONFIG Token]
D --> J[Multiple Instances]
J --> K[HTTP_INTERCEPTORS]
E --> L[Decorator Modifiers]
E --> L2[inject Options]
L2 --> L3[optional, self, skipSelf, host]
L --> M[@Optional]
L --> N[@Self]
L --> O[@SkipSelf]
L --> P[@Host]
G --> Q[Conditional Logic]
I --> Q
K --> Q
Q --> R[Resolved Service]
Advanced Pattern Examples:
export const API_CONFIG = new InjectionToken<ApiConfig>('api.config', {
providedIn: 'root',
factory: () => ({
baseUrl: environment.apiUrl,
timeout: 5000
})
});
export const loggerProvider = {
provide: LoggerService,
useFactory: (http: HttpClient, config: ApiConfig) => {
return new LoggerService(http, config.logLevel);
},
deps: [HttpClient, API_CONFIG]
};
export const authInterceptorProvider = {
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
};
@Component({
selector: 'app-feature'
})
export class FeatureComponent {
private optionalService = inject(OptionalService, { optional: true
});
private selfService = inject(LocalService, { self: true
});
private parentService = inject(SharedService, { skipSelf: true });
private hostService = inject(HostService, { host: true
});
constructor() {
if (this.optionalService) {
console.log('Optional service available');
}
}
}
6. Service Lifecycle & Memory Management
graph TD
A[Service Request] --> B{First Request?}
B -->|Yes| C[Create Instance]
B -->|No| D[Return Cached]
C --> E[Constructor Called]
E --> F[Dependencies Injected]
F --> G[Service Initialized]
G --> H[Cache in Injector]
H --> I[Return to Consumer]
D --> I
I --> J[Service in Use]
J --> K{Scope Type?}
K -->|Root| L[Lives Forever
Until App Destroy]
K -->|Component| M[Lives with Component]
K -->|Route| N[Route Environment Injector]
M --> O{Component Destroyed?}
O -->|Yes| P[Service Destroyed]
O -->|No| J
N --> Q{withAutoCleanupInjectors Enabled?}
Q -->|Yes, Route Unused| R[Service Destroyed]
Q -->|No| S
L --> S[App Shutdown]
S --> T[All Services Destroyed]
P --> U[Garbage Collection]
R --> U
T --> U
✅ Best Practice: Use root-level services for shared state,
component-level for local state, and always clean up subscriptions in services. Leaving a route does not destroy its providers unless you enable withAutoCleanupInjectors.
7. Testing Services & DI
describe('UserService', () => {
let service: UserService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
UserService,
provideHttpClient(),
provideHttpClientTesting()
]
});
service = TestBed.inject(UserService);
httpMock = TestBed.inject(HttpTestingController);
});
it('should load users', () => {
const mockUsers = [{ id: 1, name: 'John' }];
service.loadUsers();
const req = httpMock.expectOne('/api/users');
expect(req.request.method).toBe('GET');
req.flush(mockUsers);
expect(service.users()).toEqual(mockUsers);
});
});
describe('UserComponent', () => {
let component: UserComponent;
let mockUserService: { loadUsers: Mock };
beforeEach(() => {
mockUserService = { loadUsers: vi.fn() };
TestBed.configureTestingModule({
providers: [
{ provide: UserService, useValue: mockUserService }
]
});
component = TestBed.createComponent(UserComponent).componentInstance;
});
it('should use injected service', () => {
component.refreshUsers();
expect(mockUserService.loadUsers).toHaveBeenCalled();
});
});