Skip to main content

Overview

Services in Angular provide shared functionality across components. Angular 21 introduces improved dependency injection patterns using the inject() function and DestroyRef for better resource management.

Service architecture

The Angular PWA Demo uses a hierarchical service structure:
_services/
├── __baseService/         # Base service with HTTP configurations
├── __Utils/              # Utility services (Config, Speech, etc.)
├── __AI/                 # AI-related services
├── __Games/              # Game logic services
├── BackendService/       # API communication
└── AlgorithmService/     # Business logic

Creating a basic service

1

Generate the service

Use the Angular CLI to create a service:
ng generate service _services/MyService/my-service
2

Configure provider scope

Use providedIn: 'root' for singleton services:
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class MyService {
  // Service implementation
}
3

Inject the service

Use the modern inject() function in components:
import { Component, inject } from '@angular/core';
import { MyService } from './_services/MyService/my-service';

@Component({
  // ...
})
export class MyComponent {
  private readonly myService = inject(MyService);
}

Base service pattern

The base service provides shared HTTP configurations at src/app/_services/__baseService/base.service.ts:1:
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class BaseService {
  public HTTPOptions_Text_Plain = {
    headers: new HttpHeaders(),
    'responseType': 'text' as 'json'
  };

  public HTTPOptions_Text = {
    headers: new HttpHeaders({
      'Accept': 'application/text'
    }),
    'responseType': 'text' as 'json'
  };

  public HTTPOptions_JSON = {
    headers: new HttpHeaders({
      'Content-Type': 'application/json'
    }),
    'responseType': 'text' as 'json'
  };

  constructor() {}
}
Create a base service to share common configurations and reduce code duplication across services.

Modern dependency injection with inject()

Angular 21 introduces the inject() function for cleaner dependency injection. Here’s the backend service at src/app/_services/BackendService/backend.service.ts:1:
import { Injectable, OnInit, inject, DestroyRef } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { Observable } from 'rxjs';
import { ConfigService } from '../__Utils/ConfigService/config.service';
import { BaseService } from '../__baseService/base.service';

@Injectable({
  providedIn: 'root'
})
export class BackendService extends BaseService implements OnInit {
  // Modern inject() function instead of constructor DI
  public readonly http = inject(HttpClient);
  public readonly _configService = inject(ConfigService);
  
  // DestroyRef for automatic subscription cleanup
  private readonly destroyRef = inject(DestroyRef);

  constructor() {
    super();
  }

  ngOnInit(): void {
    // Initialization logic
  }

  _GetWebApiAppVersion(): Observable<string> {
    const p_url = `${this._configService.getConfigValue('baseUrlNetCore')}api/Demos/GetAppVersion`;
    return this.http.get<string>(p_url, this.HTTPOptions_Text);
  }

  // Async method with automatic cleanup
  public SetLog(p_PageTitle: string, p_logMsg: string, logType: LogType = LogType.Info): void {
    if (p_PageTitle === '' || p_logMsg === '') return;

    const p_url = `${this._configService.getConfigValue('baseUrlNetCore')}api/Demos/SetLog?p_logMsg=${p_logMsg}&logType=${logType.toString()}`;
    
    this.http.get<string>(p_url, this.HTTPOptions_Text)
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe({
        next: (logResult) => { /* Handle success */ },
        error: (err) => { /* Handle error */ }
      });
  }
}

Key features

inject() function

Modern, functional approach to dependency injection that works outside constructors

DestroyRef

Automatic cleanup of subscriptions without manual unsubscribe logic

Service inheritance

Extends BaseService for shared HTTP configurations

Type safety

Fully typed observables and method parameters

Configuration service

The configuration service demonstrates async initialization and config management at src/app/_services/__Utils/ConfigService/config.service.ts:1:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { ActivatedRoute } from '@angular/router';
import { _environment } from 'src/environments/environment';

@Injectable({
  providedIn: 'root'
})
export class ConfigService {
  constructor(
    protected http: HttpClient,
    public route: ActivatedRoute
  ) {}

  // Load configuration at app startup
  loadConfig() {
    return this.http.get('./assets/config/config.json').toPromise()
      .then((data: any) => {
        _environment.externalConfig = data;
      })
      .catch(error => {
        console.error('Error loading configuration:', error);
      });
  }

