REST (Representational State Transfer) ist ein Architekturstil für verteilte Systeme, der 2000 von Roy Fielding in seiner Dissertation eingeführt wurde. REST definiert einen Satz von Prinzipien, die beschreiben, wie Netzwerkressourcen definiert und adressiert werden können.
Die Hauptmerkmale von REST-APIs sind:
Der HttpClient ist Angulars primäres Werkzeug für die
Kommunikation mit REST-APIs. Seit Angular 4.3 ist er Teil des
@angular/common/http-Pakets und bietet eine moderne,
leistungsstarke HTTP-Client-Implementierung.
Um den HttpClient zu verwenden, müssen Sie das
HttpClientModule in Ihrem App-Modul importieren:
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
HttpClientModule // Import hier hinzufügen
],
bootstrap: [AppComponent]
})
export class AppModule { }In Angular 14+ mit der Standalone-API können Sie den HttpClient auch direkt in der Komponente oder im Service importieren:
// app.component.ts (Angular 14+)
import { Component } from '@angular/core';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule, HttpClientModule],
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
// ...
}Bei Angular 16+ wird die Verwendung von
provideHttpClient() in der
bootstrapApplication-Methode empfohlen:
// main.ts (Angular 16+)
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { provideHttpClient } from '@angular/common/http';
bootstrapApplication(AppComponent, {
providers: [
provideHttpClient()
]
});GET-Anfragen werden verwendet, um Ressourcen vom Server abzurufen. Der HttpClient bietet typisierte Antworten, die die Arbeit mit zurückgegebenen Daten erheblich erleichtern:
// user.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { User } from './models/user.model';
@Injectable({
providedIn: 'root'
})
export class UserService {
private apiUrl = 'https://api.example.com/users';
constructor(private http: HttpClient) { }
// Typisierte Antwort mit einer Benutzerliste
getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.apiUrl);
}
// Abrufen eines einzelnen Benutzers mit einer ID
getUser(id: number): Observable<User> {
return this.http.get<User>(`${this.apiUrl}/${id}`);
}
}POST-Anfragen werden verwendet, um neue Ressourcen zu erstellen:
// user.service.ts (Ergänzung)
createUser(user: User): Observable<User> {
return this.http.post<User>(this.apiUrl, user);
}PUT aktualisiert eine gesamte Ressource, während PATCH nur Teile einer Ressource aktualisiert:
// user.service.ts (Ergänzung)
// Vollständiges Update eines Benutzers
updateUser(user: User): Observable<User> {
return this.http.put<User>(`${this.apiUrl}/${user.id}`, user);
}
// Partielles Update eines Benutzers
partialUpdateUser(id: number, changes: Partial<User>): Observable<User> {
return this.http.patch<User>(`${this.apiUrl}/${id}`, changes);
}DELETE-Anfragen werden verwendet, um Ressourcen zu entfernen:
// user.service.ts (Ergänzung)
deleteUser(id: number): Observable<void> {
return this.http.delete<void>(`${this.apiUrl}/${id}`);
}Der HttpClient erlaubt die Anpassung von Anfragen durch Konfigurationsoptionen:
// user.service.ts (Ergänzung)
import { HttpParams, HttpHeaders } from '@angular/common/http';
// Anfrage mit URL-Parametern
getUsersWithPagination(page: number, pageSize: number): Observable<User[]> {
const params = new HttpParams()
.set('page', page.toString())
.set('pageSize', pageSize.toString());
return this.http.get<User[]>(this.apiUrl, { params });
}
// Anfrage mit benutzerdefinierten Headers
getUserWithCustomHeaders(id: number): Observable<User> {
const headers = new HttpHeaders()
.set('Content-Type', 'application/json')
.set('Authorization', 'Bearer your-token-here');
return this.http.get<User>(`${this.apiUrl}/${id}`, { headers });
}Die RxJS-Operatoren catchError und
throwError sind nützlich für die Behandlung von
HTTP-Fehlern:
// error-handling.service.ts
import { Injectable } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class ErrorHandlingService {
handleError(error: HttpErrorResponse): Observable<never> {
let errorMessage = 'Ein unbekannter Fehler ist aufgetreten!';
if (error.error instanceof ErrorEvent) {
// Client-seitiger Fehler
errorMessage = `Fehler: ${error.error.message}`;
} else {
// Server-seitiger Fehler
errorMessage = `Statuscode: ${error.status}, Nachricht: ${error.message}`;
}
console.error(errorMessage);
return throwError(() => new Error(errorMessage));
}
}
// user.service.ts (mit Fehlerbehandlung)
import { catchError } from 'rxjs/operators';
// Ergänzung zum UserService
constructor(
private http: HttpClient,
private errorService: ErrorHandlingService
) { }
getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.apiUrl)
.pipe(
catchError(error => this.errorService.handleError(error))
);
}Seit Angular 10 ist es möglich, HTTP-Anfragen während der Ausführung
über ein AbortController-Signal abzubrechen, was in Angular
11 und höher standardmäßig unterstützt wird:
// user.service.ts (Ergänzung)
getUserWithTimeout(id: number): Observable<User> {
// Erzeugen eines AbortSignal, das nach 5 Sekunden die Anfrage abbricht
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
return this.http.get<User>(`${this.apiUrl}/${id}`, {
signal: controller.signal
}).pipe(
finalize(() => clearTimeout(timeoutId))
);
}Interceptoren sind ein mächtiges Feature von Angular, um HTTP-Anfragen und -Antworten global zu manipulieren. Sie werden häufig für Authentifizierung, Logging oder Fehlerbehandlung verwendet.
// auth.interceptor.ts
import { Injectable } from '@angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { AuthService } from './auth.service';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private auth: AuthService) {}
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
// Fügt den Auth-Token zu jeder ausgehenden Anfrage hinzu
const token = this.auth.getToken();
if (token) {
const authRequest = request.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
return next.handle(authRequest);
}
return next.handle(request);
}
}In Angular < 15:
// app.module.ts
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthInterceptor } from './interceptors/auth.interceptor';
@NgModule({
// ...
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
}
]
})
export class AppModule { }In Angular 15+ mit der Provider-Funktion:
// main.ts (Angular 15+)
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { authInterceptor } from './app/interceptors/auth.interceptor';
bootstrapApplication(AppComponent, {
providers: [
provideHttpClient(
withInterceptors([authInterceptor])
)
]
});In Angular 16+ kann der Interceptor auch als Funktion definiert werden:
// auth.interceptor.ts (Angular 16+)
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { AuthService } from './auth.service';
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const authService = inject(AuthService);
const token = authService.getToken();
if (token) {
const authReq = req.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
return next(authReq);
}
return next(req);
};Angular HTTP kombiniert hervorragend mit RxJS-Operatoren für komplexe Datenverarbeitung:
// user.service.ts (mit RxJS-Operatoren)
import { map, tap, switchMap, debounceTime, distinctUntilChanged } from 'rxjs/operators';
import { Observable, Subject, combineLatest } from 'rxjs';
// Suchfunktion mit Debounce
private searchTerms = new Subject<string>();
search(term: string): void {
this.searchTerms.next(term);
}
getSearchResults(): Observable<User[]> {
return this.searchTerms.pipe(
debounceTime(300), // Warte 300ms nach letzter Eingabe
distinctUntilChanged(), // Ignoriere doppelte Suchbegriffe
switchMap(term => this.http.get<User[]>(`${this.apiUrl}/search?q=${term}`))
);
}
// Daten kombinieren und transformieren
getUserWithPosts(userId: number): Observable<{user: User, posts: Post[]}> {
const user$ = this.http.get<User>(`${this.apiUrl}/users/${userId}`);
const posts$ = this.http.get<Post[]>(`${this.apiUrl}/posts?userId=${userId}`);
return combineLatest([user$, posts$]).pipe(
map(([user, posts]) => ({ user, posts }))
);
}Eine einfache Cache-Implementierung mit RxJS
shareReplay:
// cache.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { shareReplay, tap, catchError } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class CacheService {
private cache: Record<string, Observable<any>> = {};
constructor(private http: HttpClient) { }
get<T>(url: string, options?: any): Observable<T> {
if (!this.cache[url]) {
this.cache[url] = this.http.get<T>(url, options).pipe(
shareReplay(1), // Cache das letzte Ergebnis
catchError(err => {
delete this.cache[url]; // Fehlerhafte Antworten nicht cachen
return throwError(() => err);
})
);
}
return this.cache[url];
}
clearCache(): void {
this.cache = {};
}
clearCacheEntry(url: string): void {
delete this.cache[url];
}
}Obwohl REST das dominierende API-Paradigma ist, gewinnt GraphQL an Popularität. Angular arbeitet gut mit dem Apollo-Client zusammen:
// Beispiel zur Integration von Apollo Client mit Angular
// app.module.ts
import { APOLLO_OPTIONS } from 'apollo-angular';
import { HttpLink } from 'apollo-angular/http';
import { InMemoryCache } from '@apollo/client/core';
@NgModule({
// ...
providers: [
{
provide: APOLLO_OPTIONS,
useFactory: (httpLink: HttpLink) => {
return {
cache: new InMemoryCache(),
link: httpLink.create({
uri: 'https://api.example.com/graphql',
}),
};
},
deps: [HttpLink],
},
],
})
export class AppModule {}
// user.service.ts (mit Apollo)
import { Injectable } from '@angular/core';
import { Apollo, gql } from 'apollo-angular';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
const GET_USERS = gql`
query GetUsers {
users {
id
name
email
}
}
`;
@Injectable({
providedIn: 'root'
})
export class UserGraphQLService {
constructor(private apollo: Apollo) { }
getUsers(): Observable<User[]> {
return this.apollo.query<{users: User[]}>({
query: GET_USERS
}).pipe(
map(result => result.data.users)
);
}
}Mit der Einführung von Signals in Angular 16+ und deren Weiterentwicklung in Angular 17+ können HTTP-Anfragen nun eleganter mit Signals integriert werden:
// user.service.ts (Angular 17+)
import { Injectable, signal, computed } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { toSignal } from '@angular/core/rxjs-interop';
@Injectable({
providedIn: 'root'
})
export class UserSignalService {
private apiUrl = 'https://api.example.com/users';
// Signals für den Zustand
private usersSignal = signal<User[]>([]);
private loadingSignal = signal<boolean>(false);
private errorSignal = signal<string | null>(null);
// Öffentliche readonly-Signale
public users = this.usersSignal.asReadonly();
public loading = this.loadingSignal.asReadonly();
public error = this.errorSignal.asReadonly();
// Berechnetes Signal für gefilterte Benutzer
public activeUsers = computed(() =>
this.users().filter(user => user.isActive)
);
constructor(private http: HttpClient) {}
loadUsers(): void {
this.loadingSignal.set(true);
this.errorSignal.set(null);
this.http.get<User[]>(this.apiUrl).subscribe({
next: (users) => {
this.usersSignal.set(users);
this.loadingSignal.set(false);
},
error: (err) => {
this.errorSignal.set(err.message || 'Ein Fehler ist aufgetreten');
this.loadingSignal.set(false);
}
});
}
// Alternative mit toSignal
users$ = this.http.get<User[]>(this.apiUrl);
usersSignalAlt = toSignal(this.users$, { initialValue: [] as User[] });
}Angular 18 bietet verbesserte HTTP-Konfigurationsoptionen, insbesondere für Standalone-Anwendungen:
// main.ts (Angular 18)
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { provideHttpClient, withFetch, withJsonpSupport, withXsrfConfiguration } from '@angular/common/http';
bootstrapApplication(AppComponent, {
providers: [
provideHttpClient(
// Verwendet Fetch API statt XMLHttpRequest
withFetch(),
// JSONP-Unterstützung für Cross-Origin-Anfragen
withJsonpSupport(),
// XSRF-Konfiguration
withXsrfConfiguration({
cookieName: 'XSRF-TOKEN',
headerName: 'X-XSRF-TOKEN'
})
)
]
});Eine gut strukturierte Anwendung teilt die API-Kommunikation in spezialisierte Services auf:
// api.service.ts - Basis-Service für alle API-Aufrufe
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from 'src/environments/environment';
@Injectable({
providedIn: 'root'
})
export class ApiService {
private baseUrl = environment.apiUrl;
constructor(private http: HttpClient) { }
get<T>(path: string, options = {}): Observable<T> {
return this.http.get<T>(`${this.baseUrl}${path}`, options);
}
post<T>(path: string, body: any, options = {}): Observable<T> {
return this.http.post<T>(`${this.baseUrl}${path}`, body, options);
}
put<T>(path: string, body: any, options = {}): Observable<T> {
return this.http.put<T>(`${this.baseUrl}${path}`, body, options);
}
delete<T>(path: string, options = {}): Observable<T> {
return this.http.delete<T>(`${this.baseUrl}${path}`, options);
}
}
// user.service.ts - Spezialisierter Service für Benutzer-APIs
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { ApiService } from './api.service';
import { User } from '../models/user.model';
@Injectable({
providedIn: 'root'
})
export class UserService {
private path = '/users';
constructor(private api: ApiService) { }
getAll(): Observable<User[]> {
return this.api.get<User[]>(this.path);
}
getById(id: number): Observable<User> {
return this.api.get<User>(`${this.path}/${id}`);
}
create(user: User): Observable<User> {
return this.api.post<User>(this.path, user);
}
update(user: User): Observable<User> {
return this.api.put<User>(`${this.path}/${user.id}`, user);
}
delete(id: number): Observable<void> {
return this.api.delete<void>(`${this.path}/${id}`);
}
}Moderne Angular-Komponenten sollten observables von Services abonnieren und Async-Pipe verwenden:
// users.component.ts
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { UserService } from '../services/user.service';
import { User } from '../models/user.model';
@Component({
selector: 'app-users',
template: `
<div *ngIf="users$ | async as users; else loading">
<div *ngFor="let user of users">
{{ user.name }} - {{ user.email }}
</div>
</div>
<ng-template #loading>Laden...</ng-template>
`
})
export class UsersComponent implements OnInit {
users$: Observable<User[]>;
constructor(private userService: UserService) { }
ngOnInit(): void {
this.users$ = this.userService.getAll();
}
}Oder mit Signals (Angular 17+):
// users.component.ts (Angular 17+)
import { Component, OnInit, inject } from '@angular/core';
import { UserSignalService } from '../services/user-signal.service';
@Component({
selector: 'app-users',
standalone: true,
imports: [CommonModule],
template: `
@if (userService.loading()) {
<div>Laden...</div>
} @else if (userService.error()) {
<div class="error">{{ userService.error() }}</div>
} @else {
<div *ngFor="let user of userService.users()">
{{ user.name }} - {{ user.email }}
</div>
}
`
})
export class UsersComponent implements OnInit {
userService = inject(UserSignalService);
ngOnInit(): void {
this.userService.loadUsers();
}
}Die REST-API-Kommunikation in Angular hat sich über die letzten
Versionen hinweg deutlich verbessert. Während die grundlegenden
HTTP-Methoden (GET, POST, PUT,
DELETE) konstant geblieben sind, wurde die API um
fortgeschrittene Funktionen wie typisierte Antworten, Interceptoren und
verbesserte Fehlerbehandlung erweitert.
Mit Angular 16+ wurden Signals eingeführt, die eine elegantere und reaktivere Möglichkeit bieten, mit REST-API-Daten zu arbeiten. Außerdem haben sich die Konfigurationsoptionen für HTTP-Clients erweitert, insbesondere für Standalone-Anwendungen.
Unabhängig von der Angular-Version sollten Entwickler darauf achten, ihre API-Kommunikation zu strukturieren, typsicher zu gestalten und angemessene Fehlerbehandlung zu implementieren, um robuste und wartbare Anwendungen zu erstellen.