Reputation: 419
I'm starting to learn Angular 2+ after working with AngularJS for a bit and have been playing around with Angular Universal for the SSR to try it out and learn along the way.
However, I've been running into this CORS issue with an API request I have in the ngOnInit
on one of the views that loads additional details about the selected item.
The CORS issue only happens when I navigate from the list page, which is a static list of coins to select with router links, to a specific coin's detail page.
I was using these two tutorials as guide posts when working through this stuff.
Angular SSR With HttpClient rehydration
Any help would be appreciated, my skills aren't super strong on the frontend and I'm not very familiar with CORS.
Router Code Snippet:
const routes: Routes = [
{ path: '', component: CoinListComponent },
{ path: 'coin/:symbol', component: CoinSnapshotComponent },
];
@NgModule({
imports: [
RouterModule.forRoot(routes, {
initialNavigation: 'enabled',
}),
],
exports: [RouterModule],
})
export class AppRoutingModule {}
List View:
<h2>Current Coins</h2>
<ul *ngFor="let coin of coins">
<li>
<div>
<a routerLink="/coin/{{coin.symbol}}"><span>{{coin.name}} - {{coin.symbol}}</span></a>
</div>
</li>
</ul>
Coin Detail Component:
interface CoinData {
[key: string]: number;
}
interface CoinDataResponse {
data: {
currency: string;
rates: CoinData;
};
}
@Component({
selector: 'app-coin-snapshot',
templateUrl: './coin-snapshot.component.html',
styleUrls: ['./coin-snapshot.component.scss'],
})
export class CoinSnapshotComponent implements OnInit {
coins = coins;
coin: Coin;
defaultCoin: Coin;
coinData: CoinData | undefined;
constructor(
private route: ActivatedRoute,
private http: CustomHttpClientService
) {
this.defaultCoin = {
symbol: 'DEFAULT',
name: 'DEFAULT',
link: 'DEFAULT',
};
const symbol = this.route.snapshot.paramMap.get('symbol');
this.coin = this.findCoinBySymbol(symbol);
}
ngOnInit(): void {
if (!this.isDefault(this.coin)) {
this.getCoinData(this.coin);
}
}
findCoinBySymbol(symbol: string | null) {
const coin = this.coins.find((coin) => coin.symbol === symbol);
if (!coin) {
return this.defaultCoin;
}
return coin;
}
isDefault(coin: Coin) {
return coin.name === 'DEFAULT' && coin.symbol === 'DEFAULT';
}
getCoinData(coin: Coin) {
this.http.get<CoinDataResponse>(coin.link).subscribe((coin) => {
this.coinData = coin.data.rates;
});
}
}
Coin Object that the link comes from:
export interface Coin {
name: string;
symbol: string;
link: string;
}
const coins: Coin[] = [
{
name: 'Bitcoin',
symbol: 'BTC',
link: 'https://api.coinbase.com/v2/exchange-rates?currency=BTC',
},
{
name: 'Ethereum',
symbol: 'ETH',
link: 'https://api.coinbase.com/v2/exchange-rates?currency=ETH',
},
{
name: 'Chainlink',
symbol: 'LINK',
link: 'https://api.coinbase.com/v2/exchange-rates?currency=LINK',
},
];
export default coins;
CustomHttpClient Code (Mainly from Angular SSR With HttpClient rehydration article):
import { Injectable, Inject, PLATFORM_ID } from '@angular/core';
import { HttpParams, HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
import {
StateKey,
makeStateKey,
TransferState,
} from '@angular/platform-browser';
import { isPlatformServer } from '@angular/common';
@Injectable()
export class CustomHttpClientService {
constructor(
private httpClient: HttpClient,
private transferState: TransferState,
@Inject(PLATFORM_ID) private platformId: Object
) {}
get<T>(path: string, params?: HttpParams): Observable<T> {
const transferKey: StateKey<T> = makeStateKey(
`${path}?${params != null ? params.toString() : ''}`
);
if (this.transferState.hasKey(transferKey)) {
return of(this.transferState.get<any>(transferKey, 0)).pipe(
tap(() => this.transferState.remove(transferKey))
);
} else {
return this.httpClient
.get<T>(path, { observe: 'body', responseType: 'json', params: params })
.pipe(
tap((response) => {
if (isPlatformServer(this.platformId)) {
this.transferState.set<T>(transferKey, response);
}
})
);
}
}
}
Core Module:
import { NgModule } from '@angular/core';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { CustomHttpClientService } from 'src/app/core/custom-http-client.service';
import { CookiesInterceptor } from 'src/app/core/cookies.interceptor';
@NgModule({
imports: [HttpClientModule],
providers: [
CustomHttpClientService,
{
provide: HTTP_INTERCEPTORS,
useClass: CookiesInterceptor,
multi: true,
},
],
})
export class CoreModule {}
Upvotes: 0
Views: 767
Reputation: 34435
You need to set up your own API (or a proxy) to make requests to Coinbase, as their CORS policy prevent direct requests from a browser.
The reason why you are not getting the error when refreshing the page is that it uses SSR, meaning that the request to Coinbase is actually done from your server (not browser), which is not impacted by CORS restrictions
Upvotes: 3