Xây dựng một ứng dụng web so sánh chỉ số của các loại tiền số với Vue.js

Vue.js là một Javascript framwork đơn giản cho phép bạn xây dựng các ứng dụng web động với tầng fron-end. Đã có rất nhiều bài viết so sánh nó với React và Angular. Là một lập trình viên back-end và là một người không có kinh nghiệm làm việc với với web application phía front-end. Tôi tìm thấy VueJs, dùng nó và tôi thích nó hơn so với React và Angular.

Xây dựng một web app xem thông tin các loại tiền số

Lý thuyết và tài liệu hướng dẫn học Vue.js thì có rất nhiều, chúng ta có thể dễ dàng tìm được trên Internet (Trên Viblo cũng có rất nhiều bài về Vue.js). Theo mình cách học lập trình tốt nhất là luyện tập, sau khi chúng ta đọc qua cả đống lý thuyết về ngôn ngữ, framwork... hãy bắt tay vào làm một "cái gì đó" ngay và luôn. Tiền số đang nổi lên trong thời gian gần đây, mình đã đọc và thấy chúng cực kỳ thú vị, nên trong bài này mình sẽ hướng dẫn viết một trang web đơn giản hiển thị 10 loại tiền số phổ biến hàng đầu cùng các thông tin tỷ giá của chúng. Demo

Các thư viện hỗ trợ sẽ sử dụng

Đầu tiên chúng ta sẽ tạo trang web của chúng ta bằng html, javascript, vài thư viện javascript. Bởi vì tôi không phải là một web-designer (và có mắt thẩm mỹ kém 😄) nên tôi sử dụng css framwork là Bootstrap của Twitter. Tất nhiên chúng ta sẽ dùng Vue.js và một vài thư viện hỗ trợ:

  • vue2-filters: Một thư viện Vue.js đơn giản cung cấp một số bộ lọc mẫu cho việc lọc hiển thị text.
  • axios: Một thư viện phổ biến của Javascript để thực hiện các HTTP request

Cấu trúc project

  • File index.html sẽ include toàn bộ thư viện chúng ta sẽ sử dụng
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Cryptocurrency</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <link rel="stylesheet" href="https://bootswatch.com/simplex/bootstrap.min.css">
    <link rel="stylesheet" href="/static/css/style.css">
</head>
<body class="container">
<h1>Cryptocurrency Index</h1>

<div class="row">
    <div class="jumbotron col-xs-offset-2 col-xs-8">
        <p>
            Hello!!! Trang web này cung cập thông tin 10 loại tiền số thông dụng nhất,
            nó còn cho bạn một cái nhìn tổng quan và dễ dàng để so sánh tỷ giá các đồng tiền trong một tuần.
        </p>
    </div>
</div>

<div id="app">
</div>

<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<script src="https://unpkg.com/vue"></script>
<script src="https://cdn.jsdelivr.net/npm/vue2-filters/dist/vue2-filters.min.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="/static/js/app.js"></script>
</body>
</html>

Chúng ta đã có một trang html cơ bản, sẵn sàng cho các bước xây dựng ứng dụng bên dưới đây.

Thiết kế ứng dụng của chúng ta

Tôi chỉ muốn làm một ứng dụng đơn giản và hướng vào nội dung chính, vậy nên trong trường hợp này chúng ta sẽ xây dựng một danh sách các loại tiền số của chúng ta, chỉ cần đơn giản thôi. Nó là một bảng html liệt kê các loại tiền và thuộc tính của nó.

<div id="app">
    <table class="table table-hover">
        <thead>
        <tr>
            <td>Thứ hạng</td>
            <td>Tên</td>
            <td>Ký hiệu</td>
            <td>Giá (USD)</td>
            <td>1H</td>
            <td>1D</td>
            <td>1W</td>
            <td>Giá trị vốn hoá thị trường(Market Cap) (USD)</td>
        </thead>
        <tbody>
        </tbody>
    </table>
</div>

Style một chút:

  • /static/css/style.css
h1 {
  text-align: center;
}

td img {
  width: 25px;
}

.jumbotron p {
  font-size: 1.2em;
}

.jumbotron {
  margin-top: 5em;
  margin-bottom: 5em;
}

Chúng ta đã hoàn thành việc lên layout cho ứng dụng, thực hiện bước tiếp theo.

Lấy thông tin các đồng tiền số

Để hoàn thiện ứng dụng, chúng ta cần lấy được thông tin cập nhật của các đồng tiền, đối với ứng dụng này các công việc phải làm sẽ là:

  • Top 10 đồng tiền số phổ biến nhất và tổng giá trị vốn hóa của nó tương ứng
  • Tên của mỗi loại tiền
  • Biểu tượng của mỗi loại tiền
  • Giá trị của một "đồng xu" tương ứng, tính bằng USD.
  • Phần trăm thay đổi về giá trị của đơn vị tiền tệ trong 1 giờ, 1 ngày, và 1 tuần May mắn cho chúng ta, có một API miễn phí rất tốt cho việc lấy toàn bộ thông tin trên CoinMarketCap, không yêu cầu đăng ký hay cài đặt gì cả. Bạn chỉ cần vào đọc tài liệu của API và nhúng nó vào mọi hệ thống mà bạn đang xây dựng 😃))

