Back to Curriculum

Services and Dependency Injection

📚 Lesson 3 of 8 ⏱️ 45 min

Services and Dependency Injection

45 min

Services in Angular are classes that provide reusable functionality across components. They encapsulate business logic, data access, and shared functionality that doesn't belong in components. Services are decorated with `@Injectable()` and can be injected into components, other services, and directives. Using services keeps components focused on presentation logic while business logic lives in services. This separation of concerns makes code more maintainable, testable, and reusable.

Dependency Injection (DI) is a design pattern where dependencies are provided to classes rather than created within them. Angular's DI system automatically provides dependencies when you declare them in constructors. This makes code more testable (you can inject mock services in tests), maintainable (dependencies are explicit), and flexible (you can swap implementations easily). Angular's DI container manages service instances and their lifecycle, ensuring services are created and shared appropriately.

Services are singletons by default when provided at the root level (`providedIn: 'root'`). This means Angular creates one instance of the service and shares it across all components that inject it. This is efficient and ensures consistent state. Services can also be provided at the component or module level, creating separate instances for different parts of the application. Understanding service scope helps you manage state and avoid unintended side effects.

The `@Injectable()` decorator marks a class as a service that can be injected. The `providedIn` property determines where the service is provided: `'root'` makes it available application-wide, component-level provides it only to that component and its children, and module-level provides it to that module. Services can inject other services, creating dependency chains. Angular's DI system resolves these dependencies automatically, but circular dependencies must be avoided.

Common service use cases include data services (HTTP calls, API interactions), logging services, authentication services, configuration services, and utility services. Services should be focused on a single responsibility and provide a clear API. Services can use RxJS Observables for asynchronous operations, making them ideal for HTTP requests and real-time data. Understanding when to use services vs keeping logic in components is crucial for good Angular architecture.

Best practices include providing services at the root level when they're used application-wide, using services for shared business logic and data access, keeping services focused and testable, using dependency injection rather than creating instances manually, and documenting service APIs clearly. Services enable you to build maintainable, testable Angular applications with clear separation of concerns.

Key Concepts

  • Services provide reusable functionality across components.
  • Dependency Injection provides dependencies automatically.
  • Services are singletons by default when provided at root.
  • @Injectable() decorator marks classes as injectable services.
  • Services keep components focused on presentation logic.

Learning Objectives

Master

  • Creating and using Angular services
  • Understanding Dependency Injection and how it works
  • Providing services at different levels (root, component, module)
  • Injecting services into components and other services

Develop

  • Separation of concerns thinking
  • Understanding service-oriented architecture
  • Designing maintainable, testable Angular applications

Tips

  • Use providedIn: 'root' for application-wide services.
  • Inject services in constructors, not by creating instances manually.
  • Keep services focused on a single responsibility.
  • Use services for HTTP calls, business logic, and shared functionality.

Common Pitfalls

  • Creating service instances manually instead of using DI.
  • Putting business logic in components instead of services.
  • Creating circular dependencies between services.
  • Not understanding service scope, causing unexpected behavior.

Summary

  • Services provide reusable functionality across components.
  • Dependency Injection automatically provides service instances.
  • Services are singletons by default when provided at root.
  • Services keep components focused on presentation logic.
  • Understanding services and DI is essential for Angular architecture.

Exercise

Create services and inject them into components.

// user.service.ts
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';

export interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private users: User[] = [
    { id: 1, name: 'Alice', email: 'alice@example.com', age: 25 },
    { id: 2, name: 'Bob', email: 'bob@example.com', age: 30 },
    { id: 3, name: 'Charlie', email: 'charlie@example.com', age: 35 }
  ];

  getUsers(): Observable<User[]> {
    return of(this.users);
  }

  getUserById(id: number): Observable<User | undefined> {
    const user = this.users.find(u => u.id === id);
    return of(user);
  }

  addUser(user: Omit<User, 'id'>): Observable<User> {
    const newUser = { ...user, id: this.users.length + 1 };
    this.users.push(newUser);
    return of(newUser);
  }
}

// user-list.component.ts
import { Component, OnInit } from '@angular/core';
import { UserService, User } from './user.service';

@Component({
  selector: 'app-user-list',
  template: `
    <div class="user-list">
      <h2>Users</h2>
      <div *ngFor="let user of users" class="user-item">
        <h3>{{ user.name }}</h3>
        <p>Email: {{ user.email }}</p>
        <p>Age: {{ user.age }}</p>
        <button (click)="selectUser(user)">Select</button>
      </div>
    </div>
  `,
  styles: [`
    .user-list {
      padding: 20px;
    }
    .user-item {
      border: 1px solid #ddd;
      padding: 10px;
      margin: 10px 0;
      border-radius: 5px;
    }
  `]
})
export class UserListComponent implements OnInit {
  users: User[] = [];

  constructor(private userService: UserService) {}

  ngOnInit() {
    this.userService.getUsers().subscribe(users => {
      this.users = users;
    });
  }

  selectUser(user: User) {
    console.log('Selected user:', user);
  }
}

Code Editor

Output