> ## Documentation Index
> Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 04. Services & Dependency Injection

> Master Angular's powerful DI system, create injectable services, and understand provider configurations

<Frame>
  <img src="https://mintcdn.com/devweeekends/AEOaWh79Ur7CdHHv/images/courses/angular-crash-course/dependency-injection.svg?fit=max&auto=format&n=AEOaWh79Ur7CdHHv&q=85&s=a469251d7e4863dc13e3921308c520c3" alt="Angular Dependency Injection" width="900" height="550" data-path="images/courses/angular-crash-course/dependency-injection.svg" />
</Frame>

## Module Overview

<Info>
  **Estimated Time**: 3-4 hours | **Difficulty**: Intermediate | **Prerequisites**: Module 3
</Info>

Dependency Injection (DI) is a design pattern where objects receive their dependencies from external sources rather than creating them. Angular has a powerful DI system built-in that makes your code more testable, maintainable, and modular.

**What You'll Learn:**

* Creating injectable services
* Understanding the injector hierarchy
* Provider configurations
* The modern inject() function
* InjectionTokens for configuration
* Multi-providers and use cases

***

## What is Dependency Injection?

```
┌─────────────────────────────────────────────────────────────────────────┐
│                    Without DI vs With DI                                 │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│   WITHOUT DI (Tightly Coupled)                                          │
│   ─────────────────────────────                                         │
│   class UserComponent {                                                  │
│     private userService = new UserService();  // Hard to test!         │
│     private http = new HttpClient();          // Creates own deps       │
│   }                                                                      │
│                                                                          │
│   WITH DI (Loosely Coupled)                                             │
│   ───────────────────────────                                           │
│   class UserComponent {                                                  │
│     private userService = inject(UserService); // DI provides it       │
│   }                                                                      │
│                                                                          │
│   Benefits:                                                              │
│   ✓ Easier to test (mock dependencies)                                  │
│   ✓ Loosely coupled code                                                │
│   ✓ Single instance (singleton) management                              │
│   ✓ Flexible configuration                                              │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘
```

***

## Creating a Service

### Basic Service

```typescript theme={null}
// user.service.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

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

@Injectable({
  providedIn: 'root'  // Singleton, available everywhere
})
export class UserService {
  private http = inject(HttpClient);
  private apiUrl = '/api/users';
  
  getUsers(): Observable<User[]> {
    return this.http.get<User[]>(this.apiUrl);
  }
  
  getUser(id: number): Observable<User> {
    return this.http.get<User>(`${this.apiUrl}/${id}`);
  }
  
  createUser(user: Partial<User>): Observable<User> {
    return this.http.post<User>(this.apiUrl, user);
  }
  
  updateUser(id: number, user: Partial<User>): Observable<User> {
    return this.http.put<User>(`${this.apiUrl}/${id}`, user);
  }
  
  deleteUser(id: number): Observable<void> {
    return this.http.delete<void>(`${this.apiUrl}/${id}`);
  }
}
```

### Using the Service

```typescript theme={null}
// users.component.ts
import { Component, inject, OnInit } from '@angular/core';
import { UserService, User } from './user.service';
import { AsyncPipe } from '@angular/common';

@Component({
  selector: 'app-users',
  standalone: true,
  imports: [AsyncPipe],
  template: `
    @if (users$ | async; as users) {
      <ul>
        @for (user of users; track user.id) {
          <li>{{ user.name }} - {{ user.email }}</li>
        }
      </ul>
    } @else {
      <p>Loading...</p>
    }
  `
})
export class UsersComponent implements OnInit {
  // Modern inject() function
  private userService = inject(UserService);
  
  users$!: Observable<User[]>;
  
  ngOnInit() {
    this.users$ = this.userService.getUsers();
  }
}
```

***

## inject() vs Constructor Injection

Angular provides two ways to inject dependencies:

### Modern: inject() Function

```typescript theme={null}
import { Component, inject } from '@angular/core';

@Component({...})
export class UserComponent {
  // Inject in field declaration
  private userService = inject(UserService);
  private router = inject(Router);
  private route = inject(ActivatedRoute);
  
  // Can also inject in constructor body
  constructor() {
    const http = inject(HttpClient);  // Valid here too
  }
}
```

### Classic: Constructor Injection

```typescript theme={null}
import { Component } from '@angular/core';

@Component({...})
export class UserComponent {
  constructor(
    private userService: UserService,
    private router: Router,
    private route: ActivatedRoute
  ) {}
}
```

