Skip to main content

HTTP Client ile Backend Entegrasyonu

Bu bölümde, Angular’ın HTTP Client modülünü kullanarak Spring Boot backend API’si ile nasıl entegre olunacağını detaylı olarak ele alacağız. HTTP Client, RESTful API’lerle iletişim kurmak için Angular tarafından sağlanan güçlü bir modüldür.

HTTP Client Nedir?

Angular’ın HTTP Client modülü (HttpClient), HTTP istekleri göndermek ve yanıtları almak için kullanılan bir servistir. Bu modül, Observable tabanlı bir API sağlar ve modern web uygulamalarında yaygın olarak kullanılan RESTful API’lerle etkileşim için idealdir. HTTP Client’ın temel özellikleri şunlardır:
  1. Observable Tabanlı API: RxJS Observable’ları kullanarak asenkron HTTP istekleri yapar.
  2. JSON Dönüşümü: HTTP yanıtlarını otomatik olarak JSON nesnelerine dönüştürür.
  3. Tip Güvenliği: TypeScript ile tip güvenliği sağlar.
  4. Interceptor Desteği: HTTP isteklerini ve yanıtlarını yakalamak ve değiştirmek için interceptor’lar sağlar.
  5. Hata İşleme: HTTP hatalarını yakalamak ve işlemek için mekanizmalar sağlar.
  6. İstek Yapılandırması: İstek başlıkları, parametreler ve diğer yapılandırma seçenekleri için destek sağlar.

HTTP Client Kurulumu

HTTP Client’ı kullanmak için, HttpClientModule’ü import etmeniz gerekir:
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
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Temel HTTP İstekleri

HTTP Client, HTTP protokolünün tüm standart metodlarını destekler: GET, POST, PUT, DELETE, PATCH, HEAD ve OPTIONS.

GET İsteği

GET isteği, sunucudan veri almak için kullanılır:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from '../../environments/environment';
import { Product } from '../models/product.model';

@Injectable({
  providedIn: 'root'
})
export class ProductService {
  private apiUrl = `${environment.apiUrl}/products`;
  
  constructor(private http: HttpClient) { }
  
  getProducts(): Observable<Product[]> {
    return this.http.get<Product[]>(this.apiUrl);
  }
  
  getProductById(id: number): Observable<Product> {
    return this.http.get<Product>(`${this.apiUrl}/${id}`);
  }
}
Bu örnekte, ProductService sınıfı, ürünleri almak için iki GET isteği yapar. getProducts() metodu, tüm ürünleri alır ve getProductById() metodu, belirli bir ürünü alır.

POST İsteği

POST isteği, sunucuya veri göndermek için kullanılır:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from '../../environments/environment';
import { Product } from '../models/product.model';

@Injectable({
  providedIn: 'root'
})
export class ProductService {
  private apiUrl = `${environment.apiUrl}/products`;
  
  constructor(private http: HttpClient) { }
  
  createProduct(product: Product): Observable<Product> {
    return this.http.post<Product>(this.apiUrl, product);
  }
}
Bu örnekte, createProduct() metodu, yeni bir ürün oluşturmak için bir POST isteği yapar.

PUT İsteği

PUT isteği, mevcut bir kaynağı güncellemek için kullanılır:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from '../../environments/environment';
import { Product } from '../models/product.model';

@Injectable({
  providedIn: 'root'
})
export class ProductService {
  private apiUrl = `${environment.apiUrl}/products`;
  
  constructor(private http: HttpClient) { }
  
  updateProduct(id: number, product: Product): Observable<Product> {
    return this.http.put<Product>(`${this.apiUrl}/${id}`, product);
  }
}
Bu örnekte, updateProduct() metodu, mevcut bir ürünü güncellemek için bir PUT isteği yapar.

DELETE İsteği

DELETE isteği, bir kaynağı silmek için kullanılır:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from '../../environments/environment';

@Injectable({
  providedIn: 'root'
})
export class ProductService {
  private apiUrl = `${environment.apiUrl}/products`;
  
  constructor(private http: HttpClient) { }
  
  deleteProduct(id: number): Observable<void> {
    return this.http.delete<void>(`${this.apiUrl}/${id}`);
  }
}
Bu örnekte, deleteProduct() metodu, bir ürünü silmek için bir DELETE isteği yapar.

PATCH İsteği

