Back to Blog
Angular18 min read

Angular Services and Dependency Injection: Complete Guide

Angular Services provide a way to share business logic, data access, and functionality across components. Learn how to create services, use dependency injection, implement singleton patterns, and build reusable service architectures for enterprise Angular applications.

When I first started building Angular applications, I put all my business logic directly in components. That worked fine for small apps, but as applications grew, I found myself duplicating code, struggling with component communication, and making components harder to test. That's when I discovered Angular Services and Dependency Injection, and it completely changed how I structure applications.

Angular Services are singleton classes that provide reusable business logic, data access, and shared functionality across your application. They use Angular's Dependency Injection system, which means you don't manually create service instances—Angular does it for you and provides them wherever you need them. This makes your code more modular, testable, and maintainable.

In this guide, I'll walk you through creating and using Angular Services the way I do in production applications. We'll cover basic service creation with @Injectable, dependency injection in components and other services, singleton services with providedIn: 'root', module-scoped services, service patterns for API calls, sharing data between components using services, and testing services. I'll also share some architectural patterns I've learned for organizing services in large applications.

Creating a Basic Service

Create a service using the @Injectable decorator:

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { HttpHelper } from 'src/core/factory/http.helper';
import { environment } from 'src/environments/environment';

@Injectable({
  providedIn: 'root'
})
export class BusinessService {
  private url: string;

  constructor(private http: HttpHelper) {
    this.url = `${environment.ApiUrl}business`;
  }

  public GetBusinesses(data: any): Observable<any> {
    return this.http.post(`${this.url}/get`, data);
  }

  public GetBusiness(businessId: number): Observable<any> {
    return this.http.get(`${this.url}/${businessId}/details`);
  }

  public SaveBusiness(data: any, businessId: number): Observable<any> {
    return this.http.post(`${this.url}/${businessId}/details`, data);
  }

  public DeleteBusiness(id: number): Observable<any> {
    return this.http.delete(`${this.url}/${id}`);
  }
}

The @Injectable decorator marks the class as a service. providedIn: 'root' makes it a singleton available application-wide. Services can inject other services through constructor injection.

Using Services in Components

Inject services into components via constructor:

import { Component, OnInit } from '@angular/core';
import { BusinessService } from 'src/services/business.service';
import { ToastService } from 'src/core/controls/toast-global/toast.service';

@Component({
  selector: 'app-business-list',
  templateUrl: './business-list.component.html',
  styleUrls: ['./business-list.component.scss']
})
export class BusinessListComponent implements OnInit {
  public businesses: any[] = [];
  public loading: boolean = false;

  constructor(
    private businessService: BusinessService,
    private toast: ToastService
  ) {}

  ngOnInit(): void {
    this.loadBusinesses();
  }

  private loadBusinesses(): void {
    this.loading = true;
    this.businessService.GetBusinesses({ pageNumber: 1, pageSize: 10 })
      .subscribe({
        next: (response) => {
          this.businesses = response.data;
          this.loading = false;
        },
        error: (error) => {
          this.toast.error(error);
          this.loading = false;
        }
      });
  }
}

Service with HTTP Client

Services commonly handle HTTP requests:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { map, tap, catchError } from 'rxjs/operators';
import { environment } from 'src/environments/environment';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private url: string;

  constructor(private http: HttpClient) {
    this.url = `${environment.ApiUrl}users`;
  }

  public SearchUsers(searchTerm: string, isAll: boolean = false): Observable<any[]> {
    if (!searchTerm || searchTerm.length < 3) {
      return of([]);
    }

    return this.http.post(`${this.url}/search?isAll=${isAll}`, { search: searchTerm })
      .pipe(
        map((response: any) => response.data || []),
        tap(users => console.log('Found users:', users)),
        catchError(error => {
          console.error('Search error:', error);
          return of([]);
        })
      );
  }

  public GetUser(userId: number): Observable<any> {
    return this.http.get(`${this.url}/${userId}`);
  }

  public CreateUser(userData: any): Observable<any> {
    return this.http.post(`${this.url}`, userData);
  }

  public UpdateUser(userId: number, userData: any): Observable<any> {
    return this.http.put(`${this.url}/${userId}`, userData);
  }

  public DeleteUser(userId: number): Observable<any> {
    return this.http.delete(`${this.url}/${userId}`);
  }
}

Service Providers and providedIn

Different ways to provide services:

// Application-wide singleton (recommended)
@Injectable({
  providedIn: 'root'
})
export class GlobalService { }

// Platform-wide singleton
@Injectable({
  providedIn: 'platform'
})
export class PlatformService { }

// Module-scoped service
@Injectable()
export class ModuleService { }

// Then provide in module
@NgModule({
  providers: [ModuleService]
})
export class FeatureModule { }

// Component-scoped service
@Component({
  selector: 'app-component',
  providers: [ComponentService]
})
export class MyComponent { }

Service Communication Patterns

Services can communicate using RxJS:

import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class NotificationService {
  private notificationsSubject = new BehaviorSubject<any[]>([]);
  public notifications$ = this.notificationsSubject.asObservable();

  addNotification(notification: any): void {
    const current = this.notificationsSubject.value;
    this.notificationsSubject.next([...current, notification]);
  }

  removeNotification(id: string): void {
    const current = this.notificationsSubject.value;
    this.notificationsSubject.next(current.filter(n => n.id !== id));
  }

  clearNotifications(): void {
    this.notificationsSubject.next([]);
  }
}

Best Practices

  • Use providedIn: 'root' for application-wide singleton services
  • Keep services focused on a single responsibility
  • Use services for business logic, not presentation logic
  • Handle errors appropriately in service methods
  • Return Observables for async operations
  • Use RxJS operators for data transformation
  • Keep services stateless when possible
  • Use BehaviorSubject for shared state management
  • Document service methods and their return types
  • Test services independently from components

Conclusion

Angular Services and Dependency Injection provide a powerful way to organize code, share business logic, and manage application state. By following these patterns, you can build maintainable, testable, and scalable Angular applications. Services are the foundation of clean architecture in Angular applications.