<Note>
  **Recommendation**: Use `inject()` for new code. It's more concise, works with functional features like guards and resolvers, and doesn't require decorator metadata for TypeScript.
</Note>

***

## Injector Hierarchy

Angular has a hierarchical injector system:

```
┌─────────────────────────────────────────────────────────────────────────┐
│                    Injector Hierarchy                                    │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│                     ┌─────────────────┐                                 │
│                     │  Root Injector  │                                 │
│                     │ (Application)   │                                 │
│                     │ providedIn: root│                                 │
│                     └────────┬────────┘                                 │
│                              │                                           │
│            ┌─────────────────┼─────────────────┐                        │
│            ▼                 ▼                 ▼                        │
│   ┌────────────────┐ ┌────────────────┐ ┌────────────────┐             │
│   │ Module Injector│ │ Module Injector│ │ Module Injector│             │
│   │  (Feature A)   │ │  (Feature B)   │ │  (Feature C)   │             │
│   └───────┬────────┘ └────────────────┘ └────────────────┘             │
│           │                                                              │
│   ┌───────┼───────────────┐                                             │
│   ▼       ▼               ▼                                             │
│ ┌─────┐ ┌─────┐       ┌─────┐                                          │
│ │Comp │ │Comp │       │Comp │  ← Component injectors                   │
│ │  A  │ │  B  │       │  C  │    (providers: [...])                    │
│ └─────┘ └─────┘       └─────┘                                          │
│                                                                          │
│   Resolution Order: Component → Module → Root                            │
│   (Bubble up until found)                                               │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘
```

### providedIn Options

```typescript theme={null}
// Application-wide singleton (recommended)
@Injectable({ providedIn: 'root' })
export class GlobalService {}

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

// No automatic provision (must provide manually)
@Injectable()
export class ManualService {}
```

***

## Provider Configurations

### Providing in Component

```typescript theme={null}
@Component({
  selector: 'app-feature',
  standalone: true,
  providers: [
    FeatureService  // New instance for this component tree
  ],
  template: `...`
})
export class FeatureComponent {
  private featureService = inject(FeatureService);
}
```

### useClass - Alternative Implementation

```typescript theme={null}
// Interface/abstract class
export abstract class Logger {
  abstract log(message: string): void;
}

// Real implementation
@Injectable()
export class ConsoleLogger implements Logger {
  log(message: string) {
    console.log(`[LOG] ${message}`);
  }
}

// Mock for testing
@Injectable()
export class MockLogger implements Logger {
  log(message: string) {
    // Do nothing
  }
}

// Provide alternative implementation
@Component({
  providers: [
    { provide: Logger, useClass: ConsoleLogger }
  ]
})
```

### useValue - Static Values

```typescript theme={null}
// Provide a static value
@Component({
  providers: [
    { provide: 'API_URL', useValue: 'https://api.example.com' }
  ]
})
export class AppComponent {
  private apiUrl = inject<string>('API_URL');
}
```

### useFactory - Dynamic Creation

```typescript theme={null}
// Factory function for complex initialization
export function loggerFactory(config: AppConfig): Logger {
  if (config.production) {
    return new CloudLogger();
  }
  return new ConsoleLogger();
}

@Component({
  providers: [
    {
      provide: Logger,
      useFactory: loggerFactory,
      deps: [AppConfig]  // Dependencies for factory
    }
  ]
})
```

### useExisting - Alias

```typescript theme={null}
// Create an alias for an existing service
@Component({
  providers: [
    NewLogger,
    { provide: OldLogger, useExisting: NewLogger }
  ]
})
```

***

## InjectionToken

For non-class dependencies, use `InjectionToken`:

```typescript theme={null}
// tokens.ts
import { InjectionToken } from '@angular/core';

export interface AppConfig {
  apiUrl: string;
  production: boolean;
  features: {
    darkMode: boolean;
    analytics: boolean;
  };
}

export const APP_CONFIG = new InjectionToken<AppConfig>('app.config');

// Default value with factory
export const API_BASE_URL = new InjectionToken<string>('api.base.url', {
  providedIn: 'root',
  factory: () => 'https://api.example.com'
});
```

```typescript theme={null}
// app.config.ts
export const appConfig: ApplicationConfig = {
  providers: [
    {
      provide: APP_CONFIG,
      useValue: {
        apiUrl: 'https://api.example.com',
        production: true,
        features: {
          darkMode: true,
          analytics: true
        }
      }
    }
  ]
};
```