Chúng ta sẽ dùng CoinMarketCap, api này cho phép chúng ta lấy top 10 đồng tiền phổ biến nhất, kèm theo đó là "rank" và các thông tin của nó, đây là những gì API đó sẽ trả cho chúng ta:

[
  {
    "id": "bitcoin",
    "name": "Bitcoin",
    "symbol": "BTC",
    "rank": "1",
    "price_usd": "2747.54",
    "price_btc": "1.0",
    "24h_volume_usd": "2242640000.0",
    "market_cap_usd": "45223373666.0",
    "available_supply": "16459587.0",
    "total_supply": "16459587.0",
    "percent_change_1h": "-2.83",
    "percent_change_24h": "19.78",
    "percent_change_7d": "17.2",
    "last_updated": "1500596647"
  },
  ...
]

Một mảng top 10 loại tiền số phổ biến hàng đầu. Mỗi đối tượng JSON trong mảng đại diện cho một đồng tiền, chứa đầy đủ thông tin chúng ta cần.

Còn một thứ chúng ta vẫn đang thiếu, chúng ta muốn hiển thị symbol của đồng tiền ngay cạnh tên của nó cho đẹp. Thật không may CoinMarketCap không cung cấp bất kỳ thông tin nào về image, nhưng thật may mắn tôi đã thấy một API khác làm được việc này CryptoCompare API, api sẽ cung cấp nốt thông tin còn thiếu cho chúng ta, giống như CoinMarketCap chúng ta sẽ không phải đăng ký. API lấy thông tin về toàn bộ đồng tiền số Coinlist sẽ trả về cho chúng ta dữ liệu:

{
  ...
  "Data": {
    "AVT": {
      "Id": "138642",
      "Url": "/coins/avt/overview",
      "ImageUrl": "/media/1383599/avt.png",
      "Name": "AVT",
      "CoinName": "AventCoin",
      "FullName": "AventCoin (AVT)",
      "Algorithm": "N/A",
      "ProofType": "N/A",
      "FullyPremined": "0",
      "TotalCoinSupply": "10000000",
      "PreMinedValue": "N/A",
      "TotalCoinsFreeFloat": "N/A",
      "SortOrder": "1266"
    },
    ...
  }
}

với thông tin ImageUrl chúng ta có thể hiển thị hình ảnh của từng đồng tiền. Kết hợp dữ liệu của 2 API chúng ta đã có tất cả mọi thứ chúng ta cần.

Cài đặt Vue app

Để cho đơn giản chúng ta sẽ viết toàn bộ code Vue vào file static/js/app.js Đầu tiên chúng ta chuẩn bị một vue app, các giá trị sẽ sử dụng trong app

// API dành cho việc lấy thông tin của các đồng tiến số
// (thông tin chúng ta cần là logo images của các loại tiền)
// Doc: https://www.cryptocompare.com/api/
let CRYPTOCOMPARE_API_URI = "https://www.cryptocompare.com";

// API dành cho việc lấy các thông tin quan trọng của top 10 đồng tiền số
// Doc: https://coinmarketcap.com/api/
let COINMARKETCAP_API_URI = "https://api.coinmarketcap.com";

// Thời gian sẽ update lại thông tin đang hiển thị
let UPDATE_INTERVAL = 60 * 1000;

let app = new Vue({
    el: "#app",
    data: {
        coins: [], // Array chứ thông tin các đồng tiền khác nhau(bitcoin, ethereum...)
        coinData: {} // Đối tượng chứa kết quả của API CryptoCompare. Chúng ta sẽ dùng nó để lấy ra thông tin logos
    },
    methods: {

        /**
         * Lấy thông tin của tất cả các đồng tiền, thông tin được sử dụng để lấy ra
         * logo tương ứng của từng đồng tiền.
         */
        getCoinData: function() {
        },

        /**
         * Lấy thông tin top 10 đồng tiền phổ biến
         * Thông tin sẽ được refresh sau UPDATE_INTERVAL / 1000 seconds
         * bằng cách gọi lại API
         */
        getCoins: function() {
        },

        /**
         * Truyền vào ký hiệu của đồng tiền, trả ra logo của đồng tiền đó
         */
        getCoinImage: function(symbol) {
        }
    }
});

Implement các phương thức

  • Thực hiện Implement phương thức getCoins phương thức này sẽ gọi API CoinMarketCap. API sẽ trả lại một array JSON chúng ta sẽ lưu nó trong biến của ứng dụng Vue. Để thực hiện API request ở phía client tôi thích dùng axios nó phổ biến và đơn giản. Phương thức getCoins của chúng ta sẽ như sau:
getCoins: function() {
  let self = this;

  axios.get(COINMARKETCAP_API_URI + "/v1/ticker/?limit=10")
    .then((resp) => {
      this.coins = resp.data;
    })
    .catch((err) => {
      console.error(err);
    });
},

