Daily Archives: 29/03/2017

Angular – HTTP

Trong phần này chúng ta sẽ tìm hiểu cách gửi các gói tin đến một web server và nhận dữ liệu trả về từ server đó.

Tạo web server API

Để có thể gửi các truy vấn API thì trước hết chúng ta phải có một web server có các URL mẫu đã, may mắn là trong Angular có sẵn các lớp service có khả năng tạo một web server “mini” chạy cục bộ để chúng ta có thể thử nghiệm tính năng truy vấn API này.

Đầu tiên chúng ta tạo một project từ quickstart.

Trong project này mình có sử dụng bootstrap, bạn có thể lấy link CDN của bootstrap ở đây và chèn vào file index.html.

Trong web server này chúng ta sẽ lưu danh sách các ngôn ngữ lập trình và cho phép truy vấn, chỉnh sửa, xóa các thông tin này.

Đầu tiên chúng ta tạo một file có tên language.ts trong thư mục src/app như sau:

export class Language {
    id: number;
    name: string;
}

Trong file này chúng ta định nghĩa lớp Language gồm 2 trường là idname.

Tiếp theo chúng tạo một file có tên language-db.service.ts trong thư mục src/app như sau:

import { InMemoryDbService } from 'angular-in-memory-web-api';

export class LanguageDBService implements InMemoryDbService {
    createDb() {
        let languages = [
            {id: 1, name: 'C++'},
            {id: 2, name: 'Java'},
            {id: 3, name: 'Javascript'},
            {id: 4, name: 'Ruby'},
            {id: 5, name: 'Python'},
            {id: 6, name: 'Golang'} 
        ];
        return {languages};
    }
}

Lớp này có implement lớp giao diện InMemoryDbService, đây là lớp hỗ trợ tạo webserver cục bộ, khi implement lớp đó chúng ta phải code phương thức createDb(), phương thức này phải trả về một đối tượng mảng (lưu ý trong câu lệnh return thì mảng phải được bọc trong cặp dấu ngoặc nhọn {}), nên chúng ta tạo một đối tượng có tên languages lưu thông tin các ngôn ngữ lập trình. Một lưu ý khác là các phần tử mảng phải có một phần tử tên là id.

Khi biên dịch thì InMemoryDbService sẽ hiểu là tạo một dịch vụ web API cục bộ cho phép thao tác trên mảng đó, và cung cấp một số URL cho các API, ở đây mình chỉ liệt kê một số URL thường dùng:

  • GET – api/languages: lấy danh sách
  • GET – api/languages/{id}: lấy theo id
  • POST – api/languages: tạo mới đối tượng
  • PUT – api/languages/{id}: cập nhật theo id
  • DELETE – api/languages/{id}: xóa theo id

Bạn có thể tìm hiểu thêm tại đây: https://github.com/angular/in-memory-web-api

Truy vấn API

Để có thể truy vấn các API này thì chúng ta sử dụng lớp Http có trong Angular.

Chúng ta sửa lại lớp AppComponent trong file app.component.ts như sau:

import { Component, OnInit } from '@angular/core';

import { Headers, Http } from '@angular/http';
import 'rxjs/add/operator/toPromise';

import { LanguageComponent } from './language.component';

import { Language } from './language';

@Component({
    selector: 'my-app',
    template: `
        <div class="container">
        <h1>List of programming languages:</h1>
            <ul class="list-group">
                <li *ngFor="let lang of languages" class="list-group-item">
                    {{lang.id}} - {{lang.name}}
                </li>
            </ul> 
        </div>
    `,
})
export class AppComponent { 
 
    private languages: Language[];
    private headers = new Headers({'Content-Type' : 'application/json'});
 
    constructor(private http: Http) {} ;
 
    ngOnInit() {
        this.http.get("http://localhost:3000/api/languages")
                 .toPromise()
                 .then(response => { 
                     this.languages = response.json().data as Language[]; 
                 })
                 .catch(this.handleError); 
    }
 
    private handleError(error: any): Promise<any> {
        return Promise.reject(error.message || error);
    }
}

Chúng ta sẽ cần đến các lớp Headers, Http từ @angular/http, hàm toPromise() từ rxjs/add/operator. Lớp Language từ file language.ts đã định nghĩa ở trên. OnInit từ @angular/core để định nghĩa phương thức ngOnInit().

Đầu tiên chúng ta khai báo thuộc tính languages có kiểu mảng đối tượng của lớp Language. Sau đó khai báo thuộc tính headers từ lớp Headers, đây là lớp hỗ trợ khai báo các metadata trong gói tin HTTP.

Lớp HTTP là một lớp service, do đó chúng ta tạo biến http trong phương thức constructor() luôn.

