Angular Services & DI
Learn Angular Services and Dependency Injection, including @Injectable, providedIn root, constructor injection, inject(), backend API services, shared state, hierarchical DI, providers, InjectionToken, facades, testing, and enterprise service architecture.
Learning Objectives
By the end of this lesson, you will understand:
- What Services are in Angular and why business logic should be separated from Components.
- What Dependency Injection means and how Angular's DI system works.
- How to create, provide, and inject Services using constructor injection and
inject(). - How
@Injectable(),providedIn: 'root', provider scopes, and Injection Tokens work. - How services support backend communication, shared state, enterprise architecture, and testing.
Introduction
Imagine you're building an e-commerce application. A ProductComponent might need to display products, search products, add products to a cart, calculate discounts, check inventory, load data from an API, handle errors, and cache product information.
If all of that logic lives inside one component, the component becomes difficult to understand, test, reuse, and maintain.
ProductComponent│├── UI Logic├── Product API Calls├── Cart Logic├── Discount Calculation├── Inventory Logic├── Error Handling└── Data Caching
A better architecture separates responsibilities:
ProductComponent│▼ProductService│▼HTTP Client│▼Backend API
The component focuses on the UI. The service handles reusable application logic and data access. Dependency Injection is how the component receives that service.
A Real-World Story
In an online banking application, a dashboard needs customer information, account balances, transactions, and notifications. Putting every API call and transformation inside DashboardComponent creates a large, tightly coupled component.
DashboardComponent│├── AccountService├── TransactionService└── NotificationService│▼HttpClient│▼Backend APIs
Each service has a clear responsibility, and Angular's DI system creates and provides those services where needed.
What Is an Angular Service?
A Service is typically a TypeScript class designed to provide reusable functionality to other parts of the application.
Services are commonly used for API communication, business logic, data transformation, shared state, authentication, logging, caching, configuration, notifications, and utility operations.
import { Injectable } from '@angular/core';@Injectable({providedIn: 'root'})export class ProductService {getProducts(): string[] {return ['Laptop', 'Mobile', 'Tablet'];}}
Why Do We Need Services?
As requirements grow, components that contain product logic, cart logic, inventory logic, and API access become hard to maintain. Services separate those responsibilities.
Component│└── Presentation & User InteractionServices│├── Reusable Logic├── Data Access├── State Coordination└── Business Operations
Separation of Concerns
A class should have a clear and focused responsibility. Components should primarily present UI and respond to interaction. Services should own reusable application behavior.
ProductComponent -> Display Product UIProductService -> Product OperationsCartService -> Cart OperationsAuthService -> Authentication StateNotificationService -> User Notifications
What Is Dependency Injection?
Dependency Injection is a design pattern where a class receives the objects it depends on rather than creating them directly.
Without DI:
productService = new ProductService();
With DI:
ProductComponent│▼Requests ProductService│▼Angular Injector│▼Provides ProductService
Why Is Dependency Injection Important?
DI improves loose coupling, testability, dependency replacement, centralized object creation, reusability, configurable scopes, and architecture.
Creating an Angular Service
import { Injectable } from '@angular/core';@Injectable({providedIn: 'root'})export class ProductService {getProducts() {return [{ id: 1, name: 'Laptop' },{ id: 2, name: 'Mobile' }];}}
Understanding @Injectable()
The @Injectable() decorator marks a class as participating in Angular's Dependency Injection system and allows provider metadata to be configured.
Understanding providedIn: 'root'
providedIn: 'root' commonly makes a service available application-wide through the root injector.
Application│▼Root Injector│▼ProductService│├── Component A├── Component B└── Service C
Singleton Service Behavior
Consumers resolving the same root-provided service usually share one instance. This is useful for shared state, authentication data, caching, and configuration, but mutable singleton state must be designed carefully.
Injecting a Service with Constructor Injection
@Component({selector: 'app-products',templateUrl: './products.component.html'})export class ProductsComponent {constructor(private productService: ProductService) {}}
Using the Service
export class ProductsComponent {products: string[] = [];constructor(private productService: ProductService) {}ngOnInit(): void {this.products = this.productService.getProducts();}}
Modern Dependency Injection with inject()
import { Component, inject } from '@angular/core';@Component({selector: 'app-products',templateUrl: './products.component.html'})export class ProductsComponent {private productService = inject(ProductService);}
Constructor Injection vs inject()
Both approaches resolve dependencies through Angular's DI system. inject() is especially useful in functional guards, functional interceptors, provider factories, and field initialization in injection contexts.
Service Communication with Backend APIs
import { Injectable, inject } from '@angular/core';import { HttpClient } from '@angular/common/http';@Injectable({providedIn: 'root'})export class ProductService {private http = inject(HttpClient);getProducts() {return this.http.get('/api/products');}}
Why Not Call APIs Directly from Components?
Components should not usually own URL construction, data transformation, error mapping, caching, and UI logic all at once. A service provides a clearer API/data access boundary.
Real-World Example: E-Commerce Architecture
Components│├── ProductComponent -> ProductService├── CartComponent -> CartService└── OrderComponent -> OrderService│▼HttpClient│▼Backend APIs
Sharing Data Between Components
Services can coordinate state between unrelated components, such as a product page adding items and a header displaying cart count.
Simple Shared State Example
@Injectable({providedIn: 'root'})export class CartService {cartCount = 0;addItem(): void {this.cartCount++;}}
A plain property is simple, but modern apps often use Signals, RxJS Observables, or dedicated state management depending on complexity.
Shared State with Signals
import { Injectable, signal } from '@angular/core';@Injectable({providedIn: 'root'})export class CartService {private readonly _cartCount = signal(0);readonly cartCount = this._cartCount.asReadonly();addItem(): void {this._cartCount.update(count => count + 1);}}
Services and RxJS
Many enterprise Angular apps expose shared state through Observable, Subject, or BehaviorSubject. Signals and RxJS can also be used together where appropriate.
Dependency Injection Architecture
Consumer│▼Requests Dependency│▼Injector│▼Find Provider│▼Create or Reuse Instance│▼Return Dependency
What Is a Provider?
A provider tells Angular how to obtain a value for a dependency. Provider location affects service scope and lifecycle.
providers: [ProductService]
Hierarchical Dependency Injection
Angular's DI system is hierarchical, so different parts of the app can receive different service instances when providers are scoped differently.
Application Injector│▼Environment / Route Scope│▼Component Injector│▼Child Component Injector
Component-Level Providers
@Component({selector: 'app-editor',providers: [EditorService]})export class EditorComponent {}
Each component instance can receive its own service instance, which is useful for isolated state.
Root Provider vs Component Provider
Root providers commonly create one shared application-level instance. Component providers can create isolated instances for component subtrees.
Injection Tokens
Not every dependency is a class. Configuration values such as API URLs can use InjectionToken.
import { InjectionToken } from '@angular/core';export const API_URL =new InjectionToken<string>('API_URL');{provide: API_URL,useValue: 'https://api.example.com'}private apiUrl = inject(API_URL);
Provider Strategies
Common provider strategies include useClass, useValue, useFactory, and useExisting. They allow flexible dependency configuration.
Services Depending on Other Services
@Injectable({providedIn: 'root'})export class OrderService {private http = inject(HttpClient);private authService = inject(AuthService);}
Dependency Graph
OrderComponent│▼OrderService│├── HttpClient└── AuthService│▼TokenService
Avoid Circular Dependencies
Circular dependencies often indicate responsibilities need to be reconsidered. Extract shared logic into a more appropriate service when needed.
Enterprise Service Architecture
src/app/core/├── auth/├── http/└── logging/features/├── products/services/└── orders/services/shared/└── services/
Organize services by responsibility and business domain instead of putting every service into one global folder.
Types of Services in Enterprise Applications
- API Services: backend communication.
- Domain Services: reusable business operations.
- State Services: shared reactive state.
- Infrastructure Services: logging, storage, analytics.
- Facade Services: simplified interfaces over complex feature workflows.
Real-World Enterprise Example: Checkout
CheckoutComponent│▼CheckoutFacade│├── CartService├── InventoryService├── PaymentService├── OrderService└── ShippingService
A facade can reduce coupling between UI components and lower-level implementation details.
Testing Benefits of Dependency Injection
DI makes dependencies easier to replace with mocks, stubs, or test implementations, enabling predictable unit tests.
Real-World Example: Authentication Service
An AuthService can own login, logout, authentication state, and current user information. Guards, headers, and profiles can all consume that state, while backend services still enforce authorization.
Service Lifetime and Memory Management
Root services may live for the lifetime of the app. Be careful with long-lived subscriptions, timers, event listeners, large cached datasets, and unbounded state.
Don't Turn Services into Global Dumping Grounds
A huge CommonService that handles authentication, products, payments, uploads, logging, and formatting becomes a "God Service". Prefer focused service boundaries.
Best Practices
- Keep Components focused primarily on presentation and interaction.
- Move reusable business and data-access logic into appropriate Services.
- Use
providedIn: 'root'for truly application-wide singleton services. - Use component-level providers when isolated instances are intentionally required.
- Understand both Constructor Injection and modern
inject(). - Use
InjectionTokenfor injectable configuration and non-class dependencies. - Avoid unnecessary circular dependencies and giant common services.
- Use Signals or RxJS appropriately for reactive shared state.
- Organize services by feature or domain in large applications.
- Make services easy to test and mock.
Common Pitfalls
Creating Services Manually
Avoid new ProductService() when a service participates in Angular DI.
Providing a Service at the Wrong Level
Component-level providers can create multiple instances and break expected shared state.
Putting Everything in One Service
Split responsibilities logically instead of creating one massive AppService.
Storing Too Much Mutable Global State
Root services can become global state containers if updates are not controlled.
Circular Dependencies
Reconsider responsibilities when services depend on each other in cycles.
Exposing Mutable State Directly
Prefer service methods for mutation and read-only state for consumers.
Common Misconceptions
Misconception
Every service is automatically a singleton.
Reality
Service lifetime depends on provider scope. Root-provided services are commonly shared, while component-level providers can create separate instances.
Misconception
Services are only for API calls.
Reality
Services can manage business logic, state, caching, authentication, logging, configuration, and other reusable responsibilities.
Misconception
Dependency Injection means Angular automatically injects every class.
Reality
Angular resolves dependencies that are available through configured providers.
Misconception
inject() completely replaces Constructor Injection.
Reality
Both approaches are valid and both appear in modern and existing Angular applications.
Advanced interview questions
Interview Prep
Practice concise answers, then expand each card for the explanation.
1BeginnerQuestionWhat is a Service in Angular?+
Answer
2BeginnerQuestionWhat is Dependency Injection?+
Answer
3BeginnerQuestionWhy do we use Services?+
Answer
4BeginnerQuestionWhat does providedIn: 'root' mean?+
Answer
5IntermediateQuestionAre Angular Services always singletons?+
Answer
6IntermediateQuestionWhat is the difference between Constructor Injection and inject()?+
Answer
7IntermediateQuestionWhat is hierarchical Dependency Injection?+
Answer
8IntermediateQuestionWhat is an InjectionToken?+
Answer
9IntermediateQuestionWhy should HTTP calls generally be placed in Services?+
Answer
10AdvancedQuestionHow can two unrelated Components share data?+
Answer
11AdvancedQuestionWhat is a Facade Service?+
Answer
12AdvancedQuestionHow would you structure Services in a large Angular application?+
Answer
13AdvancedQuestionWhat problems can occur if a shared state service is provided at Component level?+
Answer
14AdvancedQuestionWhat is the advantage of DI for unit testing?+
Answer
Summary
Angular Services and Dependency Injection provide the architectural foundation for separating application responsibilities.
Component│▼Service│▼Reusable LogicComponent│▼Requests Service│▼Angular Injector│▼Resolves Provider│▼Provides Service Instance
Dependency Injection is not simply a convenient way to access services. It is an architectural mechanism for loose coupling, testability, reusable functionality, controlled dependency lifecycles, and clear separation of responsibilities.
Next Lesson: Angular HTTP Client - you'll learn how Angular communicates with backend REST APIs, configure HttpClient, perform HTTP requests, create typed API models, handle errors, work with Observables, manage loading states, cancel requests, and design scalable API communication architecture.