Như chúng ta thấy bạn dễ dàng tạo ra một GET request với axios, api trả lại một array và chúng ta lưu nó vào self.coins (Cập nhật giá trị của biến coins của ứng dụng Vue). Khi việc cập nhật giá trị đó xảy ra, Vue sẽ render lại các phần của trang html mà có phụ thuộc vào biến bị thay đổi giá trị.

  • Tiếp theo, implement phương thức getCoinData, chúng ta sẽ gọi api trả lại thông tin của tất cả các đồng tiền số và sẽ sử dụng thông tin này để lấy ra logos của từng đồng tiền chúng ta cần.
getCoinData: function() {
  let self = this;

  axios.get(CRYPTOCOMPARE_API_URI + "/api/data/coinlist")
    .then((resp) => {
      this.coinData = resp.data.Data;
      this.getCoins();
    })
    .catch((err) => {
      this.getCoins();
      console.error(err);
    });
}

Một API đơn giản, nhưng chúng ta lưu ý chúng ta sẽ gọi phương thức getCoins trong kết quả (kể cả thất bại) của phương thức này vì phương thức này chỉ cần gọi một lần khi page load, nhưng getCoins lại cần gọi nhiều lần khi cập nhật giá trị và chúng ta cần đảm bảo thông tin về logos đã có trước khi hiển thị.

  • Cuối cùng là pương thức getCoinImage kết hợp thông tin của 2 API
getCoinImage: function(symbol) {
	// có 2 mã đặc biệt
  symbol = (symbol === "MIOTA" ? "IOT" : symbol);
  symbol = (symbol === "VERI" ? "VRM" : symbol);
  return CRYPTOCOMPARE_API_URI + this.coinData[symbol].ImageUrl;
}

Thực hiện load dữ liệu khi bắt đầu ứng dụng

Chúng ta chạy ứng dụng, nhưng chư thấy hiện tượng gì xảy ra cả 😐 Vòng đời của một ứng dụng Vue có phương thức created - khi toàn bộ ứng dụng Vue đã sẵn sàng trên trình duyệt. Chúng ta sẽ thêm vào một đoạn như sau:

let app = new Vue({
  // ...
  created: function() {
    this.getCoinData();
  }
});

Đó là những gì chúng ta cần: Khi hoàn thành việc load page, phương thức getCoinData sẽ được gọi, tiếp theo là phương thức getCoins Cuối cùng chúng ta muốn dữ liệu sẽ tự động cập nhật, chúng ta sẽ thêm một đoạn này vào file app.js

setInterval(() => {
  app.getCoins();
}, UPDATE_INTERVAL);

Chúng ta đã xây dựng xong ứng dụng Vue cho trang web của chúng ta. Còn một bước nữa là hoàn thành ứng dụng.

Hiển thị dữ liệu

Hiển thị dự liệu ra phía người dùng cuối ở phần HTML. Sử dụng những gì bạn đã học về Vue.js chúng ta sẽ có vòng lặp để hiển thị dữ liệu lên bảng:

<tbody>
    <tr v-for="coin in coins">
      <td>{{ coin.rank }}</td>
      <td><img v-bind:src="getCoinImage(coin.symbol)"> {{ coin.name }}</td>
      <td>{{ coin.symbol }}</td>
      <td>{{ coin.price_usd | currency }}</td>
      <td>
        <span v-if="coin.percent_change_1h > 0">+</span>{{ coin.percent_change_1h }}%
      </td>
      <td>
        <span v-if="coin.percent_change_24h > 0">+</span>{{ coin.percent_change_24h }}%
      </td>
      <td>
        <span v-if="coin.percent_change_7d > 0">+</span>{{ coin.percent_change_7d }}%
      </td>
      <td>{{ coin.market_cap_usd | currency }}</td>
    </tr>
  </tbody>

Thêm chút style cho sinh động:

  • Nếu chỉ số tăng sẽ là màu xanh, ngược lại sẽ làu màu đỏ: Thêm một phương thức cho Vue app
getColor: (num) => {
  return num > 0 ? "color:green;" : "color:red;";
}

và sửa lại phần binding dữ liệu:

<tbody>
  <tr v-for="coin in coins">
    <td>{{ coin.rank }}</td>
    <td><img v-bind:src="getCoinImage(coin.symbol)"> {{ coin.name }}</td>
    <td>{{ coin.symbol }}</td>
    <td>{{ coin.price_usd | currency }}</td>
    <td v-bind:style="getColor(coin.percent_change_1h)">
      <span v-if="coin.percent_change_1h > 0">+</span>{{ coin.percent_change_1h }}%
    </td>
    <td v-bind:style="getColor(coin.percent_change_24h)">
      <span v-if="coin.percent_change_24h > 0">+</span>{{ coin.percent_change_24h }}%
    </td>
    <td v-bind:style="getColor(coin.percent_change_7d)">
      <span v-if="coin.percent_change_7d > 0">+</span>{{ coin.percent_change_7d }}%
    </td>
    <td>{{ coin.market_cap_usd | currency }}</td>
  </tr>
</tbody>

Như vậy là chúng ta đã hoàn thành ứng dụng của mình! Các bạn có thể tham khảo source code đã hoàn thành của mình tại Gtihub