```typescript theme={null}
// Using the token
@Component({...})
export class HeaderComponent {
  private config = inject(APP_CONFIG);
  
  get showDarkMode() {
    return this.config.features.darkMode;
  }
}
```

***

## Multi-Providers

Provide multiple values for the same token:

```typescript theme={null}
// HTTP Interceptors (common use case)
export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(
      withInterceptors([
        authInterceptor,
        loggingInterceptor,
        errorInterceptor
      ])
    )
  ]
};

// Custom multi-provider
export const VALIDATORS = new InjectionToken<Validator[]>('validators');

@Component({
  providers: [
    { provide: VALIDATORS, useClass: RequiredValidator, multi: true },
    { provide: VALIDATORS, useClass: EmailValidator, multi: true },
    { provide: VALIDATORS, useClass: MinLengthValidator, multi: true }
  ]
})
export class FormComponent {
  private validators = inject(VALIDATORS);  // Array of validators
}
```

***

## Service Design Patterns

### State Service with Signals

```typescript theme={null}
// state.service.ts
import { Injectable, signal, computed } from '@angular/core';

export interface AppState {
  user: User | null;
  theme: 'light' | 'dark';
  notifications: Notification[];
}

@Injectable({ providedIn: 'root' })
export class StateService {
  // Private writable signals
  private _user = signal<User | null>(null);
  private _theme = signal<'light' | 'dark'>('light');
  private _notifications = signal<Notification[]>([]);
  
  // Public read-only signals
  readonly user = this._user.asReadonly();
  readonly theme = this._theme.asReadonly();
  readonly notifications = this._notifications.asReadonly();
  
  // Computed values
  readonly isLoggedIn = computed(() => this._user() !== null);
  readonly unreadCount = computed(() => 
    this._notifications().filter(n => !n.read).length
  );
  
  // Actions
  setUser(user: User | null) {
    this._user.set(user);
  }
  
  toggleTheme() {
    this._theme.update(t => t === 'light' ? 'dark' : 'light');
  }
  
  addNotification(notification: Notification) {
    this._notifications.update(n => [...n, notification]);
  }
  
  markAsRead(id: string) {
    this._notifications.update(notifications =>
      notifications.map(n => 
        n.id === id ? { ...n, read: true } : n
      )
    );
  }
}
```

### Repository Pattern

```typescript theme={null}
// base-repository.ts
export abstract class BaseRepository<T extends { id: number }> {
  protected abstract endpoint: string;
  protected http = inject(HttpClient);
  
  getAll(): Observable<T[]> {
    return this.http.get<T[]>(this.endpoint);
  }
  
  getById(id: number): Observable<T> {
    return this.http.get<T>(`${this.endpoint}/${id}`);
  }
  
  create(item: Partial<T>): Observable<T> {
    return this.http.post<T>(this.endpoint, item);
  }
  
  update(id: number, item: Partial<T>): Observable<T> {
    return this.http.put<T>(`${this.endpoint}/${id}`, item);
  }
  
  delete(id: number): Observable<void> {
    return this.http.delete<void>(`${this.endpoint}/${id}`);
  }
}

// user-repository.ts
@Injectable({ providedIn: 'root' })
export class UserRepository extends BaseRepository<User> {
  protected endpoint = '/api/users';
  
  // Add user-specific methods
  getByEmail(email: string): Observable<User> {
    return this.http.get<User>(`${this.endpoint}/email/${email}`);
  }
}
```

### Facade Pattern

```typescript theme={null}
// user-facade.service.ts
@Injectable({ providedIn: 'root' })
export class UserFacade {
  private userService = inject(UserService);
  private authService = inject(AuthService);
  private notificationService = inject(NotificationService);
  
  // Expose state
  readonly currentUser = this.authService.currentUser;
  readonly isLoading = signal(false);
  
  // Unified interface for complex operations
  async login(email: string, password: string) {
    this.isLoading.set(true);
    try {
      const user = await firstValueFrom(
        this.authService.login(email, password)
      );
      this.notificationService.show('Welcome back!');
      return user;
    } catch (error) {
      this.notificationService.showError('Login failed');
      throw error;
    } finally {
      this.isLoading.set(false);
    }
  }
  
  async updateProfile(updates: Partial<User>) {
    const currentUser = this.currentUser();
    if (!currentUser) throw new Error('Not logged in');
    
    const updated = await firstValueFrom(
      this.userService.updateUser(currentUser.id, updates)
    );
    this.authService.setUser(updated);
    this.notificationService.show('Profile updated');
    return updated;
  }
}
```

