|
| 1 | +# @unirate/angular |
| 2 | + |
| 3 | +Official Angular module for the [UniRate API](https://unirateapi.com) — free |
| 4 | +currency exchange rates, historical data, and VAT rates. |
| 5 | + |
| 6 | +- Observable-based `UniRateService` with full API parity |
| 7 | +- `UniRateModule.forRoot()` for NgModule apps |
| 8 | +- `provideUniRate()` for standalone Angular 16+ apps |
| 9 | +- `currencyRate` and `currencyConvert` pipes (use with Angular's built-in `async` pipe) |
| 10 | +- Zero runtime dependencies (peer deps: `@angular/core`, `@angular/common`, `rxjs`) |
| 11 | +- Compiled with [ng-packagr](https://github.com/ng-packagr/ng-packagr) in Angular Package Format (APF) — AOT and Ivy compatible |
| 12 | + |
| 13 | +## Install |
| 14 | + |
| 15 | +```bash |
| 16 | +npm install @unirate/angular |
| 17 | +``` |
| 18 | + |
| 19 | +Requires Angular 16–22 and RxJS 7. |
| 20 | + |
| 21 | +## Quick start |
| 22 | + |
| 23 | +### Standalone app (`app.config.ts`) |
| 24 | + |
| 25 | +```ts |
| 26 | +import { provideUniRate } from '@unirate/angular'; |
| 27 | + |
| 28 | +export const appConfig: ApplicationConfig = { |
| 29 | + providers: [ |
| 30 | + provideUniRate({ apiKey: environment.UNIRATE_API_KEY }), |
| 31 | + ], |
| 32 | +}; |
| 33 | +``` |
| 34 | + |
| 35 | +### NgModule app (`app.module.ts`) |
| 36 | + |
| 37 | +```ts |
| 38 | +import { UniRateModule } from '@unirate/angular'; |
| 39 | + |
| 40 | +@NgModule({ |
| 41 | + imports: [ |
| 42 | + UniRateModule.forRoot({ apiKey: environment.UNIRATE_API_KEY }), |
| 43 | + ], |
| 44 | +}) |
| 45 | +export class AppModule {} |
| 46 | +``` |
| 47 | + |
| 48 | +### Inject the service |
| 49 | + |
| 50 | +```ts |
| 51 | +import { Component, inject, OnInit } from '@angular/core'; |
| 52 | +import { AsyncPipe } from '@angular/common'; |
| 53 | +import { Observable } from 'rxjs'; |
| 54 | +import { UniRateService } from '@unirate/angular'; |
| 55 | + |
| 56 | +@Component({ |
| 57 | + selector: 'app-rates', |
| 58 | + standalone: true, |
| 59 | + imports: [AsyncPipe], |
| 60 | + template: `<p>EUR: {{ rate$ | async }}</p>`, |
| 61 | +}) |
| 62 | +export class RatesComponent implements OnInit { |
| 63 | + private rates = inject(UniRateService); |
| 64 | + rate$!: Observable<number>; |
| 65 | + |
| 66 | + ngOnInit() { |
| 67 | + this.rate$ = this.rates.getRate('USD', 'EUR'); |
| 68 | + } |
| 69 | +} |
| 70 | +``` |
| 71 | + |
| 72 | +### Use the pipes |
| 73 | + |
| 74 | +```ts |
| 75 | +// In a standalone component: |
| 76 | +import { CurrencyRatePipe, CurrencyConvertPipe } from '@unirate/angular'; |
| 77 | + |
| 78 | +@Component({ |
| 79 | + standalone: true, |
| 80 | + imports: [AsyncPipe, CurrencyRatePipe, CurrencyConvertPipe], |
| 81 | + template: ` |
| 82 | + <p>1 USD = {{ 'USD' | currencyRate:'EUR' | async }} EUR</p> |
| 83 | + <p>100 USD = {{ 100 | currencyConvert:'USD':'EUR' | async }} EUR</p> |
| 84 | + `, |
| 85 | +}) |
| 86 | +``` |
| 87 | + |
| 88 | +Or add `UniRateModule` to `imports` in an NgModule to get the pipes automatically. |
| 89 | + |
| 90 | +### Async config (`forRootAsync`) |
| 91 | + |
| 92 | +```ts |
| 93 | +UniRateModule.forRootAsync({ |
| 94 | + useFactory: (config: ConfigService) => ({ |
| 95 | + apiKey: config.getOrThrow('UNIRATE_API_KEY'), |
| 96 | + }), |
| 97 | + deps: [ConfigService], |
| 98 | +}) |
| 99 | +``` |
| 100 | + |
| 101 | +## API |
| 102 | + |
| 103 | +### `UniRateService` |
| 104 | + |
| 105 | +All methods return `Observable<T>`. Subscribe with Angular's `async` pipe or `firstValueFrom()`. |
| 106 | + |
| 107 | +```ts |
| 108 | +getRate(from: string, to: string): Observable<number> |
| 109 | +getRate(from: string): Observable<Record<string, number>> |
| 110 | + |
| 111 | +convert(to: string, amount: number, from: string): Observable<number> |
| 112 | + |
| 113 | +listCurrencies(): Observable<string[]> |
| 114 | + |
| 115 | +// Pro-gated — returns 403 on the free tier |
| 116 | +getHistoricalRate(date: string, amount: number, from: string, to: string): Observable<number> |
| 117 | +getHistoricalRate(date: string, amount: number, from: string): Observable<Record<string, number>> |
| 118 | +getHistoricalRates(date: string, amount: number, base: string): Observable<Record<string, number>> |
| 119 | +convertHistorical(amount: number, from: string, to: string, date: string): Observable<number> |
| 120 | +getTimeSeries(startDate: string, endDate: string, amount: number, base: string, currencies?: string[]): Observable<Record<string, Record<string, number>>> |
| 121 | +getHistoricalLimits(): Observable<HistoricalLimitsResponse> |
| 122 | +getVATRates(): Observable<VATRatesAll> |
| 123 | +getVATRates(country: string): Observable<VATRateOne> |
| 124 | +``` |
| 125 | + |
| 126 | +Raw Promise-based access: `service.raw.getRate(...)` returns a `Promise<T>` from the underlying `UniRateClient`. |
| 127 | + |
| 128 | +### Error handling |
| 129 | + |
| 130 | +All errors extend `UniRateError`: |
| 131 | + |
| 132 | +| Class | HTTP status | |
| 133 | +|---|---| |
| 134 | +| `AuthenticationError` | 401 | |
| 135 | +| `ProRequiredError` | 403 | |
| 136 | +| `InvalidCurrencyError` | 404 | |
| 137 | +| `InvalidRequestError` | 400 | |
| 138 | +| `RateLimitError` | 429 | |
| 139 | +| `UniRateError` | other / network | |
| 140 | + |
| 141 | +```ts |
| 142 | +import { catchError } from 'rxjs/operators'; |
| 143 | +import { UniRateError, ProRequiredError } from '@unirate/angular'; |
| 144 | + |
| 145 | +this.rates.getHistoricalRate('2023-01-01', 1, 'USD', 'EUR').pipe( |
| 146 | + catchError((err: UniRateError) => { |
| 147 | + if (err instanceof ProRequiredError) { |
| 148 | + console.warn('Historical rates require a Pro plan.'); |
| 149 | + } |
| 150 | + throw err; |
| 151 | + }), |
| 152 | +).subscribe(); |
| 153 | +``` |
| 154 | + |
| 155 | +## Rate limits |
| 156 | + |
| 157 | +The free plan allows 1,000 requests/month. Historical endpoints (`/api/historical/*`) require a Pro subscription and return `ProRequiredError` on the free tier. |
| 158 | + |
| 159 | +## Related clients |
| 160 | + |
| 161 | +<!-- unirate-ecosystem-start --> |
| 162 | +| Ecosystem | Package | |
| 163 | +|---|---| |
| 164 | +| Python | [unirate-api](https://pypi.org/project/unirate-api/) | |
| 165 | +| Node.js | [unirate-api](https://www.npmjs.com/package/unirate-api) | |
| 166 | +| React | [@unirate/react](https://www.npmjs.com/package/@unirate/react) | |
| 167 | +| Vue | [@unirate/vue](https://www.npmjs.com/package/@unirate/vue) | |
| 168 | +| Next.js | [@unirate/next](https://www.npmjs.com/package/@unirate/next) | |
| 169 | +| SvelteKit | [@unirate/sveltekit](https://www.npmjs.com/package/@unirate/sveltekit) | |
| 170 | +| NestJS | [@unirate/nestjs](https://www.npmjs.com/package/@unirate/nestjs) | |
| 171 | +| Nuxt | [@unirate/nuxt](https://www.npmjs.com/package/@unirate/nuxt) | |
| 172 | +| Remix | [@unirate/remix](https://www.npmjs.com/package/@unirate/remix) | |
| 173 | +| Eleventy | [@unirate/eleventy](https://www.npmjs.com/package/@unirate/eleventy) | |
| 174 | +| Astro | [@unirate/astro](https://www.npmjs.com/package/@unirate/astro) | |
| 175 | +| Go | [unirate-api-go](https://github.com/UniRate-API/unirate-api-go) | |
| 176 | +| Rust | [unirate-api](https://crates.io/crates/unirate-api) | |
| 177 | +| Swift | [unirate-api-swift](https://github.com/UniRate-API/unirate-api-swift) | |
| 178 | +| MCP Server | [@unirate/mcp](https://www.npmjs.com/package/@unirate/mcp) | |
| 179 | +<!-- unirate-ecosystem-end --> |
| 180 | + |
| 181 | +## License |
| 182 | + |
| 183 | +MIT — see [LICENSE](LICENSE). |
0 commit comments