  // Load main pages configuration
  _loadMainPages(): Promise<void> {
    return new Promise((resolve) => {
      this.http.get('./assets/config/mainPages.json').toPromise()
        .then((data: any) => {
          _environment.mainPageList = data;
          _environment.mainPageList.forEach((element: MainPage) => {
            _environment.mainPageListDictionary[element.log_name] = element;
          });
          resolve();
        })
        .catch(error => {
          console.error('Error loading configuration:', error);
        });
    });
  }

  // Get configuration value by key
  getConfigValue(key: string) {
    let jsonData: string = JSON.parse(JSON.stringify(_environment.externalConfig))[key];
    return jsonData;
  }

  // Generate unique GUID
  generateGuid(): string {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (char) => {
      const random = (Math.random() * 16) | 0;
      const value = char === 'x' ? random : (random & 0x3) | 0x8;
      return value.toString(16);
    });
  }

  // Query URL parameters
  queryUrlParams(paraName: string): string {
    let returnValue = "";
    this.route.queryParams.subscribe(params => {
      returnValue = params[paraName] ? params[paraName] : "";
    });
    return returnValue;
  }
}
Configuration services that load at app startup should be provided in APP_INITIALIZER to ensure config is loaded before app renders.

Utility service: Speech service

The speech service shows browser API integration at src/app/_services/__Utils/SpeechService/speech.service.ts:1:
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class SpeechService {
  recognition: any;
  isListening: boolean = false;
  transcript: string = '';
  error: string = '';

  constructor() {
    // Initialize the SpeechRecognition object
    const SpeechRecognition = (window as any).SpeechRecognition || 
                             (window as any).webkitSpeechRecognition;
    
    if (SpeechRecognition) {
      this.recognition = new SpeechRecognition();
      this.recognition.lang = 'en-US';
      this.recognition.interimResults = false;
      this.recognition.maxAlternatives = 1;

      this.recognition.onresult = (event: any) => {
        this.transcript = event.results[0][0].transcript;
      };

      this.recognition.onerror = (event: any) => {
        this.error = event.error;
        this.isListening = false;
        console.error('Error:', this.error);
      };

      this.recognition.onend = () => {
        this.isListening = false;
      };
    } else {
      console.info('Speech Recognition API is not supported in your browser.');
    }
  }

  startListening() {
    if (this.recognition) {
      this.isListening = true;
      this.recognition.start();
    }
  }

  stopListening() {
    if (this.recognition) {
      this.isListening = false;
      this.recognition.stop();
    }
  }

  speakText(): string {
    if (this.transcript) {
      const utterance = new SpeechSynthesisUtterance(this.transcript);
      utterance.lang = 'en-US';
      window.speechSynthesis.speak(utterance);
    } else {
      alert('No text to speak!');
    }
    return this.transcript;
  }

  speakTextCustom(_transcript: string, lang: string = '') {
    setTimeout(() => {
      const utterance = new SpeechSynthesisUtterance(_transcript);
      utterance.lang = (lang == '') ? 'en-US' : lang;
      speechSynthesis.speak(utterance);
    }, 1000);
  }
}

Service injection patterns

Constructor injection (traditional)

export class MyComponent {
  constructor(private myService: MyService) {}
}

inject() function (modern)

export class MyComponent {
  private readonly myService = inject(MyService);
}
The inject() function can be used in field initializers, factory functions, and class constructors, making it more flexible than constructor injection.

Handling subscriptions with DestroyRef

import { Component, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';

export class MyComponent implements OnDestroy {
  private subscription = new Subscription();

  ngOnInit() {
    this.subscription.add(
      this.myService.getData().subscribe(data => {
        // Handle data
      })
    );
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

Best practices

This makes services tree-shakeable and ensures a single instance across the app.
The inject() function is more flexible and works in more contexts than constructor injection.
Use takeUntilDestroyed() with DestroyRef to automatically unsubscribe from observables.
Extend a base service class to share HTTP configurations and common utilities.
Each service should have a single responsibility. Split large services into smaller, focused ones.
Always handle HTTP errors gracefully and provide fallback values.

Next steps

Components guide

Learn about creating components

Configuration guide

Configure your application

Build docs developers (and LLMs) love