Angular Tutorial 0/42 lessons ~6 min read Lesson 16

    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.

    Course progress0%
    Focus
    45 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    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.

    text
    ProductComponent
    ├── UI Logic
    ├── Product API Calls
    ├── Cart Logic
    ├── Discount Calculation
    ├── Inventory Logic
    ├── Error Handling
    └── Data Caching

    A better architecture separates responsibilities:

    text
    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.

    text
    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.

    typescript
    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.

    text
    Component
    └── Presentation & User Interaction
    Services
    ├── 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.

    text
    ProductComponent -> Display Product UI
    ProductService -> Product Operations
    CartService -> Cart Operations
    AuthService -> Authentication State
    NotificationService -> 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:

    typescript
    productService = new ProductService();

    With DI:

    text
    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

    typescript
    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.

    text
    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

    typescript
    @Component({
    selector: 'app-products',
    templateUrl: './products.component.html'
    })
    export class ProductsComponent {
    constructor(
    private productService: ProductService
    ) {}
    }

    Using the Service

    typescript
    export class ProductsComponent {
    products: string[] = [];
    constructor(private productService: ProductService) {}
    ngOnInit(): void {
    this.products = this.productService.getProducts();
    }
    }

    Modern Dependency Injection with inject()

    typescript
    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

    typescript
    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

    text
    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

    typescript
    @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

    typescript
    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

    text
    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.

    typescript
    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.

    text
    Application Injector
    Environment / Route Scope
    Component Injector
    Child Component Injector

    Component-Level Providers

    typescript
    @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.

    typescript
    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

    typescript
    @Injectable({
    providedIn: 'root'
    })
    export class OrderService {
    private http = inject(HttpClient);
    private authService = inject(AuthService);
    }

    Dependency Graph

    text
    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

    text
    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

    text
    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 InjectionToken for 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.

    14 questions
    1BeginnerQuestionWhat is a Service in Angular?+

    Answer

    An Angular Service is typically an injectable TypeScript class used to encapsulate reusable functionality such as API communication, business operations, shared state, logging, caching, or configuration.
    2BeginnerQuestionWhat is Dependency Injection?+

    Answer

    Dependency Injection is a design pattern where a class receives its dependencies from an external system rather than creating them directly.
    3BeginnerQuestionWhy do we use Services?+

    Answer

    Services separate reusable logic from UI components, improve code reuse, simplify testing, centralize shared functionality, and create cleaner architectural boundaries.
    4BeginnerQuestionWhat does providedIn: 'root' mean?+

    Answer

    It configures the service to be provided through Angular's root injector, making it broadly available throughout the application and typically shared.
    5IntermediateQuestionAre Angular Services always singletons?+

    Answer

    No. The number and lifetime of service instances depend on provider scope.
    6IntermediateQuestionWhat is the difference between Constructor Injection and inject()?+

    Answer

    Constructor Injection declares dependencies through constructor parameters. inject() resolves dependencies directly within an Angular injection context.
    7IntermediateQuestionWhat is hierarchical Dependency Injection?+

    Answer

    Angular organizes injectors hierarchically, allowing dependencies to have different scopes and instances depending on where providers are configured.
    8IntermediateQuestionWhat is an InjectionToken?+

    Answer

    An InjectionToken represents a dependency that may not naturally have a class type, such as configuration values or other injectable values.
    9IntermediateQuestionWhy should HTTP calls generally be placed in Services?+

    Answer

    Services keep components focused on presentation and interaction while centralizing backend communication logic.
    10AdvancedQuestionHow can two unrelated Components share data?+

    Answer

    They can inject a shared service that exposes reactive state using Signals or RxJS Observables.
    11AdvancedQuestionWhat is a Facade Service?+

    Answer

    A Facade Service provides a simplified interface over multiple services or complex feature logic.
    12AdvancedQuestionHow would you structure Services in a large Angular application?+

    Answer

    Organize services by responsibility and business domain: core services, feature services, state services, and shared cross-feature services.
    13AdvancedQuestionWhat problems can occur if a shared state service is provided at Component level?+

    Answer

    Each component subtree may receive a different service instance, causing inconsistent data if global sharing was expected.
    14AdvancedQuestionWhat is the advantage of DI for unit testing?+

    Answer

    Dependencies can be replaced with mocks, stubs, or test implementations so the unit under test is isolated.

    Summary

    Angular Services and Dependency Injection provide the architectural foundation for separating application responsibilities.

    text
    Component
    Service
    Reusable Logic
    Component
    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.

    Ready to mark this lesson complete?Track your journey across the entire course.