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?