Suggest an editImprove this articleRefine the answer for “Services and dependency injection in Angular”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Angular services and dependency injection**: a service is an `@Injectable` class holding shared logic (HTTP, state, validation), and DI delivers instances automatically through constructor parameters or `inject()`. ```typescript @Injectable({ providedIn: 'root' }) export class UserService { constructor(private http: HttpClient) {} getUsers(): Observable<User[]> { return this.http.get<User[]>('/api/users'); } } // In component: constructor(private userService: UserService) {} ``` **Key:** no `new UserService()` anywhere; Angular resolves and injects it at runtime.Shown above the full answer for quick recall.Answer (EN)Image**Angular services and dependency injection** work together: services are `@Injectable` classes that hold shared logic, and DI is the mechanism that delivers them to components through constructor parameters or the `inject()` function, without any manual `new` calls. ## Theory ### TL;DR - A service is a plain class with `@Injectable` that holds logic shared across components (API calls, state, validation) - DI delivers instances automatically: no `new ServiceName()` in your components - `@Injectable({ providedIn: 'root' })` creates one app-wide singleton, tree-shakable by default - Injectors form a hierarchy: root, module, component; child injectors inherit from parent - Decision rule: logic needed in 2+ components goes into a service; component-only state stays in the component ### Quick example ```typescript // user.service.ts import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) // one instance for the whole app export class UserService { constructor(private http: HttpClient) {} // Angular injects HttpClient getUsers(): Observable<User[]> { return this.http.get<User[]>('/api/users'); } } // user-list.component.ts @Component({ selector: 'app-user-list', template: '...' }) export class UserListComponent implements OnInit { constructor(private userService: UserService) {} // Angular resolves this ngOnInit() { this.userService.getUsers().subscribe(users => this.users = users); } } ``` `UserService` never calls `new HttpClient()`. The component never calls `new UserService()`. Angular's injector reads constructor parameter types at runtime and passes in the right instances. That is the entire idea. ### What DI solves Without DI, a component that needs HTTP calls would hardcode the setup directly, or create its own `UserService` instance. Swapping that in a test means rewriting the component. With DI, you change one line in `TestBed.configureTestingModule`: ```typescript providers: [{ provide: UserService, useValue: mockUserService }] ``` The component stays untouched. DI exists for testability and flexibility, not for magic. ### How the injector resolves dependencies Angular's injector is a tree. At the top sits the root injector, created at app bootstrap. Below it: module injectors, then component injectors. When a component requests a service, Angular walks up the tree until it finds a matching provider. `@Injectable({ providedIn: 'root' })` registers at the root. One instance, shared everywhere. Lazy-loaded modules get their own child injector branch; services declared in a lazy module's `providers: []` exist only in that branch. Component-level providers (`providers: [SomeService]` inside `@Component`) create a fresh instance for that component and its children, destroyed when the component is destroyed. TypeScript's `emitDecoratorMetadata` flag makes all of this work. At compile time, decorators write parameter type metadata into the class. At runtime, the injector reads `design:paramtypes` and uses the class constructor itself as the injection token. No string identifiers needed. ### Providing services: three patterns ```typescript // 1. Root singleton - most common, tree-shakable @Injectable({ providedIn: 'root' }) export class AuthService {} // 2. Component-scoped - fresh instance per component subtree @Component({ selector: 'app-checkout', providers: [CartService] // isolated, destroyed with the component }) export class CheckoutComponent {} // 3. Injection token for non-class values export const API_URL = new InjectionToken<string>('API_URL'); // register providers: [{ provide: API_URL, useValue: 'https://api.example.com' }] // consume private apiUrl = inject(API_URL); ``` ### Provider types | Provider | What it does | Typical use | |---|---|---| | `useClass` | Swap in a different class | Replace `ConsoleLogger` with `FileLogger` | | `useValue` | Provide a static value | Config constants, feature flags | | `useFactory` | Build instance via function | Instance needs runtime config or deps | | `useExisting` | Alias to another token | Backward-compatible service rename | ### Modern `inject()` function Angular 14 introduced `inject()` as an alternative to constructor injection. It works in field initializers and is the preferred style in standalone components. ```typescript import { inject } from '@angular/core'; @Component({ selector: 'app-dashboard', template: '...' }) export class DashboardComponent { private userService = inject(UserService); private apiUrl = inject(API_URL); // no constructor block needed } ``` Constructor injection still works and is common in older codebases. Both are valid. Most teams pick one style per project and stay consistent. ### Common mistakes In practice, the scoping issue trips up more teams than anything else: services that should live at component level end up in root, and state bleeds between views that should be isolated. **Missing provider registration:** ```typescript @Injectable() // no providedIn - not registered anywhere export class DataService {} // NullInjectorError: No provider for DataService! ``` Fix: `@Injectable({ providedIn: 'root' })` for a singleton, or add to the relevant `providers: []`. **Bypassing DI with `new`:** ```typescript @Component({...}) export class MyComponent { private service = new DataService(/* what goes here? */); } ``` `DataService` needs `HttpClient`. You would have to instantiate that too and set up the whole HTTP stack. Untestable. Use constructor injection or `inject()` instead. **Circular dependency:** ```typescript // user.service.ts constructor(private auth: AuthService) {} // auth.service.ts constructor(private user: UserService) {} // A needs B, B needs A // Cannot instantiate cyclic dependency! ``` Fix: extract the shared logic into a third service. Or inject lazily using `inject()` inside a method instead of at class initialization. **Accidental provider override:** if two providers register for the same token in the same injector, the last one wins silently. Useful for intentional mocking; surprising when it happens by accident during a module merge. ### Real-world usage - `HttpClient` is itself a DI service, provided by `provideHttpClient()` in standalone apps - Angular Material's `MatDialog` is a root-level service injected wherever a modal is needed - NgRx `Store` is injected as a service into any component or effect that reads state - AngularFire's `Firestore` service follows the same pattern for database access - In NX monorepos, the generator scaffolds services with `providedIn: 'root'` out of the box ### Follow-up questions **Q:** What is the difference between `providedIn: 'root'` and module-level `providers: []`? **A:** `providedIn: 'root'` creates a single app-wide instance and is tree-shakable: if nothing injects the service, it does not end up in the bundle. Module-level `providers: []` scopes to that module's injector; lazy-loaded modules create their own instance if they declare the provider themselves. **Q:** How does Angular match constructor parameters to providers without string identifiers? **A:** TypeScript with `emitDecoratorMetadata: true` writes parameter type info as `design:paramtypes` on the class. The injector reads this at runtime and uses the class constructor itself as the injection token. **Q:** How do hierarchical injectors work when a lazy module loads? **A:** The lazy module gets a child injector that inherits from the root. Services declared in the lazy module's `providers: []` are isolated to that branch. A `providedIn: 'root'` service is always the same shared instance regardless of where it is injected. **Q:** How do you mock a service in unit tests? **A:** Pass a mock with `providers: [{ provide: UserService, useValue: { getUsers: () => of([]) } }]` in `TestBed.configureTestingModule`. The component gets the mock, not the real service. Access the same instance in the test body with `TestBed.inject(UserService)`. **Q:** (Senior) When would you choose `useFactory` with `deps` over `useClass`? **A:** When the service instance needs values unavailable at class definition time. A logging service that sets a per-feature prefix, or an interceptor that reads a runtime config object injected from the parent. The `deps` array tells Angular which providers to resolve and pass as arguments to the factory function. ## Examples ### Basic: constructor injection with error handling ```typescript // user.service.ts import { Injectable } from '@angular/core'; import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { Observable, throwError } from 'rxjs'; import { catchError } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class UserService { private usersUrl = 'https://jsonplaceholder.typicode.com/users'; constructor(private http: HttpClient) {} getUsers(): Observable<User[]> { return this.http.get<User[]>(this.usersUrl).pipe( catchError((error: HttpErrorResponse) => throwError(() => new Error(`Failed to load users: ${error.message}`)) ) ); } } // user-list.component.ts @Component({ selector: 'app-user-list', template: ` <ul *ngIf="users.length"> <li *ngFor="let user of users">{{ user.name }}</li> </ul> <p *ngIf="error">{{ error }}</p> ` }) export class UserListComponent implements OnInit { users: User[] = []; error = ''; constructor(private userService: UserService) {} ngOnInit() { this.userService.getUsers().subscribe({ next: (users) => this.users = users, error: (err) => this.error = err.message }); } } // Loads from JSONPlaceholder, shows names or an error message ``` The service handles HTTP errors in one place. Every component that calls `getUsers()` gets consistent error handling without repeating the `catchError` logic. ### Intermediate: component-scoped service with `inject()` ```typescript // form-state.service.ts - scoped to the checkout component subtree @Injectable() // no providedIn - provided at component level below export class FormStateService { private formData: Partial<Order> = {}; update(data: Partial<Order>) { this.formData = { ...this.formData, ...data }; } get(): Partial<Order> { return this.formData; } } // checkout.component.ts @Component({ selector: 'app-checkout', template: '<app-address-form /><app-payment-form />', providers: [FormStateService] // new instance, destroyed with this component }) export class CheckoutComponent { private state = inject(FormStateService); } // address-form.component.ts - child inherits the same instance @Component({ selector: 'app-address-form', template: '...' }) export class AddressFormComponent { private state = inject(FormStateService); // same instance as CheckoutComponent } ``` `FormStateService` shares state between `CheckoutComponent` and its child `AddressFormComponent`, but a different checkout session elsewhere in the app gets a completely separate instance. This is the component-scoped injector pattern in practice. ### Advanced: factory provider with injection token ```typescript // logger.service.ts @Injectable({ providedIn: 'root' }) export class LoggerService { prefix = ''; log(msg: string) { console.log(`${this.prefix}${msg}`); } } // feature.module.ts import { InjectionToken, NgModule } from '@angular/core'; import { LoggerService } from './logger.service'; export const FEATURE_LOGGER = new InjectionToken<LoggerService>('FeatureLogger'); @NgModule({ providers: [{ provide: FEATURE_LOGGER, useFactory: (rootLogger: LoggerService) => { const logger = new LoggerService(); logger.prefix = '[Feature] '; return logger; // separate instance, different prefix }, deps: [LoggerService] // pulls root LoggerService as factory argument }] }) export class FeatureModule {} // feature.component.ts @Component({ template: '<p>Check console</p>' }) export class FeatureComponent { constructor(@Inject(FEATURE_LOGGER) private logger: LoggerService) { this.logger.log('Loaded'); // logs "[Feature] Loaded" } } // Root LoggerService logs without prefix everywhere else ``` The factory creates a dedicated logger for the feature module. It pulls `LoggerService` from the parent injector via `deps`, then customizes its own copy. The root logger stays unchanged. This is how Angular Material and other libraries customize behavior per lazy module.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.