PATCH isteği, bir kaynağın belirli alanlarını güncellemek için kullanılır:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from '../../environments/environment';
import { Product } from '../models/product.model';

@Injectable({
  providedIn: 'root'
})
export class ProductService {
  private apiUrl = `${environment.apiUrl}/products`;
  
  constructor(private http: HttpClient) { }
  
  updateProductPrice(id: number, price: number): Observable<Product> {
    return this.http.patch<Product>(`${this.apiUrl}/${id}`, { price });
  }
}
Bu örnekte, updateProductPrice() metodu, bir ürünün fiyatını güncellemek için bir PATCH isteği yapar.

HTTP İstek Yapılandırması

HTTP Client, istek başlıkları, parametreler ve diğer yapılandırma seçenekleri için destek sağlar.

HTTP Başlıkları

HTTP başlıkları, istek hakkında ek bilgiler sağlamak için kullanılır:
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from '../../environments/environment';
import { Product } from '../models/product.model';

@Injectable({
  providedIn: 'root'
})
export class ProductService {
  private apiUrl = `${environment.apiUrl}/products`;
  
  constructor(private http: HttpClient) { }
  
  createProduct(product: Product): Observable<Product> {
    const headers = new HttpHeaders({
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${localStorage.getItem('token')}`
    });
    
    return this.http.post<Product>(this.apiUrl, product, { headers });
  }
}
Bu örnekte, createProduct() metodu, bir ürün oluşturmak için bir POST isteği yapar ve istek başlıklarını ayarlar.

HTTP Parametreleri

HTTP parametreleri, URL’ye ek parametreler eklemek için kullanılır:
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from '../../environments/environment';
import { Product } from '../models/product.model';
import { PaginatedResponse } from '../models/paginated-response.model';

@Injectable({
  providedIn: 'root'
})
export class ProductService {
  private apiUrl = `${environment.apiUrl}/products`;
  
  constructor(private http: HttpClient) { }
  
  getProducts(page = 0, size = 10, sort = 'id,asc'): Observable<PaginatedResponse<Product>> {
    let params = new HttpParams()
      .set('page', page.toString())
      .set('size', size.toString())
      .set('sort', sort);
    
    return this.http.get<PaginatedResponse<Product>>(this.apiUrl, { params });
  }
  
  searchProducts(keyword: string, category?: string, minPrice?: number, maxPrice?: number): Observable<Product[]> {
    let params = new HttpParams().set('keyword', keyword);
    
    if (category) {
      params = params.set('category', category);
    }
    
    if (minPrice !== undefined) {
      params = params.set('minPrice', minPrice.toString());
    }
    
    if (maxPrice !== undefined) {
      params = params.set('maxPrice', maxPrice.toString());
    }
    
    return this.http.get<Product[]>(`${this.apiUrl}/search`, { params });
  }
}
Bu örnekte, getProducts() metodu, sayfalama ve sıralama parametrelerini ayarlar ve searchProducts() metodu, arama parametrelerini ayarlar.

Yanıt Türü

HTTP Client, yanıt türünü belirtmek için jenerik tip parametresi kullanır:
import { Injectable } from '@angular/core';
import { HttpClient, HttpResponse } from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from '../../environments/environment';
import { Product } from '../models/product.model';

@Injectable({
  providedIn: 'root'
})
export class ProductService {
  private apiUrl = `${environment.apiUrl}/products`;
  
  constructor(private http: HttpClient) { }
  
  getProductWithFullResponse(id: number): Observable<HttpResponse<Product>> {
    return this.http.get<Product>(`${this.apiUrl}/${id}`, { observe: 'response' });
  }
  
  downloadProductImage(id: number): Observable<Blob> {
    return this.http.get(`${this.apiUrl}/${id}/image`, { responseType: 'blob' });
  }
  
  getProductAsText(id: number): Observable<string> {
    return this.http.get(`${this.apiUrl}/${id}`, { responseType: 'text' });
  }
}
Bu örnekte, getProductWithFullResponse() metodu, tam HTTP yanıtını (başlıklar ve durum kodu dahil) alır, downloadProductImage() metodu, bir blob olarak yanıt alır ve getProductAsText() metodu, metin olarak yanıt alır.

HTTP Interceptor’lar

HTTP Interceptor’lar, HTTP isteklerini ve yanıtlarını yakalamak ve değiştirmek için kullanılır. Interceptor’lar, kimlik doğrulama, loglama, hata işleme ve diğer çapraz kesit endişeleri için kullanışlıdır.

Auth Interceptor

Auth Interceptor, her HTTP isteğine kimlik doğrulama token’ını eklemek için kullanılır:
import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { Observable } from 'rxjs';
import { AuthService } from '../services/auth.service';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(private authService: AuthService) { }
  
  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    const token = this.authService.getToken();
    
    if (token) {
      request = request.clone({
        setHeaders: {
          Authorization: `Bearer ${token}`
        }
      });
    }
    
    return next.handle(request);
  }
}
Bu interceptor, her HTTP isteğine Authorization başlığını ekler.

Error Interceptor

Error Interceptor, HTTP hatalarını yakalamak ve işlemek için kullanılır:
import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { Router } from '@angular/router';
import { AuthService } from '../services/auth.service';
import { NotificationService } from '../services/notification.service';

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
  constructor(
    private authService: AuthService,
    private notificationService: NotificationService,
    private router: Router
  ) { }
  
  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    return next.handle(request).pipe(
      catchError((error: HttpErrorResponse) => {
        let errorMessage = 'Bir hata oluştu.';
        
        if (error.error instanceof ErrorEvent) {
          // İstemci taraflı hata
          errorMessage = `Hata: ${error.error.message}`;
        } else {
          // Sunucu taraflı hata
          if (error.status === 401) {
            // Kimlik doğrulama hatası
            this.authService.logout();
            this.router.navigate(['/auth/login']);
            errorMessage = 'Oturum süreniz doldu. Lütfen tekrar giriş yapın.';
          } else if (error.status === 403) {
            // Yetkilendirme hatası
            errorMessage = 'Bu işlemi gerçekleştirmek için yetkiniz yok.';
          } else if (error.status === 404) {
            // Kaynak bulunamadı hatası
            errorMessage = 'İstenen kaynak bulunamadı.';
          } else if (error.status === 500) {
            // Sunucu hatası
            errorMessage = 'Sunucu hatası. Lütfen daha sonra tekrar deneyin.';
          } else if (error.error && error.error.message) {
            // Özel hata mesajı
            errorMessage = error.error.message;
          }
        }
        
        this.notificationService.error(errorMessage);
        return throwError(errorMessage);
      })
    );
  }
}
Bu interceptor, HTTP hatalarını yakalar, hata türüne göre uygun mesajları gösterir ve gerekirse kullanıcıyı yönlendirir.

Loading Interceptor

Loading Interceptor, HTTP istekleri sırasında yükleme göstergesini göstermek için kullanılır:
import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { Observable } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { LoadingService } from '../services/loading.service';

@Injectable()
export class LoadingInterceptor implements HttpInterceptor {
  private activeRequests = 0;
  
  constructor(private loadingService: LoadingService) { }
  
  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    if (this.activeRequests === 0) {
      this.loadingService.show();
    }
    
    this.activeRequests++;
    
    return next.handle(request).pipe(
      finalize(() => {
        this.activeRequests--;
        if (this.activeRequests === 0) {
          this.loadingService.hide();
        }
      })
    );
  }
}
Bu interceptor, aktif istek sayısını takip eder ve gerektiğinde yükleme göstergesini gösterir veya gizler.

Caching Interceptor

Caching Interceptor, HTTP yanıtlarını önbelleğe almak için kullanılır:
import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor, HttpResponse } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { tap, shareReplay } from 'rxjs/operators';

@Injectable()
export class CachingInterceptor implements HttpInterceptor {
  private cache = new Map<string, HttpResponse<any>>();
  
  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    // Yalnızca GET isteklerini önbelleğe al
    if (request.method !== 'GET') {
      return next.handle(request);
    }
    
    // Önbelleğe alınmaması gereken istekleri kontrol et
    if (request.headers.has('x-no-cache')) {
      return next.handle(request);
    }
    
    const cachedResponse = this.cache.get(request.urlWithParams);
    if (cachedResponse) {
      return of(cachedResponse.clone());
    }
    
    return next.handle(request).pipe(
      tap(event => {
        if (event instanceof HttpResponse) {
          this.cache.set(request.urlWithParams, event.clone());
        }
      }),
      shareReplay(1)
    );
  }
}
Bu interceptor, GET isteklerinin yanıtlarını önbelleğe alır ve aynı istek tekrar yapıldığında önbellekten yanıt döndürür.

Interceptor’ları Kaydetme

Interceptor’ları kullanmak için, bunları providers dizisine eklemeniz gerekir:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

import { AuthInterceptor } from './core/interceptors/auth.interceptor';
import { ErrorInterceptor } from './core/interceptors/error.interceptor';
import { LoadingInterceptor } from './core/interceptors/loading.interceptor';
import { CachingInterceptor } from './core/interceptors/caching.interceptor';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    AppRoutingModule
  ],
  providers: [
    { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
    { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
    { provide: HTTP_INTERCEPTORS, useClass: LoadingInterceptor, multi: true },
    { provide: HTTP_INTERCEPTORS, useClass: CachingInterceptor, multi: true }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }
Interceptor’lar, sağlandıkları sırayla çalıştırılır. Bu örnekte, AuthInterceptor ilk olarak çalıştırılır, ardından ErrorInterceptor, LoadingInterceptor ve CachingInterceptor çalıştırılır.

HTTP İsteklerini Kullanma

HTTP isteklerini komponentlerde kullanmak için, ilgili servisi enjekte etmeniz ve metodları çağırmanız gerekir:
import { Component, OnInit } from '@angular/core';
import { ProductService } from '../../core/services/product.service';
import { Product } from '../../core/models/product.model';

@Component({
  selector: 'app-product-list',
  templateUrl: './product-list.component.html',
  styleUrls: ['./product-list.component.scss']
})
export class ProductListComponent implements OnInit {
  products: Product[] = [];
  loading = false;
  error: string | null = null;
  
  constructor(private productService: ProductService) { }
  
  ngOnInit(): void {
    this.loadProducts();
  }
  
  loadProducts(): void {
    this.loading = true;
    this.error = null;
    
    this.productService.getProducts().subscribe(
      response => {
        this.products = response.content;
        this.loading = false;
      },
      error => {
        this.error = 'Ürünler yüklenirken bir hata oluştu.';
        this.loading = false;
        console.error('Ürünler yüklenirken hata:', error);
      }
    );
  }
  
  deleteProduct(id: number): void {
    if (confirm('Bu ürünü silmek istediğinizden emin misiniz?')) {
      this.productService.deleteProduct(id).subscribe(
        () => {
          this.products = this.products.filter(product => product.id !== id);
        },
        error => {
          console.error('Ürün silinirken hata:', error);
        }
      );
    }
  }
}
Bu örnekte, ProductListComponent sınıfı, ProductService’i kullanarak ürünleri yükler ve siler.

HTTP İsteklerini Zincirleme

Bazen, bir HTTP isteğinin sonucuna bağlı olarak başka bir HTTP isteği yapmak isteyebilirsiniz. Bu durumda, RxJS operatörlerini kullanarak HTTP isteklerini zincirleyebilirsiniz:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { switchMap } from 'rxjs/operators';
import { ProductService } from '../../core/services/product.service';
import { CategoryService } from '../../core/services/category.service';
import { Product } from '../../core/models/product.model';
import { Category } from '../../core/models/category.model';

@Component({
  selector: 'app-product-detail',
  templateUrl: './product-detail.component.html',
  styleUrls: ['./product-detail.component.scss']
})
export class ProductDetailComponent implements OnInit {
  product: Product | null = null;
  category: Category | null = null;
  relatedProducts: Product[] = [];
  loading = false;
  error: string | null = null;
  
  constructor(
    private route: ActivatedRoute,
    private productService: ProductService,
    private categoryService: CategoryService
  ) { }
  
  ngOnInit(): void {
    this.loading = true;
    this.error = null;
    
    this.route.paramMap.pipe(
      switchMap(params => {
        const id = Number(params.get('id'));
        return this.productService.getProductById(id);
      }),
      switchMap(product => {
        this.product = product;
        return this.categoryService.getCategoryById(product.categoryId);
      }),
      switchMap(category => {
        this.category = category;
        return this.productService.getProductsByCategory(category.id);
      })
    ).subscribe(
      response => {
        this.relatedProducts = response.content.filter(p => p.id !== this.product?.id).slice(0, 4);
        this.loading = false;
      },
      error => {
        this.error = 'Ürün detayları yüklenirken bir hata oluştu.';
        this.loading = false;
        console.error('Ürün detayları yüklenirken hata:', error);
      }
    );
  }
}
Bu örnekte, ProductDetailComponent sınıfı, üç HTTP isteğini zincirler:
  1. Ürün detaylarını almak için bir istek
  2. Ürünün kategorisini almak için bir istek
  3. İlgili ürünleri almak için bir istek
switchMap operatörü, bir Observable’ın sonucunu alır ve başka bir Observable döndürür.

Paralel HTTP İstekleri

Bazen, birden fazla HTTP isteğini paralel olarak yapmak ve tüm sonuçları beklemek isteyebilirsiniz. Bu durumda, forkJoin operatörünü kullanabilirsiniz:
import { Component, OnInit } from '@angular/core';
import { forkJoin } from 'rxjs';
import { ProductService } from '../../core/services/product.service';
import { CategoryService } from '../../core/services/category.service';
import { Product } from '../../core/models/product.model';
import { Category } from '../../core/models/category.model';

@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.scss']
})
export class DashboardComponent implements OnInit {
  featuredProducts: Product[] = [];
  newProducts: Product[] = [];
  categories: Category[] = [];
  loading = false;
  error: string | null = null;
  
  constructor(
    private productService: ProductService,
    private categoryService: CategoryService
  ) { }
  
  ngOnInit(): void {
    this.loading = true;
    this.error = null;
    
    forkJoin({
      featured: this.productService.getFeaturedProducts(),
      new: this.productService.getNewProducts(),
      categories: this.categoryService.getCategories()
    }).subscribe(
      results => {
        this.featuredProducts = results.featured;
        this.newProducts = results.new;
        this.categories = results.categories;
        this.loading = false;
      },
      error => {
        this.error = 'Veriler yüklenirken bir hata oluştu.';
        this.loading = false;
        console.error('Veriler yüklenirken hata:', error);
      }
    );
  }
}
Bu örnekte, DashboardComponent sınıfı, üç HTTP isteğini paralel olarak yapar ve tüm sonuçları bekler.

Retry Mekanizması

Bazen, HTTP istekleri geçici ağ sorunları nedeniyle başarısız olabilir. Bu durumda, retry veya retryWhen operatörlerini kullanarak istekleri yeniden deneyebilirsiniz:
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError, timer } from 'rxjs';
import { retry, retryWhen, delayWhen, scan, catchError } from 'rxjs/operators';
import { environment } from '../../environments/environment';
import { Product } from '../models/product.model';

@Injectable({
  providedIn: 'root'
})
export class ProductService {
  private apiUrl = `${environment.apiUrl}/products`;
  
  constructor(private http: HttpClient) { }
  
  getProducts(): Observable<Product[]> {
    return this.http.get<Product[]>(this.apiUrl).pipe(
      retry(3), // Başarısız olursa 3 kez daha dene
      catchError(this.handleError)
    );
  }
  
  getProductById(id: number): Observable<Product> {
    return this.http.get<Product>(`${this.apiUrl}/${id}`).pipe(
      retryWhen(errors => 
        errors.pipe(
          scan((count, error) => {
            if (count >= 3) {
              throw error;
            }
            return count + 1;
          }, 0),
          delayWhen(count => timer(count * 1000)) // Her denemede artan gecikme
        )
      ),
      catchError(this.handleError)
    );
  }
  
  private handleError(error: HttpErrorResponse) {
    let errorMessage = 'Bir hata oluştu.';
    
    if (error.error instanceof ErrorEvent) {
      // İstemci taraflı hata
      errorMessage = `Hata: ${error.error.message}`;
    } else {
      // Sunucu taraflı hata
      errorMessage = `Hata kodu: ${error.status}, Mesaj: ${error.message}`;
    }
    
    console.error(errorMessage);
    return throwError(errorMessage);
  }
}
Bu örnekte, getProducts() metodu, başarısız olursa 3 kez daha denenir ve getProductById() metodu, her denemede artan bir gecikme ile 3 kez daha denenir.

Dosya Yükleme

HTTP Client, dosya yükleme işlemleri için de kullanılabilir:
import { Injectable } from '@angular/core';
import { HttpClient, HttpEvent, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from '../../environments/environment';

@Injectable({
  providedIn: 'root'
})
export class FileUploadService {
  private apiUrl = `${environment.apiUrl}/files`;
  
  constructor(private http: HttpClient) { }
  
  uploadFile(file: File): Observable<any> {
    const formData = new FormData();
    formData.append('file', file, file.name);
    
    return this.http.post(`${this.apiUrl}/upload`, formData);
  }
  
  uploadFileWithProgress(file: File): Observable<HttpEvent<any>> {
    const formData = new FormData();
    formData.append('file', file, file.name);
    
    const request = new HttpRequest('POST', `${this.apiUrl}/upload`, formData, {
      reportProgress: true
    });
    
    return this.http.request(request);
  }
  
  uploadMultipleFiles(files: File[]): Observable<any> {
    const formData = new FormData();
    
    for (let i = 0; i < files.length; i++) {
      formData.append('files', files[i], files[i].name);
    }
    
    return this.http.post(`${this.apiUrl}/upload-multiple`, formData);
  }
}
Bu örnekte, FileUploadService sınıfı, dosya yükleme işlemleri için üç metod sağlar: uploadFile(), uploadFileWithProgress() ve uploadMultipleFiles(). Dosya yükleme işlemini bir komponentte kullanmak için:
import { Component } from '@angular/core';
import { HttpEventType, HttpResponse } from '@angular/common/http';
import { FileUploadService } from '../../core/services/file-upload.service';

@Component({
  selector: 'app-file-upload',
  templateUrl: './file-upload.component.html',
  styleUrls: ['./file-upload.component.scss']
})
export class FileUploadComponent {
  selectedFiles?: FileList;
  currentFile?: File;
  progress = 0;
  message = '';
  
  constructor(private fileUploadService: FileUploadService) { }
  
  selectFile(event: any): void {
    this.selectedFiles = event.target.files;
  }
  
  upload(): void {
    if (this.selectedFiles) {
      const file: File | null = this.selectedFiles.item(0);
      
      if (file) {
        this.currentFile = file;
        this.progress = 0;
        
        this.fileUploadService.uploadFileWithProgress(this.currentFile).subscribe(
          event => {
            if (event.type === HttpEventType.UploadProgress) {
              this.progress = Math.round(100 * event.loaded / (event.total || 1));
            } else if (event instanceof HttpResponse) {
              this.message = event.body.message;
            }
          },
          err => {
            this.progress = 0;
            this.message = 'Dosya yüklenemedi!';
            this.currentFile = undefined;
          }
        );
      }
      
      this.selectedFiles = undefined;
    }
  }
}
Bu örnekte, FileUploadComponent sınıfı, dosya seçme ve yükleme işlemlerini gerçekleştirir ve yükleme ilerlemesini gösterir.

Spring Boot Backend ile Entegrasyon

Angular uygulamasını Spring Boot backend ile entegre etmek için, HTTP Client’ı kullanarak Spring Boot API’sine istekler gönderebilirsiniz. Spring Boot API’si, RESTful endpoint’ler sağlar ve Angular uygulaması bu endpoint’lere HTTP istekleri gönderir.

API URL Yapılandırması

API URL’sini yapılandırmak için, environment.ts ve environment.prod.ts dosyalarını kullanabilirsiniz:
// environment.ts (Development)
export const environment = {
  production: false,
  apiUrl: 'http://localhost:8080/api'
};

// environment.prod.ts (Production)
export const environment = {
  production: true,
  apiUrl: 'https://api.example.com/api'
};
Bu yapılandırma, geliştirme ortamında yerel bir API’ye ve üretim ortamında gerçek bir API’ye bağlanmanızı sağlar.

Model Sınıfları

Spring Boot API’sinden dönen verileri temsil etmek için model sınıfları oluşturabilirsiniz:
// product.model.ts
export interface Product {
  id: number;
  name: string;
  description: string;
  price: number;
  stock: number;
  imageUrl: string;
  categoryId: number;
  createdAt: string;
  updatedAt: string;
}

// category.model.ts
export interface Category {
  id: number;
  name: string;
  description: string;
  parentId?: number;
}

// paginated-response.model.ts
export interface PaginatedResponse<T> {
  content: T[];
  totalElements: number;
  totalPages: number;
  size: number;
  number: number;
}

// auth-response.model.ts
export interface AuthResponse {
  token: string;
  user: User;
}

// user.model.ts
export interface User {
  id: number;
  username: string;
  email: string;
  role: string;
  createdAt: string;
}
Bu model sınıfları, Spring Boot API’sinden dönen JSON verilerini TypeScript nesnelerine dönüştürmek için kullanılır.

Servis Sınıfları

Spring Boot API’sine istekler göndermek için servis sınıfları oluşturabilirsiniz:
// auth.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { map, tap, catchError } from 'rxjs/operators';
import { Router } from '@angular/router';
import { environment } from '../../../environments/environment';
import { User } from '../models/user.model';
import { AuthResponse } from '../models/auth-response.model';
import { JwtHelperService } from '@auth0/angular-jwt';

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  private apiUrl = `${environment.apiUrl}/auth`;
  private currentUserSubject = new BehaviorSubject<User | null>(null);
  private jwtHelper = new JwtHelperService();
  
  currentUser$ = this.currentUserSubject.asObservable();
  isAuthenticated$ = this.currentUser$.pipe(map(user => !!user));
  
  constructor(
    private http: HttpClient,
    private router: Router
  ) {
    this.loadUserFromLocalStorage();
  }
  
  login(username: string, password: string): Observable<AuthResponse> {
    return this.http.post<AuthResponse>(`${this.apiUrl}/login`, { username, password })
      .pipe(
        tap(response => {
          this.setSession(response);
          this.currentUserSubject.next(response.user);
        })
      );
  }
  
  register(user: User): Observable<User> {
    return this.http.post<User>(`${this.apiUrl}/register`, user);
  }
  
  logout(): void {
    localStorage.removeItem('token');
    localStorage.removeItem('user');
    this.currentUserSubject.next(null);
    this.router.navigate(['/auth/login']);
  }
  
  isAuthenticated(): boolean {
    const token = localStorage.getItem('token');
    return !!token && !this.jwtHelper.isTokenExpired(token);
  }
  
  getCurrentUser(): User | null {
    return this.currentUserSubject.value;
  }
  
  getToken(): string | null {
    return localStorage.getItem('token');
  }
  
  hasRole(role: string): boolean {
    const user = this.getCurrentUser();
    return !!user && user.role === role;
  }
  
  private setSession(authResult: AuthResponse): void {
    localStorage.setItem('token', authResult.token);
    localStorage.setItem('user', JSON.stringify(authResult.user));
  }
  
  private loadUserFromLocalStorage(): void {
    const token = localStorage.getItem('token');
    const userJson = localStorage.getItem('user');
    
    if (token && userJson && !this.jwtHelper.isTokenExpired(token)) {
      try {
        const user = JSON.parse(userJson);
        this.currentUserSubject.next(user);
      } catch (error) {
        console.error('Kullanıcı yüklenirken hata:', error);
        this.currentUserSubject.next(null);
      }
    } else {
      this.currentUserSubject.next(null);
      localStorage.removeItem('token');
      localStorage.removeItem('user');
    }
  }
}

// product.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from '../../../environments/environment';
import { Product } from '../models/product.model';
import { PaginatedResponse } from '../models/paginated-response.model';

@Injectable({
  providedIn: 'root'
})
export class ProductService {
  private apiUrl = `${environment.apiUrl}/products`;
  
  constructor(private http: HttpClient) { }
  
  getProducts(page = 0, size = 10, sort = 'id,asc'): Observable<PaginatedResponse<Product>> {
    let params = new HttpParams()
      .set('page', page.toString())
      .set('size', size.toString())
      .set('sort', sort);
    
    return this.http.get<PaginatedResponse<Product>>(this.apiUrl, { params });
  }
  
  getProductById(id: number): Observable<Product> {
    return this.http.get<Product>(`${this.apiUrl}/${id}`);
  }
  
  getProductsByCategory(categoryId: number, page = 0, size = 10, sort = 'id,asc'): Observable<PaginatedResponse<Product>> {
    let params = new HttpParams()
      .set('page', page.toString())
      .set('size', size.toString())
      .set('sort', sort);
    
    return this.http.get<PaginatedResponse<Product>>(`${this.apiUrl}/category/${categoryId}`, { params });
  }
  
  searchProducts(keyword: string, page = 0, size = 10, sort = 'id,asc'): Observable<PaginatedResponse<Product>> {
    let params = new HttpParams()
      .set('keyword', keyword)
      .set('page', page.toString())
      .set('size', size.toString())
      .set('sort', sort);
    
    return this.http.get<PaginatedResponse<Product>>(`${this.apiUrl}/search`, { params });
  }
  
  createProduct(product: Product): Observable<Product> {
    return this.http.post<Product>(this.apiUrl, product);
  }
  
  updateProduct(id: number, product: Product): Observable<Product> {
    return this.http.put<Product>(`${this.apiUrl}/${id}`, product);
  }
  
  deleteProduct(id: number): Observable<void> {
    return this.http.delete<void>(`${this.apiUrl}/${id}`);
  }
}

// category.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from '../../../environments/environment';
import { Category } from '../models/category.model';

@Injectable({
  providedIn: 'root'
})
export class CategoryService {
  private apiUrl = `${environment.apiUrl}/categories`;
  
  constructor(private http: HttpClient) { }
  
  getCategories(): Observable<Category[]> {
    return this.http.get<Category[]>(this.apiUrl);
  }
  
  getCategoryById(id: number): Observable<Category> {
    return this.http.get<Category>(`${this.apiUrl}/${id}`);
  }
  
  createCategory(category: Category): Observable<Category> {
    return this.http.post<Category>(this.apiUrl, category);
  }
  
  updateCategory(id: number, category: Category): Observable<Category> {
    return this.http.put<Category>(`${this.apiUrl}/${id}`, category);
  }
  
  deleteCategory(id: number): Observable<void> {
    return this.http.delete<void>(`${this.apiUrl}/${id}`);
  }
}

// order.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from '../../../environments/environment';
import { Order } from '../models/order.model';
import { PaginatedResponse } from '../models/paginated-response.model';

@Injectable({
  providedIn: 'root'
})
export class OrderService {
  private apiUrl = `${environment.apiUrl}/orders`;
  
  constructor(private http: HttpClient) { }
  
  getOrders(page = 0, size = 10, sort = 'id,desc'): Observable<PaginatedResponse<Order>> {
    let params = new HttpParams()
      .set('page', page.toString())
      .set('size', size.toString())
      .set('sort', sort);
    
    return this.http.get<PaginatedResponse<Order>>(this.apiUrl, { params });
  }
  
  getOrderById(id: number): Observable<Order> {
    return this.http.get<Order>(`${this.apiUrl}/${id}`);
  }
  
  getOrdersByUser(userId: number, page = 0, size = 10, sort = 'id,desc'): Observable<PaginatedResponse<Order>> {
    let params = new HttpParams()
      .set('page', page.toString())
      .set('size', size.toString())
      .set('sort', sort);
    
    return this.http.get<PaginatedResponse<Order>>(`${this.apiUrl}/user/${userId}`, { params });
  }
  
  createOrder(order: Order): Observable<Order> {
    return this.http.post<Order>(this.apiUrl, order);
  }
  
  updateOrderStatus(id: number, status: string): Observable<Order> {
    let params = new HttpParams()
      .set('status', status);
    
    return this.http.patch<Order>(`${this.apiUrl}/${id}/status`, null, { params });
  }
  
  deleteOrder(id: number): Observable<void> {
    return this.http.delete<void>(`${this.apiUrl}/${id}`);
  }
}
Bu servis sınıfları, Spring Boot API’sine istekler göndermek için HTTP Client’ı kullanır.

CORS Yapılandırması

Cross-Origin Resource Sharing (CORS), farklı kaynaklardan gelen HTTP isteklerine izin vermek için bir mekanizmadır. Angular uygulaması ve Spring Boot API’si farklı kaynaklarda çalıştığında, CORS yapılandırması gereklidir. Spring Boot tarafında CORS yapılandırması:
@Configuration
public class CorsConfig implements WebMvcConfigurer {
    
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("http://localhost:4200")
                .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")
                .allowedHeaders("*")
                .allowCredentials(true);
    }
}
Bu yapılandırma, http://localhost:4200 kaynağından gelen isteklere izin verir.

Sonuç

Bu bölümde, Angular’ın HTTP Client modülünü kullanarak Spring Boot backend API’si ile nasıl entegre olunacağını detaylı olarak ele aldık. HTTP Client, RESTful API’lerle iletişim kurmak için Angular tarafından sağlanan güçlü bir modüldür. Temel HTTP isteklerini, HTTP istek yapılandırmasını, HTTP interceptor’ları, HTTP isteklerini kullanma, zincirleme ve paralel HTTP isteklerini, retry mekanizmasını, dosya yükleme işlemlerini ve Spring Boot backend ile entegrasyonu inceledik. Bir sonraki bölümde, RxJS ile asenkron işlemleri ele alacağız.