Back to Documentation
Angular Integration Guide
Learn how to integrate FXRateSync API with Angular using services, reactive patterns, and TypeScript best practices.
What You'll Learn
- • Create Angular services for API integration
- • Implement reactive patterns with RxJS
- • Build type-safe components with TypeScript
- • Handle errors and loading states
- • Create reusable currency components
Prerequisites
This guide assumes you have:
- Angular 15+ installed
- Basic knowledge of TypeScript and RxJS
- FXRateSync API key
Step 1: Create Currency Service
First, create a service to handle all currency-related API calls:
src/app/services/currency.service.ts
import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http'; import { Observable, BehaviorSubject, throwError } from 'rxjs'; import { map, catchError, retry, shareReplay } from 'rxjs/operators'; import { environment } from '../../environments/environment'; export interface ConversionResponse { from: string; to: string; amount: number; converted_amount: number; rate: number; timestamp: string; source: string; } export interface CurrencyRate { code: string; rate: number; name: string; } @Injectable({ providedIn: 'root' }) export class CurrencyService { private readonly API_URL = environment.apiUrl; private readonly API_KEY = environment.apiKey; private loadingSubject = new BehaviorSubject<boolean>(false); public loading$ = this.loadingSubject.asObservable(); private errorSubject = new BehaviorSubject<string | null>(null); public error$ = this.errorSubject.asObservable(); constructor(private http: HttpClient) {} private getHeaders(): HttpHeaders { return new HttpHeaders({ 'Content-Type': 'application/json', 'X-API-Key': this.API_KEY }); } /** * Convert amount from one currency to another */ convert(from: string, to: string, amount: number): Observable<ConversionResponse> { this.loadingSubject.next(true); this.errorSubject.next(null); const url = `${this.API_URL}/api/v1/convert`; const params = { from, to, amount: amount.toString() }; return this.http.get<ConversionResponse>(url, { headers: this.getHeaders(), params }).pipe( retry(2), map(response => ({ ...response, converted_amount: Number(response.converted_amount), rate: Number(response.rate) })), catchError(this.handleError.bind(this)), shareReplay(1), // Always update loading state finalize(() => this.loadingSubject.next(false)) ); } /** * Get latest exchange rates for a base currency */ getLatestRates(baseCurrency: string = 'USD'): Observable<CurrencyRate[]> { const url = `${this.API_URL}/api/v1/latest`; const params = { base: baseCurrency }; return this.http.get<{rates: Record<string, number>}>(url, { headers: this.getHeaders(), params }).pipe( map(response => Object.entries(response.rates).map(([code, rate]) => ({ code, rate, name: this.getCurrencyName(code) })) ), catchError(this.handleError.bind(this)), shareReplay(1) ); } /** * Get supported currencies */ getSupportedCurrencies(): Observable<string[]> { const url = `${this.API_URL}/api/v1/currencies`; return this.http.get<{currencies: {code: string, name: string}[]}>(url, { headers: this.getHeaders() }).pipe( map(response => response.currencies.map(c => c.code)), catchError(this.handleError.bind(this)), shareReplay(1) ); } private handleError(error: HttpErrorResponse): Observable<never> { let errorMessage = 'An unexpected error occurred'; if (error.error instanceof ErrorEvent) { // Client-side error errorMessage = `Error: ${error.error.message}`; } else { // Server-side error switch (error.status) { case 400: errorMessage = 'Invalid request parameters'; break; case 401: errorMessage = 'Invalid API key'; break; case 429: errorMessage = 'Rate limit exceeded. Please try again later.'; break; case 500: errorMessage = 'Server error. Please try again later.'; break; default: errorMessage = `Error Code: ${error.status} Message: ${error.message}`; } } this.errorSubject.next(errorMessage); return throwError(() => new Error(errorMessage)); } private getCurrencyName(code: string): string { const currencyNames: Record<string, string> = { USD: 'US Dollar', EUR: 'Euro', GBP: 'British Pound', JPY: 'Japanese Yen', CAD: 'Canadian Dollar', AUD: 'Australian Dollar', CHF: 'Swiss Franc', CNY: 'Chinese Yuan' }; return currencyNames[code] || code; } }
Step 2: Environment Configuration
Configure your API credentials in the environment files:
src/environments/environment.ts
export const environment = { production: false, apiUrl: 'https://api.fxratesync.io', apiKey: 'your_api_key_here' };
⚠️
Security Note
Never commit API keys to version control. Use environment variables in production.
Step 3: Create Currency Converter Component
Build a reusable component for currency conversion:
src/app/components/currency-converter.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { Subject, debounceTime, takeUntil, distinctUntilChanged } from 'rxjs'; import { CurrencyService, ConversionResponse } from '../services/currency.service'; @Component({ selector: 'app-currency-converter', templateUrl: './currency-converter.component.html', styleUrls: ['./currency-converter.component.scss'] }) export class CurrencyConverterComponent implements OnInit, OnDestroy { converterForm!: FormGroup; result: ConversionResponse | null = null; supportedCurrencies: string[] = []; loading$ = this.currencyService.loading$; error$ = this.currencyService.error$; private destroy$ = new Subject<void>(); constructor( private fb: FormBuilder, private currencyService: CurrencyService ) {} ngOnInit(): void { this.initializeForm(); this.loadSupportedCurrencies(); this.setupAutoConversion(); } ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); } private initializeForm(): void { this.converterForm = this.fb.group({ amount: [100, [Validators.required, Validators.min(0.01)]], fromCurrency: ['USD', Validators.required], toCurrency: ['EUR', Validators.required] }); } private loadSupportedCurrencies(): void { this.currencyService.getSupportedCurrencies() .pipe(takeUntil(this.destroy$)) .subscribe({ next: (currencies) => { this.supportedCurrencies = currencies; }, error: (error) => { console.error('Failed to load currencies:', error); // Fallback currencies this.supportedCurrencies = ['USD', 'EUR', 'GBP', 'JPY', 'CAD', 'AUD']; } }); } private setupAutoConversion(): void { this.converterForm.valueChanges .pipe( debounceTime(500), distinctUntilChanged(), takeUntil(this.destroy$) ) .subscribe(() => { if (this.converterForm.valid) { this.convert(); } }); } convert(): void { if (!this.converterForm.valid) return; const { amount, fromCurrency, toCurrency } = this.converterForm.value; this.currencyService.convert(fromCurrency, toCurrency, amount) .pipe(takeUntil(this.destroy$)) .subscribe({ next: (result) => { this.result = result; }, error: (error) => { console.error('Conversion failed:', error); this.result = null; } }); } swapCurrencies(): void { const fromCurrency = this.converterForm.get('fromCurrency')?.value; const toCurrency = this.converterForm.get('toCurrency')?.value; this.converterForm.patchValue({ fromCurrency: toCurrency, toCurrency: fromCurrency }); } formatCurrency(amount: number, currency: string): string { try { return new Intl.NumberFormat('en-US', { style: 'currency', currency: currency, minimumFractionDigits: 2, maximumFractionDigits: 6 }).format(amount); } catch (error) { return `${amount.toLocaleString()} ${currency}`; } } }
Step 4: Component Template
Create the HTML template for the currency converter:
src/app/components/currency-converter.component.html
<div class="currency-converter"> <h2 class="text-2xl font-bold mb-6">Currency Converter</h2> <form [formGroup]="converterForm" class="space-y-4"> <!-- Amount Input --> <div> <label class="block text-sm font-medium text-foreground mb-1">Amount</label> <input type="number" formControlName="amount" class="w-full px-3 py-2 border border-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Enter amount" step="0.01" min="0.01" /> <div *ngIf="converterForm.get('amount')?.invalid && converterForm.get('amount')?.touched" class="text-destructive text-sm mt-1"> Please enter a valid amount </div> </div> <!-- Currency Selection --> <div class="grid grid-cols-2 gap-4"> <div> <label class="block text-sm font-medium text-foreground mb-1">From</label> <select formControlName="fromCurrency" class="w-full px-3 py-2 border border-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" > <option *ngFor="let currency of supportedCurrencies" [value]="currency"> {{ currency }} </option> </select> </div> <div> <label class="block text-sm font-medium text-foreground mb-1">To</label> <select formControlName="toCurrency" class="w-full px-3 py-2 border border-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" > <option *ngFor="let currency of supportedCurrencies" [value]="currency"> {{ currency }} </option> </select> </div> </div> <!-- Swap Button --> <div class="flex justify-center"> <button type="button" (click)="swapCurrencies()" class="px-4 py-2 bg-muted hover:bg-muted rounded-md transition-colors" > ⇄ Swap </button> </div> <!-- Convert Button --> <button type="button" (click)="convert()" [disabled]="!converterForm.valid || (loading$ | async)" class="w-full px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 disabled:bg-muted transition-colors" > <span *ngIf="loading$ | async">Converting...</span> <span *ngIf="!(loading$ | async)">Convert</span> </button> </form> <!-- Loading State --> <div *ngIf="loading$ | async" class="mt-4 text-center"> <div class="inline-block animate-spin rounded-full h-6 w-6 border-b-2 border-primary"></div> <span class="ml-2 text-muted-foreground">Converting...</span> </div> <!-- Error State --> <div *ngIf="error$ | async as error" class="mt-4 p-4 bg-destructive/10 border border-destructive/30 rounded-md"> <p class="text-destructive-foreground">{{ error }}</p> </div> <!-- Result --> <div *ngIf="result && !(loading$ | async)" class="mt-6 p-4 bg-success/10 border border-success/30 rounded-md"> <h3 class="text-lg font-semibold text-success-foreground mb-2">Conversion Result</h3> <div class="text-2xl font-bold text-success-foreground mb-2"> {{ formatCurrency(result.converted_amount, result.to) }} </div> <div class="text-sm text-success space-y-1"> <p>{{ formatCurrency(result.amount, result.from) }} = {{ formatCurrency(result.converted_amount, result.to) }}</p> <p>Exchange Rate: 1 {{ result.from }} = {{ result.rate }} {{ result.to }}</p> <p>Last Updated: {{ result.timestamp | date:'medium' }}</p> <p>Source: {{ result.source }}</p> </div> </div> </div>
Step 5: Module Configuration
Update your module to include the necessary imports:
src/app/app.module.ts
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { ReactiveFormsModule } from '@angular/forms'; import { HttpClientModule } from '@angular/common/http'; import { AppComponent } from './app.component'; import { CurrencyConverterComponent } from './components/currency-converter.component'; @NgModule({ declarations: [ AppComponent, CurrencyConverterComponent ], imports: [ BrowserModule, ReactiveFormsModule, HttpClientModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
Advanced Features
Currency Rate Display Component
Create a component to display real-time currency rates:
src/app/components/currency-rates.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Subject, interval, switchMap, takeUntil } from 'rxjs'; import { CurrencyService, CurrencyRate } from '../services/currency.service'; @Component({ selector: 'app-currency-rates', template: ` <div class="currency-rates"> <h3 class="text-lg font-semibold mb-4">Live Exchange Rates</h3> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> <div *ngFor="let rate of rates" class="p-4 bg-background rounded-lg shadow"> <div class="flex justify-between items-center"> <span class="font-medium">{{ rate.code }}</span> <span class="text-lg font-bold">{{ rate.rate | number:'1.2-6' }}</span> </div> <div class="text-sm text-muted-foreground">{{ rate.name }}</div> </div> </div> </div> ` }) export class CurrencyRatesComponent implements OnInit, OnDestroy { rates: CurrencyRate[] = []; private destroy$ = new Subject<void>(); constructor(private currencyService: CurrencyService) {} ngOnInit(): void { // Load rates immediately and then every 60 seconds this.loadRates(); interval(60000) .pipe( switchMap(() => this.currencyService.getLatestRates()), takeUntil(this.destroy$) ) .subscribe(rates => this.rates = rates); } ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); } private loadRates(): void { this.currencyService.getLatestRates() .pipe(takeUntil(this.destroy$)) .subscribe(rates => this.rates = rates); } }
Best Practices
- Use Services: Centralize API logic in services for reusability
- Reactive Programming: Leverage RxJS for handling async operations
- Error Handling: Implement comprehensive error handling strategies
- Type Safety: Use TypeScript interfaces for API responses
- Performance: Use shareReplay() to cache API responses
- Memory Management: Always unsubscribe to prevent memory leaks
Testing
Example unit test for the currency service:
src/app/services/currency.service.spec.ts
import { TestBed } from '@angular/core/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { CurrencyService } from './currency.service'; describe('CurrencyService', () => { let service: CurrencyService; let httpMock: HttpTestingController; beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule] }); service = TestBed.inject(CurrencyService); httpMock = TestBed.inject(HttpTestingController); }); afterEach(() => { httpMock.verify(); }); it('should convert currency successfully', () => { const mockResponse = { from: 'USD', to: 'EUR', amount: 100, converted_amount: 88.17, rate: 0.8817, timestamp: '2024-01-15T12:00:00Z', source: 'ecb' }; service.convert('USD', 'EUR', 100).subscribe(result => { expect(result).toEqual(mockResponse); }); const req = httpMock.expectOne(req => req.url.includes('/api/v1/convert')); expect(req.request.method).toBe('GET'); req.flush(mockResponse); }); });
Complete Angular Example
Want to see a complete Angular application with FXRateSync?