# Xây dựng ứng dụng ngân hàng Phần 3: Các phương pháp lấy và sử dụng dữ liệu ## Câu hỏi trước bài giảng [Câu hỏi trước bài giảng](https://ff-quizzes.netlify.app/web/quiz/45) ### Giới thiệu Cốt lõi của mọi ứng dụng web là *dữ liệu*. Dữ liệu có thể có nhiều dạng, nhưng mục đích chính của nó luôn là hiển thị thông tin cho người dùng. Với các ứng dụng web ngày càng trở nên tương tác và phức tạp, cách người dùng truy cập và tương tác với thông tin giờ đây là một phần quan trọng của phát triển web. Trong bài học này, chúng ta sẽ tìm hiểu cách lấy dữ liệu từ máy chủ một cách bất đồng bộ và sử dụng dữ liệu này để hiển thị thông tin trên trang web mà không cần tải lại HTML. ### Điều kiện tiên quyết Bạn cần đã xây dựng [Biểu mẫu Đăng nhập và Đăng ký](../2-forms/README.md) của ứng dụng web cho bài học này. Bạn cũng cần cài đặt [Node.js](https://nodejs.org) và [chạy API máy chủ](../api/README.md) cục bộ để lấy dữ liệu tài khoản. Bạn có thể kiểm tra xem máy chủ có hoạt động đúng không bằng cách thực hiện lệnh này trong terminal: ```sh curl http://localhost:5000/api # -> should return "Bank API v1.0.0" as a result ``` --- ## AJAX và lấy dữ liệu Các trang web truyền thống cập nhật nội dung hiển thị khi người dùng chọn một liên kết hoặc gửi dữ liệu qua biểu mẫu bằng cách tải lại toàn bộ trang HTML. Mỗi lần cần tải dữ liệu mới, máy chủ web trả về một trang HTML hoàn toàn mới cần được trình duyệt xử lý, làm gián đoạn hành động hiện tại của người dùng và hạn chế tương tác trong quá trình tải lại. Quy trình này còn được gọi là *Ứng dụng Nhiều Trang* hoặc *MPA*.  Khi các ứng dụng web bắt đầu trở nên phức tạp và tương tác hơn, một kỹ thuật mới gọi là [AJAX (Asynchronous JavaScript and XML)](https://en.wikipedia.org/wiki/Ajax_(programming)) đã xuất hiện. Kỹ thuật này cho phép các ứng dụng web gửi và nhận dữ liệu từ máy chủ một cách bất đồng bộ bằng JavaScript mà không cần tải lại trang HTML, dẫn đến các cập nhật nhanh hơn và tương tác mượt mà hơn. Khi dữ liệu mới được nhận từ máy chủ, trang HTML hiện tại cũng có thể được cập nhật bằng JavaScript sử dụng API [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model). Theo thời gian, cách tiếp cận này đã phát triển thành cái mà ngày nay được gọi là [*Ứng dụng Một Trang* hoặc *SPA*](https://en.wikipedia.org/wiki/Single-page_application).  Khi AJAX lần đầu tiên được giới thiệu, API duy nhất có sẵn để lấy dữ liệu một cách bất đồng bộ là [`XMLHttpRequest`](https://developer.mozilla.org/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest). Nhưng các trình duyệt hiện đại hiện nay cũng đã triển khai API [`Fetch`](https://developer.mozilla.org/docs/Web/API/Fetch_API) tiện lợi và mạnh mẽ hơn, sử dụng promises và phù hợp hơn để xử lý dữ liệu JSON. > Mặc dù tất cả các trình duyệt hiện đại đều hỗ trợ `Fetch API`, nếu bạn muốn ứng dụng web của mình hoạt động trên các trình duyệt cũ hoặc lỗi thời, luôn là một ý tưởng tốt để kiểm tra [bảng tương thích trên caniuse.com](https://caniuse.com/fetch) trước. ### Nhiệm vụ Trong [bài học trước](../2-forms/README.md), chúng ta đã triển khai biểu mẫu đăng ký để tạo tài khoản. Bây giờ chúng ta sẽ thêm mã để đăng nhập bằng tài khoản hiện có và lấy dữ liệu của nó. Mở tệp `app.js` và thêm một hàm `login` mới: ```js async function login() { const loginForm = document.getElementById('loginForm') const user = loginForm.user.value; } ``` Ở đây chúng ta bắt đầu bằng cách lấy phần tử biểu mẫu với `getElementById()`, sau đó lấy tên người dùng từ đầu vào với `loginForm.user.value`. Mỗi điều khiển biểu mẫu có thể được truy cập bằng tên của nó (được đặt trong HTML bằng thuộc tính `name`) như một thuộc tính của biểu mẫu. Tương tự như những gì chúng ta đã làm cho đăng ký, chúng ta sẽ tạo một hàm khác để thực hiện yêu cầu máy chủ, nhưng lần này là để lấy dữ liệu tài khoản: ```js async function getAccount(user) { try { const response = await fetch('//localhost:5000/api/accounts/' + encodeURIComponent(user)); return await response.json(); } catch (error) { return { error: error.message || 'Unknown error' }; } } ``` Chúng ta sử dụng API `fetch` để yêu cầu dữ liệu một cách bất đồng bộ từ máy chủ, nhưng lần này chúng ta không cần bất kỳ tham số bổ sung nào ngoài URL để gọi, vì chúng ta chỉ đang truy vấn dữ liệu. Theo mặc định, `fetch` tạo một yêu cầu HTTP [`GET`](https://developer.mozilla.org/docs/Web/HTTP/Methods/GET), đây là điều chúng ta đang tìm kiếm. ✅ `encodeURIComponent()` là một hàm thoát các ký tự đặc biệt cho URL. Những vấn đề nào chúng ta có thể gặp phải nếu không gọi hàm này và sử dụng trực tiếp giá trị `user` trong URL? Bây giờ hãy cập nhật hàm `login` của chúng ta để sử dụng `getAccount`: ```js async function login() { const loginForm = document.getElementById('loginForm') const user = loginForm.user.value; const data = await getAccount(user); if (data.error) { return console.log('loginError', data.error); } account = data; navigate('/dashboard'); } ``` Đầu tiên, vì `getAccount` là một hàm bất đồng bộ, chúng ta cần kết hợp nó với từ khóa `await` để chờ kết quả từ máy chủ. Như với bất kỳ yêu cầu máy chủ nào, chúng ta cũng phải xử lý các trường hợp lỗi. Hiện tại, chúng ta chỉ thêm một thông báo nhật ký để hiển thị lỗi và sẽ quay lại sau. Sau đó, chúng ta phải lưu dữ liệu vào đâu đó để sau này có thể sử dụng nó để hiển thị thông tin bảng điều khiển. Vì biến `account` chưa tồn tại, chúng ta sẽ tạo một biến toàn cục cho nó ở đầu tệp của chúng ta: ```js let account = null; ``` Sau khi dữ liệu người dùng được lưu vào một biến, chúng ta có thể điều hướng từ trang *login* đến *dashboard* bằng cách sử dụng hàm `navigate()` mà chúng ta đã có. Cuối cùng, chúng ta cần gọi hàm `login` của mình khi biểu mẫu đăng nhập được gửi, bằng cách sửa đổi HTML: ```html