Ở trong phương thức ngOnInit(), chúng ta gọi phương thức http.get(). Đây là phương thức dùng để gửi gói tin HTTP lên các webserver dùng phương thức GET, tham số là một đường url, phương thức này sẽ trả về một đối tượng thuộc lớp Observable, nói đơn giản thì Observable cũng có chức năng lấy dữ liệu bất đồng bộ (asynchronous) như Promise nhưng hiện đại hơn, tuy nhiên chúng ta sẽ không đi sâu vào tìm hiểu, do đó chúng ta sử dụng phương thức toPromise() để chuyển về một đối tượng Promise. Rồi gọi phương thức then() để thực hiện hàm callback, tham số của hàm callback này là dữ liệu về các ngôn ngữ lập trình mà lớp InMemoryDbService cung cấp, chúng ta đặt tên tham số là response, chúng ta gọi phương thức json().data, chuyển thành mảng Language[] và gán vào thuộc tính languages. Cuối cùng chúng ta gọi phương thức catch(), phương thức này dùng để bắt lỗi ngoại lệ nếu có, tham số của phương thức này là một phương thức khác, ở đây là handleError() do chúng ta tự đặt.

Trong phương thức handleError() chúng ta trả về một đối tượng Promise từ phương thức Promise.reject() với thông báo lỗi từ error.message.

Cuối cùng để chạy được thì chúng ta phải khai báo các lớp cần thiết trong lớp AppModule (app.module.ts):

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';

import { HttpModule } from '@angular/http';
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';

import { LanguageDBService } from './language-db.service';
import { LanguageComponent } from './language.component';

@NgModule({
    imports: [ 
        BrowserModule,
        HttpModule,
        InMemoryWebApiModule.forRoot(LanguageDBService)
    ],
    declarations: [ 
        AppComponent, 
        LanguageComponent 
    ],
    bootstrap: [ AppComponent ]
})
export class AppModule { }

Chúng ta import các lớp tự định nghĩa là LanguageComponent, LanguageDBService và khai báo trong mảng declarations. Khai báo 2 module cần dùng để tạo webserver và truy vấn webserver là HttpModuleInMemoryWebApiModule, rồi khai báo trong mảng imports. Khi khai báo module InMemoryWebApiModule thì chúng ta phải gọi phương thức forRoot() và truyền vào một lớp đã implements lớp InMemoryDbService, ở đây là lớp LanguageDBService.

Bây giờ giao diện trang web sẽ như thế này:

Để gửi truy vấn POST thì chúng ta dùng phương thức post() như sau:

...
ngOnInit() {    
    this.http.post(
                      "http://localhost:3000/api/languages", 
                      JSON.stringify({name: 'Pascal'}), 
                      {headers: this.headers}
                  )
             .toPromise()
             .then(res => res.json().data as Language)
             .catch(this.handleError);
    this.http.get("http://localhost:3000/api/languages")
             .toPromise()
             .then(response => { 
                 this.languages = response.json().data as Language[]; 
             })
             .catch(this.handleError); 
}
...

Tham số đầu tiên là URL, tham số thứ 2 là một chuỗi JSON, tham số thứ 3 là đối tượng Headers. API này sẽ trả về thông tin về đối tượng mới được tạo.

Để gửi gói tin DELETE thì chúng ta dùng phương thức delete(), ví dụ:

...
ngOnInit() {
    this.http.delete(
                        "http://localhost:3000/api/languages/2", 
                        {headers: this.headers}
                    )
             .toPromise()
             .then(() => null)
             .catch(this.handleError);
 
    this.http.post(
                      "http://localhost:3000/api/languages", 
                      JSON.stringify({name: 'Pascal'}), 
                      {headers: this.headers}
                  )
             .toPromise()
             .then(res => res.json().data as Language)
             .catch(this.handleError);
 
    this.http.get("http://localhost:3000/api/languages")
             .toPromise()
             .then(response => { 
                 this.languages = response.json().data as Language[]; 
             })
             .catch(this.handleError); 
 }
...

Phương thức delete() nhận vào url và một đối tượng Headers, không trả về cái gì cả nên chúng ta cho return null.

Để cập nhật một đối tượng thì chúng ta gọi phương thức put(), ví dụ:

...
ngOnInit() {
    let language: Language = {
        id: 3,
        name: 'Javascript and TypeScript'
    };
    this.http.put(
                     "http://localhost:3000/api/languages/3", 
                     JSON.stringify(language),
                     {headers: this.headers}
                 )
            .toPromise()
            .then(res => res.json().data as Language)
            .catch(this.handleError);
 
    this.http.delete(
                        "http://localhost:3000/api/languages/2", 
                        {headers: this.headers}
                    )
             .toPromise()
             .then(() => null)
             .catch(this.handleError);
 
    this.http.post(
                      "http://localhost:3000/api/languages", 
                      JSON.stringify({name: 'Pascal'}), 
                      {headers: this.headers}
                  )
             .toPromise()
             .then(res => res.json().data as Language)
             .catch(this.handleError);
 
    this.http.get("http://localhost:3000/api/languages")
             .toPromise()
             .then(response => { 
                 this.languages = response.json().data as Language[]; 
             })
             .catch(this.handleError); 
}
...

Phương thức put() nhận vào url, chuỗi JSON của đối tượng cần chỉnh sửa, và một đối tượng Headers, phương thức này trả về đối tượng đã được chỉnh sửa thành công.