***

## Testing Services

```typescript theme={null}
// user.service.spec.ts
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { UserService, User } from './user.service';

describe('UserService', () => {
  let service: UserService;
  let httpMock: HttpTestingController;
  
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [UserService]
    });
    
    service = TestBed.inject(UserService);
    httpMock = TestBed.inject(HttpTestingController);
  });
  
  afterEach(() => {
    httpMock.verify();  // Ensure no outstanding requests
  });
  
  it('should fetch users', () => {
    const mockUsers: User[] = [
      { id: 1, name: 'John', email: 'john@example.com' }
    ];
    
    service.getUsers().subscribe(users => {
      expect(users).toEqual(mockUsers);
    });
    
    const req = httpMock.expectOne('/api/users');
    expect(req.request.method).toBe('GET');
    req.flush(mockUsers);
  });
  
  it('should create a user', () => {
    const newUser = { name: 'Jane', email: 'jane@example.com' };
    const createdUser = { id: 2, ...newUser };
    
    service.createUser(newUser).subscribe(user => {
      expect(user).toEqual(createdUser);
    });
    
    const req = httpMock.expectOne('/api/users');
    expect(req.request.method).toBe('POST');
    expect(req.request.body).toEqual(newUser);
    req.flush(createdUser);
  });
});
```

***

## Practice Exercise

<Card title="Exercise: Build a Shopping Cart Service" icon="dumbbell">
  Create a `CartService` that:

  1. Uses signals for reactive state
  2. Supports add, remove, update quantity
  3. Computes total price and item count
  4. Persists to localStorage
</Card>

<Accordion title="Solution">
  ```typescript theme={null}
  // cart.service.ts
  interface CartItem {
    productId: number;
    name: string;
    price: number;
    quantity: number;
  }

  @Injectable({ providedIn: 'root' })
  export class CartService {
    private _items = signal<CartItem[]>(this.loadFromStorage());
    
    readonly items = this._items.asReadonly();
    readonly itemCount = computed(() => 
      this._items().reduce((sum, item) => sum + item.quantity, 0)
    );
    readonly total = computed(() =>
      this._items().reduce((sum, item) => sum + item.price * item.quantity, 0)
    );
    
    addItem(product: { id: number; name: string; price: number }) {
      this._items.update(items => {
        const existing = items.find(i => i.productId === product.id);
        if (existing) {
          return items.map(i => 
            i.productId === product.id 
              ? { ...i, quantity: i.quantity + 1 }
              : i
          );
        }
        return [...items, {
          productId: product.id,
          name: product.name,
          price: product.price,
          quantity: 1
        }];
      });
      this.saveToStorage();
    }
    
    removeItem(productId: number) {
      this._items.update(items => 
        items.filter(i => i.productId !== productId)
      );
      this.saveToStorage();
    }
    
    updateQuantity(productId: number, quantity: number) {
      if (quantity <= 0) {
        this.removeItem(productId);
        return;
      }
      this._items.update(items =>
        items.map(i => 
          i.productId === productId ? { ...i, quantity } : i
        )
      );
      this.saveToStorage();
    }
    
    clear() {
      this._items.set([]);
      this.saveToStorage();
    }
    
    private loadFromStorage(): CartItem[] {
      const data = localStorage.getItem('cart');
      return data ? JSON.parse(data) : [];
    }
    
    private saveToStorage() {
      localStorage.setItem('cart', JSON.stringify(this._items()));
    }
  }
  ```
</Accordion>

***

## Summary

<Steps>
  <Step title="Services">
    Use @Injectable to create reusable, testable services
  </Step>

  <Step title="inject() Function">
    Modern way to inject dependencies, works everywhere
  </Step>

  <Step title="Injector Hierarchy">
    Services can be scoped to root, module, or component level
  </Step>

  <Step title="Providers">
    Configure with useClass, useValue, useFactory, or useExisting
  </Step>

  <Step title="InjectionTokens">
    Use for non-class dependencies and configuration
  </Step>
</Steps>

***

## Next Steps

<Card title="Next: Angular Signals" icon="arrow-right" href="/courses/angular-crash-course/05-signals">
  Deep dive into Angular's reactive primitive for modern state management
</Card>
