Part 8: Dependency Injection & Services – Sharing Logic Across Your App

Welcome back! Today we’re unlocking one of Angular’s most powerful features: Dependency Injection (DI). This system manages your services – the reusable pieces of logic that power your application’s functionality.

Why Services Matter

Services help you:

  • Share data and logic across components
  • Encapsulate business logic
  • Communicate with backend APIs
  • Maintain application state
  • Avoid code duplication

Creating Your First Service

Generate a service using Angular CLI:

bash

ng generate service data
# Shorthand: ng g s data

This creates:

typescript

// data.service.ts
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root' // <- This is key!
})
export class DataService {
  constructor() { }
}

Understanding @Injectable()

The decorator tells Angular:

  • This class can be injected
  • How it should be provided (singleton by default with providedIn: 'root')

Basic Service Example

Let’s create a simple counter service:

typescript

@Injectable({
  providedIn: 'root'
})
export class CounterService {
  private count = 0;

  increment() {
    this.count++;
  }

  getCount() {
    return this.count;
  }

  reset() {
    this.count = 0;
  }
}

Injecting Services into Components

Use constructor injection:

typescript

// app.component.ts
@Component({...})
export class AppComponent {
  constructor(private counterService: CounterService) {}

  increment() {
    this.counterService.increment();
    console.log(this.counterService.getCount());
  }
}

Dependency Injection in Action

Angular’s DI system works like this:

  1. You declare a dependency (via constructor)
  2. Angular looks for a provider of that service
  3. It either:
    • Creates a new instance, or
    • Returns an existing one (for singletons)

Service Providers: Three Ways to Provide

  1. Root Level (Singleton):typescriptCopyDownload@Injectable({ providedIn: ‘root’ // Recommended default })
  2. Module Level:typescriptCopyDownload@NgModule({ providers: [DataService] // Available to all in module })
  3. Component Level:typescriptCopyDownload@Component({ providers: [DataService] // New instance per component })

Real-World Service Example: API Service

Here’s a typical HTTP service:

typescript

@Injectable({
  providedIn: 'root'
})
export class ApiService {
  private apiUrl = 'https://api.example.com';

  constructor(private http: HttpClient) {}

  getUsers() {
    return this.http.get(`${this.apiUrl}/users`);
  }

  createUser(user: User) {
    return this.http.post(`${this.apiUrl}/users`, user);
  }
}

Don’t forget to import HttpClientModule in your root module!

Advanced DI Techniques

1. Injection Tokens

For non-class dependencies (like config objects):

typescript

// tokens.ts
export const API_CONFIG = new InjectionToken('app.config');

// app.module.ts
providers: [
  { provide: API_CONFIG, useValue: { apiUrl: 'https://api.example.com' } }
]

// api.service.ts
constructor(@Inject(API_CONFIG) private config: any) {}

2. Factory Providers

Complex initialization logic:

typescript

providers: [
  {
    provide: DataService,
    useFactory: (http: HttpClient) => {
      return new DataService(http, environment.apiUrl);
    },
    deps: [HttpClient]
  }
]

3. Optional Dependencies

typescript

constructor(@Optional() private logger?: LoggerService) {
  this.logger?.log('Component created');
}

Service Communication Patterns

  1. Shared Service:typescriptCopyDownload// data.service.ts private dataSubject = new BehaviorSubject<string>(‘default’); data$ = this.dataSubject.asObservable(); updateData(value: string) { this.dataSubject.next(value); }
  2. Component Interaction Service:typescriptCopyDownload// interaction.service.ts private buttonClicked = new Subject<void>(); buttonClicked$ = this.buttonClicked.asObservable(); notifyButtonClicked() { this.buttonClicked.next(); }

Best Practices

  1. Single Responsibility: Each service should do one thing well
  2. State Management: For complex state, consider NgRx
  3. Lazy Loading: Services in lazy-loaded modules get their own instances
  4. Testing: Mock services in component tests

Common Pitfalls

❌ Creating overly large “god” services
❌ Forgetting to provide services at the right level
❌ Not unsubscribing from observables
❌ Putting component logic in services

Hands-On Exercise

Create an AuthService with:

  1. login(username, password) method (simulate API call)
  2. logout() method
  3. isAuthenticated$ observable
  4. Store token in memory (no localStorage yet)

What’s Next?

In Part 9, we’ll dive into HTTP Client – how to communicate with backend APIs!

Leave a Comment

Your email address will not be published. Required fields are marked *