17 Services

17.1 Grundlagen

Services sind ein Kernkonzept in Angular und stellen eine Möglichkeit dar, wiederverwendbare Logik zu kapseln, die in verschiedenen Teilen Ihrer Anwendung genutzt werden kann. Angular bietet verschiedene Features, die die Verwendung und Performance von Services optimieren.

17.2 Was sind Angular Services?

Ein Service ist eine Klasse mit einem spezifischen Zweck. Sie bieten Funktionalitäten wie Datenzugriff, Nutzerkommunikation, Logging, Business-Logik und mehr. Der Hauptzweck von Services ist die Trennung von Zuständigkeiten, indem Logik aus den Komponenten ausgelagert wird, wodurch:

17.3 Service-Erstellung in Angular

In Angular können Services mit dem Angular CLI erstellt werden:

ng generate service my-service

Dies erzeugt folgende Dateien: - my-service.service.ts - Die Service-Implementierung - my-service.service.spec.ts - Testdatei für den Service

17.3.1 Grundstruktur eines Services

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

@Injectable({
  providedIn: 'root'
})
export class DataService {
  private data: string[] = [];

  constructor() { }

  getData(): string[] {
    return this.data;
  }

  addData(item: string): void {
    this.data.push(item);
  }
}

17.4 Dependency Injection

17.4.1 Der @Injectable-Dekorator

Der @Injectable-Dekorator markiert eine Klasse als Service, die in Angulars Dependency Injection (DI) System registriert werden kann. Es gibt verschiedene Möglichkeiten für die Konfiguration:

@Injectable({
  providedIn: 'root',           // Anwendungsweit verfügbar
  // providedIn: 'any',         // Verfügbar für alle Lazy-Loading-Module
  // providedIn: SomeModule,    // Verfügbar nur in einem bestimmten Modul
})

17.5 Service-Nutzung in Komponenten

Um einen Service in einer Komponente zu verwenden:

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

@Component({
  selector: 'app-data-display',
  template: `
    <div>
      <h2>Daten:</h2>
      <ul>
        <li *ngFor="let item of items">{{ item }}</li>
      </ul>
      <button (click)="addItem()">Neues Element hinzufügen</button>
    </div>
  `
})
export class DataDisplayComponent {
  items: string[] = [];

  constructor(private dataService: DataService) {
    this.items = this.dataService.getData();
  }

  addItem(): void {
    const newItem = `Element ${this.items.length + 1}`;
    this.dataService.addData(newItem);
    this.items = this.dataService.getData();
  }
}

17.6 Service-Hierarchie und Provider-Ebenen

Angular bietet mehrere Ebenen, auf denen Services bereitgestellt werden können:

  1. Root-Ebene: Anwendungsweit als Singleton verfügbar
  2. Modul-Ebene: Nur innerhalb eines bestimmten Moduls verfügbar
  3. Komponenten-Ebene: Ein neuer Service-Instance für jede Komponente
@Component({
  selector: 'app-special',
  providers: [SpecialService],  // Komponenten-spezifische Instance
  template: '...'
})
export class SpecialComponent { }

17.7 Fortgeschrittene Service-Nutzung

17.7.1 HTTP-Services

Ein häufiger Anwendungsfall für Services ist die Kommunikation mit APIs:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

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

  constructor(private http: HttpClient) { }

  fetchData(): Observable<any[]> {
    return this.http.get<any[]>(this.apiUrl);
  }

  saveData(data: any): Observable<any> {
    return this.http.post<any>(this.apiUrl, data);
  }
}

17.7.2 Service-zu-Service-Kommunikation

Ein Service kann andere Services injizieren, um komplexere Funktionalitäten zu implementieren:

import { Injectable } from '@angular/core';
import { ApiService } from './api.service';
import { LogService } from './log.service';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class DataManagerService {
  constructor(
    private apiService: ApiService,
    private logService: LogService
  ) { }

  processData(): Observable<any[]> {
    this.logService.log('Daten werden abgerufen...');
    return this.apiService.fetchData().pipe(
      tap(data => {
        this.logService.log(`${data.length} Elemente abgerufen`);
      })
    );
  }
}

17.8 Standalone-Services

In neueren Angular-Versionen können Services als Teil des Standalone-API-Konzepts verwendet werden:

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

@Injectable({
  providedIn: 'root'
})
export class StandaloneService {
  getValue(): string {
    return 'Ich bin ein Service!';
  }
}

17.9 Reactive Services mit RxJS

Ein leistungsstarkes Muster in Angular ist die Kombination von Services mit RxJS:

import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class StateService {
  private stateSubject = new BehaviorSubject<any>({
    isLoading: false,
    data: null,
    error: null
  });

  // Öffentliche Observable für Komponenten
  public state$ = this.stateSubject.asObservable();

  constructor() { }

  updateState(newState: Partial<any>): void {
    this.stateSubject.next({
      ...this.stateSubject.value,
      ...newState
    });
  }

  setLoading(isLoading: boolean): void {
    this.updateState({ isLoading });
  }

  setData(data: any): void {
    this.updateState({ data, isLoading: false, error: null });
  }

  setError(error: any): void {
    this.updateState({ error, isLoading: false });
  }
}

Verwendung in einer Komponente:

@Component({
  selector: 'app-data-view',
  template: `
    <div *ngIf="(state$ | async)?.isLoading">Laden...</div>
    <div *ngIf="(state$ | async)?.error">Fehler: {{ (state$ | async)?.error }}</div>
    <div *ngIf="(state$ | async)?.data">
      {{ (state$ | async)?.data | json }}
    </div>
  `
})
export class DataViewComponent {
  state$: Observable<any>;

  constructor(private stateService: StateService) {
    this.state$ = this.stateService.state$;
  }

  loadData(): void {
    this.stateService.setLoading(true);
    // Datenabruf...
  }
}

17.10 Testing von Services in Angular

Angular bietet umfangreiche Testmöglichkeiten für Services:

// data.service.spec.ts
import { TestBed } from '@angular/core/testing';
import { DataService } from './data.service';

describe('DataService', () => {
  let service: DataService;

  beforeEach(() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(DataService);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  it('should add data correctly', () => {
    service.addData('Test Item');
    expect(service.getData()).toContain('Test Item');
    expect(service.getData().length).toBe(1);
  });
});

17.11 Best Practices für Services in Angular

  1. Single Responsibility Principle: Ein Service sollte nur eine Aufgabe haben
  2. Immutabilität bevorzugen: Rückgabewerte kopieren statt direkte Referenzen
  3. Asynchrone Operationen mit Observables: RxJS nutzen für asynchrone Aufgaben
  4. Granulare Services: Lieber mehrere spezialisierte Services als wenige komplexe
  5. Dokumentation: JSDoc-Kommentare für öffentliche Methoden
  6. Error Handling: Fehlerbehandlung in Services implementieren
  7. Cachingstrategien: Optimieren durch intelligentes Caching

Services in Angular sind ein mächtiges Werkzeug zur Organisation von Anwendungslogik. Sie bieten Flexibilität und Performance bei der Entwicklung von robusten Anwendungen.