Create a cryptocurrency app with Angular
In this tutorial, we are going to create a CryptoCurrency Price Update app using Angular 9.
You will learn about Angular 9 application development. You will also learn how to use the Subject class from RxJS, a ReactiveX library for JavaScript.
Let’s get started By creating Angular 9 project
Install the Angular CLI
To install the CLI using npm, open a terminal window and enter the following command :
npm install -g @angular/cli
now, Run the CLI command ng new and provide the name cryptoapp, as shown below:
ng new cryptoapp
Run the application
cd cryptoapp ng serve --open
your app will automatically at http://localhost:4200/
as shown below:
Create application components
Now, let’s create the components.
In the New, AngularCli enter the following command to create a bunch of new components
ng generate component search ng generate component list-card ng generate component list ng generate component dashboard
Create AppService
In this tutorial, we will be using just one Angular Service, named AppService. In Angular, a Service is a broad category encompassing any value, function, or feature that your application needs.
ng generate service App
Add Bootstrap CSS and Font Awesome
Let’s add bootstrap CSS and font awesome to our project by updating the contents of src/index.html
add the code snippet below in the head section of the HTML document:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Cryptoapp</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.css">
<link href='//fonts.googleapis.com/css?family=Open+Sans:400,700'
rel='stylesheet' type='text/css'>
<link rel="stylesheet"
href="//netdna.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.css" />
</head>
<body>
<app-root></app-root>
</body>
</html>
Install Angular dropdown-multi-select
Angular Dropdown Multiselect for Bootstrap CSS is used to create customizable dropdown multiselect in AngularX, TypeScript with bootstrap CSS.
npm install angular2-multiselect-dropdown
In order to make the multiselect component available in our application, we need to update src/app/app.module.ts
as follows:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { SearchComponent } from './search/search.component';
import { ListCardComponent } from './list-card/list-card.component';
import { ListComponent } from './list/list.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import {MultiselectDropdownModule} from 'angular-2-dropdown-multiselect';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
@NgModule({
declarations: [
AppComponent,
SearchComponent,
ListCardComponent,
ListComponent,
DashboardComponent
],
imports: [
BrowserModule,
AppRoutingModule,
MultiselectDropdownModule,
FormsModule,
HttpClientModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
The API
We will be using the api provided by coinmarketcap.com. Using the sample data returned, let us create a typescript shapefile named coin.ts
with the following content:
export class Coin {
id: string;
name: string;
symbol: string;
rank: string;
price_usd: string;
price_btc: string;
'24h_volume_usd': string;
market_cap_usd: string;
available_supply: string;
total_supply: string;
percent_change_1h: string;
percent_change_24h: string;
percent_change_7d: string;
last_updated: string;
}
Similarly, create a new file called localdata.ts in the app folder with the following content:
export let cryptoCurrencies = [
'0x',
'ATMChain',
'AdEx',
'Aeternity',
'Aragon',
'Ardor',
'Ark',
'Augur',
'Bancor',
'Basic Attenti...',
'Binance Coin',
'BitConnect',
'BitShares',
'Bitcoin',
'Bitcoin Cash',
'BitcoinDark',
'Bitdeal',
'Bitquence',
'Blocknet',
'Byteball Bytes',
'Bytecoin',
'Bytom',
'Cardano',
'ChainLink',
'Civic',
'Dash',
'Decred',
'Dentacoin',
'DigiByte',
'DigixDAO',
'Dogecoin',
'EOS',
'Edgeless',
'Ethereum',
'Ethereum Classic',
'Factom',
'FirstCoin',
'FunFair',
'GXShares',
'GameCredits',
'Gas',
'Gnosis',
'Golem',
'Hshare',
'I/O Coin',
'IOTA',
'Iconomi',
'Kin',
'Komodo',
'Kyber Network',
'Lisk',
'Litecoin',
'Loopring',
'Lykke',
'MCAP',
'MaidSafeCoin',
'Metal',
'Metaverse ETP',
'MonaCoin',
'Monaco',
'Monero',
'NAV Coin',
'NEM',
'NEO',
'Neblio',
'Nexus',
'NoLimitCoin',
'Nxt',
'OmiseGO',
'PIVX',
'Particl',
'Populous',
'Pura',
'Qtum',
'Ripple',
'SALT',
'Siacoin',
'SingularDTV',
'SmartCash',
'Status',
'Steem',
'Stellar Lumens',
'Storj',
'Stratis',
'Syscoin',
'TRON',
'TaaS',
'TenX',
'Tether',
'Ubiq',
'VeChain',
'Verge',
'Veritaseum',
'Vertcoin',
'Walton',
'Waves',
'ZCoin',
'Zcash',
'ZenCash',
'iExec RLC'
];
App Service
AppService is a general-purpose class we will use to fetch data from the API, and pass that data on to components.
Our appservice.ts
file ha following content
import {Injectable} from '@angular/core';
import {Subject} from 'rxjs';
import {HttpClient, HttpParams} from '@angular/common/http';
import {cryptoCurrencies} from './localdata';
import {Coin} from './coin';
const API_BASE_URL = 'https://api.coinmarketcap.com/v1/';
const coins: Coin[] = [];
@Injectable()
export class AppService {
private allCoins: Coin[];
private filteredCoins: Coin[];
private filter: number[];
coinsSubject: Subject<Coin[]>;
filteredCoinsSubject: Subject<Coin[]>;
apiSubject: Subject<string>;
fiatSubject: Subject<string>;
constructor(private http: HttpClient) {
this.filter = [];
this.coinsSubject = new Subject();
this.filteredCoinsSubject = new Subject();
this.apiSubject = new Subject();
this.fiatSubject = new Subject();
}
getCryptoOptions() {
return cryptoCurrencies;
}
loadMarketCaps(fiat: string) {
this.fiatSubject.next(fiat);
const url = API_BASE_URL + 'ticker/';
let params = new HttpParams();
params = params.append('limit', '25');
if (fiat !== 'usd') {
// TODO: check if fiat is valid
params = params.append('convert', fiat);
}
this.apiSubject.next('loading...');
this.http
.get<Coin[]>(url, {params})
.subscribe(
data => {
this.allCoins = data;
this.announceCoins();
this.filterMarketCaps();
}
);
// this.allCoins = mock.data;
}
filterMarketCaps() {
this.filteredCoins = [];
if (this.filter.length === 0) {
this.allCoins.forEach((coin) => this.filteredCoins.push(coin));
}
if (this.filter.length > 0) {
this.filter.forEach((i) => {
this.filteredCoins.push(this.allCoins[i]);
});
}
this.announceFilteredCoins();
}
announceCoins() {
this.coinsSubject.next(this.allCoins);
}
announceFilteredCoins() {
this.filteredCoinsSubject.next(this.filteredCoins);
}
updateFilter(filter: number[]) {
this.filter = [];
filter.forEach((elem) => {
this.filter.push(elem);
});
this.filterMarketCaps();
}
}
In the file above First we import {Injectable} from @angular/core
, {Subject}
from rxjs
, {HttpClient, HttpParams}
from @angular/common/http
Next we imported the coin shape Coin from ./coin
, and we create a constant API_BASE_URL
to hold the base url of the api.
App Component
Update the content of src/app/app.component.html
with the content below. We display the title in an h2 element, and we set the dashboard component as a child view.
<div class="container"> <div class="row"> <div class="col-sm-6 col-sm-offset-3"> <h2 class="text-center">{{ title }}</h2> <app-dashboard></app-dashboard> </div> </div> </div>
Dashboard Component
The dashboard component will serve as a parent component for two components, the search component, and the list component.
Replace the contents of `src/app/dashboard/dashboard.component.html` with the code snippet below
<app-search></app-search>
<app-list></app-list>
Now, update src/app/dashboard/dashboard.component.ts
by importing AppService, and adding AppSercieto the providers’ array which enables Angular’s Dependency Injector for AppService
import { Component, OnInit } from '@angular/core';
import {AppService} from '../app.service';
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.sass'],
providers: [AppService]
})
export class DashboardComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
Search Component
The code below goes in src/app/search-filter/search.component.html
<div>
<form>
<div class="form-group">
<select id="selectedCurrency" name="selectedCurrency" [(ngModel)]="selectedCurrency"
(change)="selectCurrency($event.target.value)" class="form-control"
id="fiat">
<option value="">Select fiat currency</option>
<option *ngFor="let currency of currencies" value="{{currency}}">{{currency.toUpperCase()}}</option>
</select>
</div>
<div class="form-group">
<ss-multiselect-dropdown id="cryptocurrencies"
name="cryptocurrencies" [texts]="myTexts" [settings]="mySettings"
[options]="cryptoCurrOptions" [(ngModel)]="optionsModel"
(ngModelChange)="filterChange($event)"></ss-multiselect-dropdown>
</div>
</form>
</div>
and the code below goes in src/app/search/search.component.ts
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {AppService} from '../app.service';
import {IMultiSelectOption, IMultiSelectSettings, IMultiSelectTexts} from 'angular-2-dropdown-multiselect';
@Component({
selector: 'app-search',
templateUrl: './search.component.html',
styleUrls: ['./search.component.css'],
providers: []
})
export class SearchComponent implements OnInit {
currencies: string[];
cryptoCurrOptions: IMultiSelectOption[];
selectedCurrency: string;
optionsModel: number[];
// Settings configuration
mySettings: IMultiSelectSettings = {
enableSearch: true,
checkedStyle: 'fontawesome',
buttonClasses: 'btn btn-default btn-block',
dynamicTitleMaxItems: 5,
displayAllSelectedText: true
};
// Text configuration
myTexts: IMultiSelectTexts = {
checkAll: 'Select all',
uncheckAll: 'Unselect all',
checked: 'item selected',
checkedPlural: 'items selected',
searchPlaceholder: 'Find',
searchEmptyResult: 'Nothing found...',
searchNoRenderText: 'Type in search box to see results...',
defaultTitle: 'Filter cryptos',
allSelected: 'All selected',
};
constructor(private appService: AppService) {
this.currencies = ['usd', 'eur'];
this.selectedCurrency = '';
this.cryptoCurrOptions = [];
this.appService.coinsSubject.subscribe({
next: (v) => this.updateCryptoOptions(v),
});
}
ngOnInit() {
}
selectCurrency(newValue) {
this.appService.loadMarketCaps(newValue);
}
filterChange(newValue) {
// BUG method should not be triggered by filter select
this.appService.updateFilter(newValue);
}
updateCryptoOptions(coins) {
this.cryptoCurrOptions = [];
coins.forEach((coin, index) => {
this.cryptoCurrOptions.push({
id: index,
name: coin.id.charAt(0).toUpperCase() + coin.id.slice(1)
});
});
}
}
List Component
This component manages the display of a list of cards. One for each crypto coin data returned from the API. The coins model is an array of the data rendered by each coin. Let us update the contents of src/app/list/list.component.html
<div class="list-cards pb-4">
<app-list-card *ngFor="let coin of coins" [coin]="coin" [fiat]="fiat"></app-list-card>
</div>
<div *ngIf="!coins">
{{noDataMsg || 'Nothing to display'}}
</div>
In src/app/list/list.component.ts
, we subscribe to three RxJs subjects in our Service.
import {Component, Input, OnInit} from '@angular/core';
import {AppService} from '../app.service';
import {Coin} from '../coin';
@Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.sass']
})
export class ListComponent implements OnInit {
coins: Coin[];
noDataMsg: string;
fiat: string;
constructor(private appService: AppService) {
this.noDataMsg = 'Select fiat currency to get started';
this.appService.filteredCoinsSubject.subscribe({
next: (v) => this.updateCoins(v),
});
this.appService.apiSubject.subscribe({
next: (msg) => this.noDataMsg = msg,
});
this.appService.fiatSubject.subscribe({
next: (newValue) => this.fiat = newValue,
});
}
updateCoins(coins: Coin[]) {
this.coins = [];
coins.forEach((coin) => this.coins.push(coin));
}
ngOnInit() {
}
}
ListCard Component
The list-card component is basically to display the market cap summary of a single cryptocurrency. The content of src/app/list-card/list-card.component.html
is below
<div class="card text-center mt-4">
<div class="card-body">
<h2 class="card-title">{{ coin.id }}</h2>
<h4 class="mb-0 pb-1">{{fiat.toUpperCase()}} {{ coin['price_'+fiat] }}</h4>
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item">24 hours: <span [class]="coin.percent_change_24h.charAt(0)==='-' ? 'red' : 'green' ">{{ coin.percent_change_24h
}}</span></li>
<li class="list-group-item">7 days: <span [class]="coin.percent_change_24h.charAt(0)==='-' ? 'red' : 'green' ">{{ coin.percent_change_7d }}</span></li>
</ul>
</div>
and src/app/list-card/list-card.component.ts
import {Component, OnInit, Input} from '@angular/core';
@Component({
selector: 'app-list-card',
templateUrl: './list-card.component.html',
styleUrls: ['./list-card.component.sass']
})
export class ListCardComponent {
@Input() coin;
@Input() fiat;
}
And below is the final result:
conclusion
I hope you learned a few things about Angular. Every article can be made better, so please leave your suggestions and contributions in the comments below. If you have questions about any of the steps, please do ask also in the comments section below.
[epcl_columns] [epcl_button label=”Code source” url=”https://github.com/Dunebook/cryptoappAngular” type=”outline” color=”red” size=”large” icon=”fa-github” target=”_blank” rel=”dofollow”][/epcl_button] [epcl_button label=”Project Demo” url=”https://cryptod.netlify.com/” type=”outline” color=”red” size=”large” icon=”fa-github” target=”_blank” rel=”dofollow”] [/epcl_columns]