Services & Dependency Injection

In a large application, you will frequently need pieces of data or logic that must be shared across multiple completely separate Components. For example, managing User Authentication state, or fetching a list of Products from a backend API.

Instead of rewriting the fetch() API code inside every single component that needs it, you write the logic once inside a central class called a Service, and Angular provides a mechanism to share it everywhere.

Creating a Service

You can effortlessly generate a Service using the Angular CLI:

ng generate service data
# or shortened:
ng g s data

The @Injectable Decorator

A Service is fundamentally a standard TypeScript class decorated with @Injectable.

import { Injectable } from '@angular/core';

@Injectable({
  // This automatically registers the service at the root level of the app!
  // This makes the Service a "Singleton" (only one instance exists universally).
  providedIn: 'root', 
})
export class DataService {
  private users: string[] = ['Alice', 'Bob', 'Charlie'];

  getUsers(): string[] {
    return this.users;
  }

  addUser(name: string) {
    this.users.push(name);
  }
}

Dependency Injection (DI)

Dependency Injection is a powerful design pattern. In React, you might manually instantiate a class (`new DataService()`) or pass it down 5 levels via props (Prop Drilling). In Angular, the framework handles the instantiation completely automatically.

If a Component needs the DataService, it literally just asks for it in its constructor, and Angular injects it.

import { Component, OnInit } from '@angular/core';
import { DataService } from '../data.service';

@Component({
  selector: 'app-user-list',
  standalone: true,
  template: `
    <ul><li *ngFor="let u of userList">{{ u }}</li></ul>
    <button (click)="addNew()">Add Dave</button>
  `
})
export class UserListComponent implements OnInit {
  userList: string[] = [];

  // 1. Angular sees DataService in the constructor parameters.
  // 2. Angular finds the Singleton DataService and INJECTS it into "this.dataService".
  constructor(private dataService: DataService) {}

  ngOnInit() {
    // 3. We successfully read from the shared Service!
    this.userList = this.dataService.getUsers();
  }

  addNew() {
    this.dataService.addUser('Dave');
    // If another component is looking at DataService, it now sees Dave too!
  }
}

Why is DI amazing?

Dependency Injection guarantees that your application is modular and extremely easy to Unit Test. During tests, you can inject "Fake" mock services to guarantee the component works properly without hitting an actual production Database!

Conclusion

Services hold all the heavy business logic of your app so Components can remain lightweight and strictly focused on UI presentation. Next up, we look at moving between drastically different UI Views via Routing & Navigation.