Author Archives: Phở Code

Java 8 – Cài đặt

Chúng ta có thể dịch và chạy code Java từ dòng lệnh hoặc dùng IDE đều được.

Sử dụng NetBeans IDE

Bạn lên trang web chính thức của Oracle và tải Java cùng với bộ phần mềm NetBeans tại địa đây:

http://www.oracle.com/technetwork/java/javase/downloads/index.html

Click vào nút Download phía trên dòng chữ NetBeans with JDK 8, click nút Accept License Agreement rồi click vào đường link phù hợp với hệ điều hành của mình để tải về.

Sau đó tiến hành cài đặt như bình thường.

Để tạo project mới thì chúng ta vào menu File → New Project…, chọn kiểu project là Java Application, sau đó bấm Next, tiếp theo chúng ta đặt tên project, chọn thư mục lưu trữ, mọi thứ khác giữ nguyên cũng được, rồi bấm Finish.

Ở trên mình tạo project có tên Example.

Nếu bạn giữ nguyên ô check Create Main Class thì NetBeans sẽ tự tạo cho chúng ta một file có tên Example.java, đây là file chứa code Java:

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package example;

/**
 *
 * @author Gigabyte
 */
public class Example {

    /**
      * @param args the command line arguments
    */
    public static void main(String[] args) {
        // TODO code application logic here
    }
 
}

Chúng ta sẽ tìm hiểu đoạn code trên sau. Để dịch và chạy đoạn code trên thì chúng ta bấm nút F6 hoặc click chuột vào nút hình tam giác màu xanh lá cây.

Trong cửa sổ Output ở dưới có hiện ra dòng chữ BUILD SUCCESSFUL là đã biên dịch và chạy thành công.

Biên dịch từ dòng lệnh

Nếu máy tính của bạn “cùi” quá không chạy nổi NetBeans hoặc bạn muốn sử dụng dòng lệnh cho “cool”, hay vì một lý do gì đó…v.v thì bạn cũng bắt đầu bằng việc lên trang web chính thức của Oracle tải về trình biên dịch Java Platform (JDK) 8xxx thay vì tải bản NetBeans with JDK 8.

Bạn tải về và cài đặt như bình thường. Nếu bạn cài đặt trên Windows thì bạn phải thêm đường dẫn đến thư mục bin trong thư mục cài đặt Java vào biến môi trường Path, trên Windows 10 bạn có thể vào Computer → System Properties → Advanced system settings → Environment Variables….Trong hộp System variables bạn tìm biến Path, click Edit, click vào nút New và copy đường dẫn đến thư mục bin vào rồi click OK là xong.

Để kiểm tra thì bạn có thể mở Command Prompt (cmd) lên và gõ lệnh java -version để kiểm tra phiên bản Java cũng như kiểm tra xem chúng ta đã đưa đường dẫn đến thư mục bin vào biến Path hay chưa, nếu ra được tương tự như hình dưới đây là được.

Khi chạy bằng dòng lệnh thì chúng ta không có công cụ nào để tạo project cả, chúng ta chỉ đơn giản là tạo một thư mục với tên mà chúng ta muốn đặt thôi. Sau đó bên trong thư mục này chúng ta tạo file .java.

Chẳng hạn mình tạo một thư mục với tên Example trong ổ C: và tạo file Example.java trong thư mục này:

Chúng ta mở file này với các trình soạn thảo văn bản thông thường, ở đây mình dùng Notepad++, và viết đoạn code sau đây:

public class Example {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}

Để dịch file Example.java, chúng ta mở Command Prompt lên, chuyển đến thư mục C:\Example (bằng lệnh cd) chúng ta gõ lệnh javac Example.java.

Chúng ta sẽ được file có tên Example.class trong thư mục Example

Khi có file .class là chúng ta có thể chạy được rồi, chúng ta chạy bằng cách gõ lệnh java -cp . Example. Tham số -cp là đường dẫn đến thư mục chứa file .class vừa được tạo ra, ở đây chúng ta đưa vào dấu chấm '.', tức là thư mục hiện tại trong Command Prompt, nếu thư mục hiện tại trong Command Prompt không phải là thư mục chứa file .class thì bạn phải ghi rõ ra đường dẫn đến thư mục đó.

Kết quả ra dòng chữ Hello World là được.

Java 8 – Giới thiệu

Java là tên của một ngôn ngữ lập trình và một nền tảng lập trình.

Ngôn ngữ lập trình Java

Ngôn ngữ lập trình Java là một ngôn ngữ cấp cao với các đặc tính sau:

  • Đơn giản
  • Hướng đối tượng
  • Phân tán
  • Đa luồng
  • Kiến trúc trung hòa
  • Hiệu suất cao
  • Mạnh mẽ
  • Bảo mật cao

File chứa code Java sẽ có phần mở rộng là .java, khi biên dịch bởi trình biên dịch javac thì các file này sẽ tạo ra các file cùng tên nhưng có phần mở rộng là .class. Khác với các ngôn ngữ như C++, Pascal là khi biên dịch thì ra được các file chứa code nhị phân của hệ điều hành, thì file .class của Java lại chứa mã bytecode, các đoạn code này sẽ được đọc bởi một phần mềm có tên là Máy ảo Java (Java Virtual Machine – Java VM). Chúng ta chỉ cần dùng chạy lệnh java là máy ảo Java sẽ được chạy và đọc code trong file .class.

Máy ảo Java là được viết cho rất nhiều hệ điều hành, từ Windows, Linux, Mac…v.v do đó khi chúng ta viết code trong file .java và dịch ra file .class, thì chỉ cần đem file .class đó lên máy nào có cài máy ảo Java là cũng có thể chạy được mà không cần phải biên dịch lại hay code lại cho phù hợp

Nền tảng lập trình Java

Nền tảng (tiếng Anh: platform) là một môi trường phần mềm hoặc phần cứng để các chương trình có thể chạy trên đó. Chẳng hạn như hệ điều hành Windows, Ubuntu, MacOS, iOS, Android chính là các nền tảng, máy ảo Java cũng là một nền tảng, máy chủ web Apache, NginX, IIS cũng là các nền tảng…v.v

Nền tảng Java bao gồm 2 phần:

  • Máy ảo Java
  • Các thư viện API

Máy ảo Java là một nền tảng để chạy các chương trình viết bằng Java và có mặt trên hầu hết các hệ điều hành và phần cứng phổ biến.

Các thư viện API là tập hợp các phần mềm đã được viết sẵn, chúng cung cấp rất nhiều công dụng hữu ích và được gom nhóm lại thành các thư viện dưới dạng lớp giao diện, rồi được đóng thành các gói, chúng ta sẽ tìm hiểu các khác niệm này sau.

Chính vì các chương trình Java không chạy trực tiếp trên hệ điều hành mà lại chạy trên máy ảo Java được cài trên hệ điều hành đó, nên đôi khi các chương trình này không chạy nhanh bằng các chương trình được viết bằng ngôn ngữ có thể chạy trực tiếp trên hệ điều hành. Tuy nhiên nền tảng Java luôn được phát triển để có thể bắt kịp tốc độ với code của hệ điều hành.

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.

Angular – Routing

Nếu bạn chưa biết Routing là gì thì có thể giải thích ngắn gọn đây là một tính năng cho phép chúng ta điều hướng các URL tới các hàm/phương thức/lớp/controller nào đó trong ứng dụng, đây là tính năng có trong hầu hết các web framework phổ biến ngày nay.

Trong Angular thì một URL sẽ được điều hướng tới một lớp Component, tức là khi người dùng trỏ URL nào vào trong trình duyệt thì Angular sẽ hiển thị template của lớp Component được điều hướng tương ứng.

Ví dụ

Chúng ta sẽ viết trang hiển thị danh sách các ngôn ngữ lập trình và trang hiển thị thông tin chi tiết của ngôn ngữ đó.

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

Trong project này mình có sử dụng các lớp CSS của bootstrap, bạn có thể lấy link CDN của bootstrap tại đây và thêm vào trong file index.html.

Tiếp theo chúng ta tạo một file có tên language.ts có nội dung như sau:

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

export const LANGUAGES: Language[] = [
    {id: 1, name: 'C++'},
    {id: 2, name: 'Java'},
    {id: 3, name: 'Javascript'},
    {id: 4, name: 'Ruby'},
    {id: 5, name: 'Python'},
    {id: 6, name: 'Golang'} 
];

Chúng ta định nghĩa lớp Language có 2 thuộc tính là idname. Và một biến LANGUAGES lưu danh sách các đối tượng Language.

Tiếp theo chúng ta tạo file có tên language.component.ts như sau:

import { Component } from '@angular/core';
import { Language, LANGUAGES } from './language';

@Component({
    selector: 'language',
    template: `
        <h1>List of programming languages:</h1>
        <ul *ngFor="let lang of languages" class="list-group"> 
            <li class="list-group-item">
                {{lang.id}} - {{lang.name}}
            </li>
        </ul>
    `
})
export class LanguageComponent {
    languages: Language[]; 
 
    constructor() {
        this.languages = LANGUAGES;
    }
}

Trong này chúng ta định nghĩa lớp LanguageComponent dùng để hiển thị danh sách các ngôn ngữ lập trình, danh sách này chúng ta lấy từ biến LANGUAGES đã định nghĩa ở trên.

Bây giờ đến phần chính là phần định nghĩa routing. Đầu tiên chúng ta sửa lại file app.module.ts như sau:

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

import { AppComponent } from './app.component';
import { RouterModule } from '@angular/router';
import { LanguageComponent } from './language.component';

@NgModule({
    imports: [ 
        BrowserModule,
        RouterModule.forRoot([            
            {
                path: 'languages',                
                component: LanguageComponent
            },
        ])
    ],
    declarations: [ 
        AppComponent, 
        LanguageComponent, 
    ],
    bootstrap: [ AppComponent ]
})
export class AppModule { }

Điểm cần lưu ý ở đây là chúng ta cần import lớp RouterModule từ @angular/router, sau đó khai báo trong mảng imports. Khi khai báo lớp RouterModule thì chúng ta phải gọi phương thức forRoot() của lớp này, bên trong phương thức này chúng ta truyền vào một mảng, các phần tử của mảng là các đối tượng gồm 2 thuộc tính là pathcomponent, trong đó path là đường dẫn URL do chúng ta định nghĩa, component là tên lớp Component sẽ được dùng để hiển thị trên trang web (lưu ý nhớ import lớp này vô trước và cũng phải khai báo trong mảng declarations).

Và kể từ bây giờ, khi người dùng gõ vào thanh địa chỉ trên trình duyệt là http://localhost:3000/languages thì phần template của lớp LanguageComponent sẽ được hiển thị, nhưng hiển thị ở đâu? Chúng ta phải chỉ định vị trí hiển thị nữa mới đủ.

Angular sẽ hiển thị ở những nơi có element <router-outlet>, do đó bây giờ chúng ta sửa lại lớp AppComponent như sau:

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

@Component({
    selector: 'my-app',
    template: `
        <div class="container"> 
            <router-outlet></router-outlet> 
        </div>
    `,
})
export class AppComponent { }

Và bây giờ nếu bạn trỏ tới đường dẫn http://localhost:3000/languages thì sẽ ra được trang có hình như bên dưới:

Chúng ta có thể thêm một element <a> để click vào URL như sau:

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

@Component({
    selector: 'my-app',
    template: `
        <div class="container"> 
            <a routerLink="/languages">Languages</a>
            <router-outlet></router-outlet> 
        </div>
    `,
})
export class AppComponent { }

Ở đây chúng ta dùng thuộc tính routerLink của lớp RouterModule, bạn cũng có thể sử dụng thuộc tính href, tuy nhiên chúng ta nên dùng routerLink là để tận dụng tính năng lấy tham số trong URL của lớp này.

Tham số URL

Bây giờ chúng ta sẽ định nghĩa URL /detail để hiển thị thông tin chi tiết của các ngôn ngữ lập trình. Đầu tiên chúng ta sửa lại file app.module.ts như sau:

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

import { AppComponent } from './app.component';
import { RouterModule } from '@angular/router';
import { LanguageComponent } from './language.component';
import { LanguageDetailComponent } from './language-detail.component';

@NgModule({
    imports: [ 
        BrowserModule,
        RouterModule.forRoot([
            {
                path: 'languages',
                component: LanguageComponent
            },
            {
                path: 'detail/:id',
                component: LanguageDetailComponent
            }
        ])
    ],
    declarations: [ AppComponent, LanguageComponent, LanguageDetailComponent ],
    bootstrap: [ AppComponent ]
})
export class AppModule { }

Chúng ta định nghĩa URL mới với path là detail/:id, với component là lớp LanguageDetailComponent. 

detail/:id có nghĩa là url có dạng localhost:3000/detail/1, localhost:3000/detail/abc…v.v Tức là có kèm theo tham số, và tham số này chúng ta đặt tên là :id (lưu ý phải có dấu 2 chấm ':').

Tiếp theo chúng ta định nghĩa lớp LanguageDetailComponent, chúng ta tạo file language-detail.component.ts như sau:

import { Component, OnInit } from '@angular/core';
import { Language, LANGUAGES } from './language';
import { ActivatedRoute, Params } from '@angular/router';

@Component({
    selector: 'lang-detail',
    template: `
        <h1>Detail:</h1>
        <div *ngIf="language">
            <h4>
                id: {{language.id}} 
            </h4>
            <h4>
                Name: {{language.name}}
            </h4>
        </div> 
    `
})
export class LanguageDetailComponent {
    language: Language; 
 
    constructor(
        private route: ActivatedRoute
    ) { }
 
    ngOnInit() {
        this.route.params.subscribe( (params) => {
            for(let lang of LANGUAGES) {
                if(lang.id == +params['id']) {
                    this.language = {
                        id: +params['id'],
                        name: lang.name
                    }
                }  
            } 
        });
    }
}

Để có thể lấy giá trị tham số thì đầu tiên chúng ta phải import 2 lớp cần thiết là ActivatedRouteParams từ @angular/router, đây là các lớp service của Angular. Sau đó chúng ta khởi tạo thuộc tính route trong phương thức constructor().

Tiếp theo chúng ta định nghĩa phương thức ngOnInit() (nhớ phải import lớp OnInit trước), phương thức này được gọi mỗi khi lớp này được gọi, trong phương thức này chúng ta lấy đối tượng Language theo tham số được gửi đến trong URL.

Để lấy tham số trong URL thì chúng ta gọi phương thức route.params.subscribe(). Phương thức này nhận vào một hàm callback có tham số là một đối tượng lưu giữ các tham số của URL, chúng ta đặt tên là params. Để lấy giá trị của các tham số, ví dụ như lấy tham số :id thì chúng ta chỉ cần ghi  +params['id'] là được, chú ý phải có dấu cộng '+'. Ở đây chúng ta lặp mảng LANGUAGES và tìm xem phần tử nào có thuộc tính id trùng với tham số :id thì gán phần tử đó cho thuộc tính language.

Cuối cùng chúng ta hiển thị nội dung của thuộc tính đó lên template.

Bây giờ bạn có thể gõ vào trình duyệt các URL như localhost:3000/details/1 và sẽ thấy thông tin của ngôn ngữ lập trình tương ứng.

RouterModule cũng cung cấp url có tham số với cú pháp riêng, chúng ta sửa lại lớp LanguageComponent như sau:

import { Component } from '@angular/core';
import { Language, LANGUAGES } from './language';

@Component({
    selector: 'language',
    template: `
        <h1>List of programming languages:</h1>
        <ul *ngFor="let lang of languages" class="list-group"> 
            <li class="list-group-item">
                <a [routerLink]="['/detail', lang.id]">
                    {{lang.id}} - {{lang.name}}
                </a>
            </li>
        </ul>
    `
})
export class LanguageComponent {
    languages: Language[]; 
 
    constructor() {
        this.languages = LANGUAGES;
    }
}

Chúng ta gán routerLink là một mảng với 2 phần tử, phần tử đầu tiên là path, phần tử thứ 2 là giá trị của tham số sẽ được gửi đi, ở đây là lang.id, tức là id của ngôn ngữ lập trình đó.

 

Angular – Service và Dependency Injection

Service (dịch vụ) chẳng qua cũng là một cách giúp cho chúng ta tái sử dụng code mà thôi, chẳng hạn như bạn có một lớp Customer, thì thay vì mỗi lần cần lấy các đối tượng Customer đang có, chúng ta phải viết code để tạo đối tượng, truyền tham số…v.v ở nhiều nơi khác nhau, thì bây giờ chúng ta chỉ cần viết một lớp service làm điều đó luôn cho chúng ta, như vậy việc quản lý code sẽ dễ dàng hơn, chẳng hạn như mỗi lần thay đổi phương thức khởi tạo, thì chúng ta chỉ cần thay đổi code trong lớp service là được, thay vì phải đi sửa lại toàn bộ những dòng code khởi tạo đó.

Dependency Injection là chức năng cho phép chúng ta “nhúng” các lớp vào các lớp khác, giống như dùng một thư viện vậy, và chúng ta có thể dùng các lớp được nhúng vào đó giống như dùng một thuộc tính bình thường mà không cần phải thực hiện các công đoạn khai báo, khởi tạo…v.v

Ví dụ

Chúng ta sẽ viết một lớp service lấy danh sách các ngôn ngữ lập trình.

Đầu tiên chúng ta tạo một project mới từ quickstart, đặt tên là gì cũng được.

Tiếp theo chúng ta viết lớp Language bằng cách 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;
}

Ở đây chúng ta chỉ lưu 2 thông tin đơn giản là idname.

Kế tiếp chúng ta tạo một file có tên language-list.ts trong thư mục src/app như sau:

import { Language } from './language';

export const LANGUAGES: Language[] = [
    {id: 1, name: 'C++'},
    {id: 2, name: 'Java'},
    {id: 3, name: 'Python'},
    {id: 4, name: 'Ruby'},
    {id: 5, name: 'Go'},
    {id: 6, name: 'Javascript'}
];

File này chỉ chứa một đối tượng LANGUAGES là một mảng chứa các đối tượng Language.

Bây giờ chúng ta sẽ viết lớp service, chúng ta tạo một file có tên language.service.ts trong thư mục src/app như sau:

import { Injectable } from '@angular/core';

import { Language } from './language';
import { LANGUAGES } from './languages-list';

@Injectable()
export class LanguageService{
    getLanguages(): Language[] {
       return LANGUAGES;
    }
}

Lớp service ở đây chúng ta đặt tên là LanguageService, trong lớp này có một phương thức là getLanguages(), phương thức này sẽ trả về mảng các đối tượng Language. Mảng này chúng ta lấy từ đối tượng LANGUAGES đã dược định nghĩa ở trên.

Thông thường chúng ta sẽ đặt tên file cho các lớp Service có phần đuôi là .service.ts cho dễ quản lý.

Điểm đáng chú ý ở đây chúng ta có import thêm lớp Injectable từ @angular/core và khai báo @Injectable trước phần khai báo lớp nữa, từ khóa @Injectable cho Angular biết lớp này có thể được “nhúng” vào các lớp khác.

Bây giờ chúng ta sửa lại lớp AppComponent trong file src/app/app.component.ts như sau:

import { Component } from '@angular/core';
import { LanguageService } from './language.service';
import { Language } from './language';

@Component({
    selector: 'my-app',
    template: `
        <div class="container">
            <h1>Programming languages:</h1> 
            <ul class="list-group">
                <li class="list-group-item" *ngFor="let lang of languages">
                    {{lang.id}} - {{lang.name}}
                </li>
            </ul>
        </div>  
    `,
    providers: [ LanguageService ]
})
export class AppComponent { 
    languages: Language[]; 
    constructor(private languageService: LanguageService) {
        this.languages = languageService.getLanguages();
    }
}

Trong lớp AppComponent chúng ta khai báo một thuộc tính là languages, thuộc tính này có kiểu là mảng các đối tượng Language.

Sau đó chúng ta khai báo phương thức constructor(), phương thức này có nhận vào một tham số là:

private languageService: LanguageService

Khai báo như thế thì Angular sẽ tạo một đối tượng thuộc lớp LanguageService cho lớp AppComponent với tên là languageService, và kể từ bây giờ chúng ta có thể dùng đối tượng languageService giống như một thuộc tính bình thường của một lớp luôn. Do đó ngay trong phương thức khởi tạo chúng ta gọi phương thức getLanguages() để lấy danh sách các đối tượng Language rồi gán vào thuộc tính languages.

Một lớp để được khai báo trong constructor() thì ngoài phần import ra, còn phải được khai báo trong tham số mảng providers nữa.

Trong template chúng ta in danh sách các ngôn ngữ lập trình ra, lưu ý ở đây chúng ta có sử dụng bootstrap (bạn có thể lấy link CDN của bootstrap tại đây rồi chèn vào file src/index.html).

Bây giờ bạn có thể lưu file lại và chạy ra được như hình dưới đây:

Tuy nhiên bản chất của các service thường là chạy ngầm, song song với ứng dụng chính, nhưng ở đây service của chúng ta không phải chạy như thế mà vẫn chạy theo thứ tự trước sau cùng với ứng dụng chính. Để lớp service chạy đúng nghĩa với tính chất của một dịch vụ thì ở đây chúng ta dùng lớp Promise, lớp Promise sẽ giúp công việc lấy dữ liệu được thực hiện một cách bất đồng bộ (asynchronous).

Ở đây mình chỉ nói về cách sử dụng chứ không đi sâu vào tìm hiểu cơ chế bất đồng bộ làm gì. Đầu tiên chúng ta sửa lại lớp LanguageService như sau:

import { Injectable } from '@angular/core';

import { Language } from './language';
import { LANGUAGES } from './languages-list';

@Injectable()
export class LanguageService{
    getLanguages(): Promise<Language[]> { 
        return Promise.resolve(LANGUAGES);
    }
}

Chúng ta cho phương thức getLanguages() có kiểu trả về là một đối tượng Promise. Bên trong chúng ta gọi phương thức Promise.resolve(LANGUAGES) để lấy đối tượng LANGUAGES, việc lấy dữ liệu này sẽ được thực hiện ngầm song song với ứng dụng chính.

Ở bên lớp AppComponent chúng ta sửa như sau:

import { Component } from '@angular/core';
import { LanguageComponent } from './language.component';
import { LanguageService } from './language.service';
import { Language } from './language';
@Component({
    selector: 'my-app',
    template: `
        <div class="container">
            <h1>Programming languages:</h1> 
            <ul class="list-group">
                <li class="list-group-item" *ngFor="let lang of languages">
                    {{lang.id}} - {{lang.name}}
                </li>
            </ul>
        </div>  
    `,
    providers: [ LanguageService ]
})
export class AppComponent {  
    languages: Language[]; 
    constructor(private languageService: LanguageService) {
        languageService.getLanguages().then( (values) {
            this.languages = values;
        });
    }
}

Phương thức getLanguages() sẽ sau khi thực thi xong việc lấy dữ liệu thì sẽ gọi một phương thức khác và truyền dữ liệu trả về vào phương thức đó, nếu bạn chưa biết thì đây là cơ chế callback (bạn có thể tìm hiểu thêm ở đây), ở đây phương thức getLanguages() sẽ trả về một đối tượng Promise, để bắt được phương thức callback đó thì chúng ta gọi phương thức then() và truyền vào tham số là một hàm, hàm đó sẽ nhận tham số trả về là mảng LANGUAGES, ở đây chúng ta đặt tên là lang rồi gán giá trị của tham số đó cho thuộc tính this.languages.

Angular – Template – Phần 3

Trong phần này chúng ta sẽ tìm hiểu về biến template và 2 thuộc tính @Input@Output.

Biến template (#var)

Đây là các biến mà chúng ta khai báo cho mỗi element trong một trang web, chúng ta có thể dùng biến này để đọc dữ liệu và gọi phương thức của lớp component hoặc chỉ thị.

Để khai báo biến template thì chúng ta dùng kí tự # rồi ghi tên biến ngay bên trong element.

<input #phone placeholder="phone number">

Trong đoạn code trên chúng ta khai báo biến #phone cho element <input>. Sau khi đã khai báo thì chúng ta có thể gọi đến biến này ở bất cứ đâu trong đoạn code template đó.

<input #phone placeholder="phone number">

<button (click)="callPhone(phone.value)">Call</button>

Khi gọi biến template thì chúng ta ghi tên biến ra nhưng không ghi dấu #.

Trong đoạn code trên, khi chúng ta click button thì biến #phone sẽ lấy dữ liệu trong thuộc tính value của element <input>, rồi truyền vào phương thức callPhone().

Nếu chúng ta chỉ khai báo biến template không như thế thì thuộc tính value của biến hầu như sẽ là value của element, tuy nhiên chúng ta có thể chỉ định biến template này tham chiếu tới một đối tượng khác, chẳng hạn như một chỉ thị, ví dụ:

<form (ngSubmit)="onSubmit(customerorm)" #customerForm="ngForm">
    <div class="form-group">
        <label for="name">
            Name
            <input class="form-control" name="name" required [(ngModel)]="customer.name">
        </label>
    </div>
    <button type="submit" [disabled]="!customerForm.form.valid">Submit</button>
</form>

Nếu bạn còn nhớ thì mỗi khi chúng ta tạo form thì Angular sẽ gán cho form đó một đối tượng chỉ thị ngForm luôn để chúng ta có thể thực hiện một số thao tác trên form dễ dàng, chúng ta chỉ cần gán đối tượng ngForm đó cho biến template là có thể sử dụng được.

Chúng ta có thể sử dụng tiền tố ref- thay cho kí tự #, tùy bạn thích dùng cái nào cũng được.

<input ref-fax placeholder="fax number">
<button (click)="callFax(fax.value)">Fax</button>

Thuộc tính @Input và @Output

Hai thuộc tính này có tác dụng khai báo các biến và sự kiện dùng trong việc kết nối dữ liệu.

Trong các bài trước chúng ta đã quen với việc kết nối dữ liệu từ thuộc tính và sự kiện của element sang một thuộc tính hoặc phương thức của lớp component, ví dụ:

<img [src]="iconUrl"/>
<button (click)="onSave()">Save</button>

Trong đoạn code trên thì src là thuộc tính của element <img> và được kết nối tới thuộc tính iconUrl, click là sự kiện của element <button> và được kết nối tới phương thức onSave().

Điều này cũng có nghĩa là chúng ta chỉ được phép sử dụng các thuộc tính và sự kiện có sẵn của element như srcclick thôi, trong Angular có 2 thuộc tính @Input@Output, 2 thuộc tính này sẽ cho phép chúng ta tự tạo ra các thuộc tính và phương thức có thể bắt dữ liệu riêng cho chúng ta.

Ví dụ, chúng ta tạo một project mới và tạo một lớp tên CustomerFormComponent như sau:

import { Component, Input, Output, EventEmitter } from '@angular/core';
import { Customer } from './customer';

@Component({
    moduleId: module.id,
    selector: 'customer', 
    template: `<button type="button" class="btn btn-primary" (click)="emitEvent()">
                   Change name
               </button>
    ` 
})
export class CustomerFormComponent { 
    @Input() name: string;
    @Output() changeName = new EventEmitter(); 
 
    emitEvent() {
        this.changeName.emit();
    }
}

Chúng ta khai báo các thuộc tính @Input@Output bằng cách thêm 2 từ khóa này vào trước tên biến, sau đó khai báo kiểu dữ liệu đối với thuộc tính @Input hoặc gán một đối tượng EventEmitter cho thuộc tính @Output. Để có thể sử dụng @Input@Output thì chúng ta phải import từ @angular/core, ngoài ra ở đây chúng ta còn import thêm cả lớp EventEmitter nữa.

Lớp EventEmitter là lớp giúp phát sinh sự kiện, để phát sự kiện thì chúng ta gọi phương thức emit() của lớp này. Trong đoạn code trên lớp CustomerFormComponent có một template bao gồm 1 button, khi click button này thì phương thức emitEvent() sẽ được gọi, trong phương thức này chúng ta gọi phương thức emit() của đối tượng changeName để phát sinh sự kiện. Chi tiết về lớp EventEmitter sẽ được trình bày trong bài khác.

Tiếp theo chúng ta sử dụng lớp CustomerFormComponent như sau:

import { Component } from '@angular/core';
import { CustomerFormComponent } from './customer-form.component';

@Component({
    selector: 'my-app',
    template: ` 
        <customer [name]="username" (changeName)="saveNewName()"></customer> 
        {{username}}
    `,
})
export class AppComponent { 
    username = "Pho Code";
 
    saveNewName() {
        this.username = "Pho Code Blog";
    }
}

Chúng ta import lớp CustomerFormComponent, sau đó khai báo selector, rồi bắt các thuộc tính @Input@Output như bắt các thuộc tính và sự kiện thông thường.

Angular – Directive – Phần 2

Trong phần này chúng ta tiếp tục tìm hiểu về directive.

Structural directive

Các chỉ thị thuộc loại structural chịu trách nhiệm điều khiển cách dữ liệu được hiển thị, chẳng hạn như thêm, bớt, chỉnh sửa các element…v.v Ở đây chúng ta chỉ tìm hiểu 3 chỉ thị thường dùng là ngIf, ngForngSwitch.

Nếu bạn để ý thì có thể nhận thấy chức năng của 3 chỉ thị này giống với các câu lệnh if, for, switch trong các ngôn ngữ lập trình.

ngIf

Chỉ thị ngIf cho phép chúng ta thêm hoặc loại bỏ một element ra khỏi trang, chúng ta gán giá trị cho chỉ thị này là một biểu thức nào đó có trả về giá trị true hoặc false, nếu biểu thức trả về true thì element sẽ hiện ra, ngược lại thì không.

<customer *ngIf="isActive"></customer>

Trong đoan code trên, element <customer> sẽ được hiển thị nếu isActive trả về true, isActive có thể là một thuộc tính/biến nào đó hoặc một phương thức…v.v

Lưu ý luôn phải có dấu sao * trước ngIf.

Một điều khác là ở đây ngIf thêm hoặc bỏ element trong trang web, chứ không phải là ẩn hay hiện element đó, tức là khác với thuộc tính hidden của các element trong HTML.

Thông thường chúng ta dùng ngIf để kiểm tra xem một đối tượng nào đó có NULL hay không

<div *ngIf="currentCustomer">Hello, {{currentCustomer.name}}</div>
<div *ngIf="nullCustomer">Hello, {{customer.name}}</div>

ngFor

Đây là chỉ thị lặp, có tác dụng lặp qua một danh sách các phần tử, khi chúng ta có một danh sách các phần tử, muốn hiển thị chúng lên trang web thì chúng ta lặp qua danh sách đó và hiển thị các phần tử theo một khuôn mẫu giống nhau. Ví dụ:

<div *ngFor="let cus of customers">
    {{cus.name}}
</div>

Giá trị của ngFor là một câu lệnh có cú pháp như sau:

let <biến lặp> of <danh sách>

Biến lặp là do chúng ta tự đặt, bạn muốn đặt là gì cũng được, ngFor sẽ lặp qua danh sách và mỗi lần lặp thì chúng ta dùng biến lặp để lấy dữ liệu của phần tử hiện tại trong danh sách.

Lưu ý luôn phải có dấu sao * trước ngFor. 

Trong ngFor có một thuộc tính tên là index, thuộc tính này lưu trữ số thứ tự của phần tử đang được lặp, chúng ta có thể lấy số thứ tự giá trị index này như sau:

<div *ngFor="let cus of customers; let i=index">{{i + 1}} : {{cus.name}}</div>

ngSwitch

Chỉ thị ngSwitch cũng tương tự như câu lệnh switch trong Javascript vậy, chỉ thị này có tác dụng hiển thị một element trong một danh sách các element, dựa vào một điều kiện cho trước.

Trong ngSwitch lại có 2 chỉ thị khác nữa là ngSwitchCasengSwitchDefault.

<div [ngSwitch]="currentCustomer.emotion">
    <happy-customer   *ngSwitchCase="'happy'"    [cus]="currentCustomer"></happy-customer>
    <sad-customer     *ngSwitchCase="'sad'"      [cus]="currentCustomer"></sad-customer>
    <confused-custoer *ngSwitchCase="'confused'" [cus]="currentCustomer"></confused-customer>
    <unknown-customer *ngSwitchDefault           [cus]="currentCustomer"></unknown-customer>
</div>

Trong đoạn code trên, ngSwitch được gán bằng giá trị của thuộc tính emotion trong đối tượng currentCustomer, thuộc tính emotion có thể là bất cứ giá trị gì, trong trường hợp này thì đây là một string “happy”, “sad” hoặc “confused”, sau đó bên trong ngSwitch, chúng ta có các element có chỉ thị ngSwitchCase, mỗi chỉ thị ngSwitchCase này được gán giá trị là một chuỗi trùng với thuộc tính emotion, nếu element nào có chỉ thị ngSwitchCase trùng với giá trị của thuộc tính emotion thì element đó được đưa vào trang web, các element còn lại thì không. Nếu không có ngSwitchCase nào trùng thì element có chỉ thị ngSwitchDefault sẽ được đưa vào.

Lưu ý là ngSwitch là một chỉ thị attribute, vì chỉ thị này không trực tiếp chỉnh sửa giao diện, do đó chúng ta không thêm dấu sao * mà bọc trong cặp dấu ngoặc vuông [], còn các chỉ thị ngSwitchCasengSwitchDefault thì cần có dấu sao * phía trước.

Angular – Directive – Phần 1

Trong phần này chúng ta sẽ tìm hiểu về các Directive (chỉ thị) có sẵn trong Angular.

Directive (chỉ thị) là một lớp và có phần khai báo metadata là @Directive, ở đây chúng ta chỉ tìm hiểu về các directive có sẵn trong Angular, còn việc định nghĩa directive sẽ được trình bày trong bài khác. Thường thì directive sẽ nằm trong một element – hay thẻ của HTML giống như một thuộc tính bình thường.

Directive có 2 loại là structuralattribute. 

Attribute directive

Đây là các chỉ thị có tác dụng lắng nghe và thay đổi cách thức hiển thị của các element, thuộc tính… trong HTML. Thông thường chúng cũng được dùng giống như một thuộc tính của một element.

Trong phần này chúng ta sẽ tìm hiểu về 3 loại chỉ thị thường dùng là:

  • NgClass: thêm/bớt các lớp CSS
  • NgStyle: thêm/bớt các style
  • NgModel: kết nối dữ liệu 2 chiều, trong bài trước chúng ta đã có tìm hiểu sơ qua

NgClass

Directive này cho phép bạn thêm hoặc bớt các lớp CSS một cách chủ động. Thông thường chúng ta sẽ gán giá trị cho chỉ thị ngClass là một đối tượng lưu dữ liệu theo dạng từ điển, tức là mỗi phần tử là một cặp <key>:<value>. Ví dụ:

export class SetClass {
    currentClasses: {};
    setCurrentClasses() {   
        this.currentClasses = {
            saveable: this.canSave,
            modified: !this.isUnchanged,
            special: this.isSpecial
        };
    }
}

Trong đoạn code trên, chúng ta có lớp SetClass, lớp này có một đối tượng currentClasses, phương thức setCurrentClasses() sẽ thiết lập đối tượng currentClasses() gồm 3 phần tử là saveable, modified và special, đây cũng sẽ được dùng làm tên lớp CSS luôn, giá trị của mỗi phần tử này dựa vào các thuộc tính khác, ở đây chúng ta dùng thuộc tính canSave, isUnchangedisSpecial.

Khi sử dụng ngClass thì chúng ta chỉ cần làm như sau:

<div [ngClass]="currentClasses">
    This div is initially saveable, unchanged, and special
</div>

Chúng ta kết nối chỉ thị ngClass tới đối tượng currentClasses là được, tất nhiên trước đó chúng ta phải gọi phương thức setCurrentClasses() để thiết lập các phần tử trong đối tượng currentClasses, và element <div> sẽ có class là saveable, unchangedspecial.

NgStyle

Chỉ thị ngStyle có chức năng thiết lập style của element bên trong element đó. Tương tự với ngClass, chúng ta cũng thường gán giá trị cho ngStyle là một đối tượng lưu trữ dạng từ điển.

export class SetStyle {
    currentStyles: {};
    setCurrentStyles() {
        this.currentStyles = {
            'font-style': this.canSave ? 'italic' : 'normal',
            'font-weight': !this.isUnchanged ? 'bold' : 'normal',
            'font-size': this.isSpecial ? '24px' : '12px'
        };
    }
}

Trong đoạn code trên chúng ta có lớp SetStyle, trong này có một đối tượng currentStyles lưu trữ các style CSS theo dạng từ điển, các key sẽ có giá trị tùy thuộc vào các thuộc tính nào đó, nói chung cũng tùy bạn quy định,

<div [ngStyle]="currentStyles">
    This div is initially italic, normal weight, and extra large (24px).
</div>

Và chúng ta cũng thiết lập chỉ thị ngStyle cho thuộc tính currentStyles này, tất nhiên là phải gọi phương thức setCurrentStyles() ở đâu đó trước.

NgModel

Chúng ta đã biết là chỉ thị ngModel dùng để kết nối dữ liệu 2 chiều, tức là vừa có thể đọc dữ liệu từ lớp và hiển thị lên template, vừa có thể chỉnh sửa dữ liệu trên template và cập nhật vào lớp đó. Ví dụ:

<input [(ngModel)]="currentCustomer.name">

Một yêu cầu cần có của chỉ thị ngModel là bạn phải import lớp FormsModule vào lớp AppModule trước thì mới có thể sử dụng:

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

@NgModule({
    imports: [
        BrowserModule,
        FormsModule 
    ],
    ...
})
export class AppModule { }

Angular – Template – Phần 2

Trong phần này chúng ta tiếp tục tìm hiểu về template trong Angular.

Bắt thuộc tính […]

Bắt thuộc tính ở đây là chúng ta gán thuộc tính của một element/thẻ cho một biểu thức của template. Khi bắt thuộc tính thì chúng ta bọc tên thuộc tính trong cặp thẻ ngoặc vuông [].

Thông thường chúng ta gán thuộc tính đó cho một giá trị nào đó trong lớp component.

<img [src]="customerUrl">

Chẳng hạn như trong đoạn code trên, chúng ta gán thuộc tính src của element <img> là giá trị của thuộc tính customerUrl, customerUrl là thuộc tính của một lớp component nào đó.

<button [disabled]="isUnchanged">Cancel is disabled</button>

Hoặc chúng ta gán thuộc tính disabled là giá trị của thuộc tính isUnchanged.

<customer [cust]="currentCustomer"></customer>

Thuộc tính đó cũng có thể là một đối tượng chứ không phải là các giá trị đơn lẻ như số, chuỗi…v.v

Lưu ý:

  • Việc kết nối giá trị từ template vô lớp component là một chiều, chúng ta không thể thay đổi giá trị của thuộc tính thông qua việc bắt thuộc tính
  • Không thể gán giá trị cho thuộc tính là một phương thức
  • Nếu element có phát sinh sự kiện thì chúng ta có thể bắt sự kiện (chúng ta sẽ tìm hiểu sau)

Ngoài việc bọc tên thuộc tính trong cặp dấu ngoặc vuông [], thì chúng ta có thể nối vào đầu tên thuộc tính tiền tố bind-, ví dụ 2 đoạn code dưới đây là giống nhau:

<img [src]="customerUrl">
<img bind-src="customerUrl">

Bạn có thể dùng 1 trong 2 cách trên đều được.

Cú pháp bắt thuộc tính này cũng giống như cú pháp {{...}} vậy, 2 đoạn code dưới đây là tương đương nhau:

<img src="{{customerUrl}}">
<img [src]="customerUrl">

Việc dùng cú pháp nào là tùy ở bạn, bạn thấy thích dùng cái gì thì dùng.

Bắt sự kiện (…)

Bắt thuộc tính là lấy dữ liệu từ lớp component truyền lên template, bắt sự kiện thì ngược lại là phát sinh dữ liệu từ template và truyền về lớp component.

Sự kiện là những hành động như nhập chữ vào ôn các ô input, click chuột, chọn item từ một danh sách.

Cách duy nhất để biết sự kiện gì vừa xảy ra là lắng nghe sự kiện đó – hay bắt sự kiện đó.

Cú pháp bắt sự kiện bao gồm tên sự kiện được bọc trong cặp dấu ngoặc tròn (), dấu bằng =, và cuối cùng là một câu lệnh nằm trong cặp dấu nháy kép "". Ví dụ:

<button (click)="onSave()">Save</button>

Trong đoạn code trên, chúng ta lắng nghe sự kiện (click), tức là click chuột, khi người dùng click chuột vào button thì sự kiện sẽ xảy ra, và câu lệnh ở bên phải sẽ được thực thi, ở đây là lời gọi hàm onSave().

Ngoài việc bọc tên sự kiện trong cặp dấu ngoặc tròn thì chúng ta có thể chèn trước tên sự kiện tiền tố on-, ví dụ 2 đoạn code dưới đây là giống nhau:

<button (click)="onSave()">Save</button>
<button on-click="onSave()">On Save</button>

Khi sự kiện xảy ra, Angular sẽ thực thi câu lệnh trong dấu nháy kép, ngoài ra Angular còn truyền thêm dữ liệu đi kèm nữa, các dữ liệu đó sẽ được nằm trong một đối tượng có tên là $event

 
<button (click)="onSave($event)">Save</button> 

Đối tượng này chứa những gì tùy thuộc vào loại sự kiện, nếu sự kiện xảy ra là các sự kiện thông thường trong HTML, mà người ta hay gọi là sự kiện DOM, thì $event sẽ chứa những gì mà chuẩn DOM quy định, chẳng hạn như target, target.value...v.v Bạn có thể xem các sự kiện DOM của đây:

https://developer.mozilla.org/en-US/docs/Web/Events 

Bắt dữ liệu 2 chiều [(…)]

Đây là cú pháp cho phép bạn vừa có thể lấy dữ liệu từ component, vừa có thể chỉnh sửa dữ liệu đó từ template.

Để làm việc này thì chúng ta bọc thuộc tính của element trong cặp dấu [()]. Lưu ý là dấu ngoặc tròn () bao giờ cũng nằm trong dấu ngoặc vuông []. Ví dụ:

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

@Component({
    selector: 'my-app',
    template: ` 
        <input [(ngModel)]="username"> 
        

Hello {{username}}

    `,
})
export class AppComponent { 
    username = "";
}

Lớp AppComponent có một thuộc tính là username. Thuộc tính ngModel có giá trị là giá trị của thuộc tính username, khi người dùng thay đổi nội dung trong element <input> thì giá trị của ngModel cũng được thay đổi theo.

Angular – Template – Phần 1

Trong các bài trước chúng ta đã làm việc qua với cú pháp của template, trong phần này chúng ta sẽ tìm hiểu kỹ hơn.

Hệ thống template chịu trách nhiệm việc hiển thị nội dung lên trang web, trong các web framework phổ biến khác như Django, Ruby on Rails… v.v cũng có hệ thống template tương tự như của Angular.

HTML

Ngôn ngữ chính của template là HTML, nhưng không phải các phần tử (hay các thẻ) đều hợp lệ với Angular, điển hình là thẻ <script>, với Angular thì <script> sẽ bị bỏ qua, không được biên dịch vì lý do bảo mật.

Chúng ta cũng có thể tự tạo ra các thẻ cho riêng mình thông qua component như trong các bài trước đã làm.

Cú pháp lấy dữ liệu {{…}}

Chúng ta đã làm việc với cú pháp sử dụng cặp dấu ngoặc nhọn {{...}} nhiều rồi, đây là cú pháp dùng để lấy giá trị của thuộc tính trong lớp component.

<h3>
     {{title}}
     <img src="{{name}}" style="height:30px">
</h3>

Angular sẽ lấy giá trị của thuộc tính trong lớp component rồi chuyển thành chuỗi và thay vào đoạn dấu ngoặc nhọn {{...}}.

<p>The sum of 1 + 1 is {{1 + 1}}</p>

Chúng ta cũng có thể thực hiện tính toán trong này.

Biểu thức

Biểu thức ở đây là các phép tính cộng, trừ, nhân, chia, gán, so sánh…v.v

Hầu hết các biểu thức có thể sử dụng là các biểu thức của Javascript, các biểu thức có thể sử dụng là:

  • Phép gán: =, +=, -= ...
  • new
  • Dấu chấm phẩy (;), dấu phẩy (,)
  • Phép tăng (++), giảm (--)

Nhưng không phải tất cả đều có thể dùng được:

  • Không thể sử dụng toán tử bit OR (ký hiệu |) và toán tử AND (ký hiệu &)

Ngữ cảnh của biểu thức

Một “ngữ cảnh” nói một cách đơn giản là các đối tượng thực hiện các biểu thức.

{{title}}
<span [hidden]="isUnchanged">changed

Trong đoạn code trên thì titleisUnchanged là các thuộc tính của một lớp component nào đó, chẳng hạn như lớp AppComponent, AppComponent chính là một “ngữ cảnh”.

Không phải tất cả các biến trong biểu thức luôn luôn thuộc về một đối tượng ngữ cảnh nào đó, ví dụ:

<div *ngFor="let cus of customers">{{cus.name}}</div>

Ở đây cus lại là một đối tượng trong phép duyệt mảng customers thôi.

Chúng ta không thể dùng các đối tượng toàn cục của Javascript hay Node.js…v.v như console.log, Math.max, window, document… trong biểu thức được.

Câu lệnh

Các câu lệnh là các đoạn code thực hiện một công việc gì đó để phản hồi lại các sự kiện. Ví dụ:

<button (click)="createCustomer()">Register</button>

Trong đoạn code trên thì createCustomer() chính là một câu lệnh trả lời lại sự kiện (click).

Ngữ cảnh của câu lệnh

Cũng tương tự như ngữ cảnh của biểu thức, ngữ cảnh của câu lệnh cũng là một đối tượng của lớp component đã tạo ra template đó. Chẳng hạn như trong đoạn code trên thì câu lệnh createCustomer() có thể có ngữ cảnh là đối tượng của một lớp AppComponent nào đó.

Câu lệnh gọi hàm có thể nhận vào tham số là các biến biểu thức, ví dụ:

<button (click)="onSave($event)">Save</button>
<button *ngFor="let cus of customers" (click)="createCustomer(cus)"></button>
<form #customerForm (ngSubmit)="onSubmit(customerForm)"> ... </form>

Và cũng tương tự như ngữ cảnh của biểu thức, chúng ta không thể gọi những đối tượng toàn cục như window, document, console.log, Math.max…v.v

Cú pháp kết nối dữ liệu

Đây là các cú pháp dùng để kết nối dữ liệu qua lại giữa lớp component và template, hỗ trợ việc đọc ghi dữ liệu một cách dễ dàng, chiều kết nối có thể là 1 chiều hoặc 2 chiều, tức là chỉ có thể đọc dữ liệu từ lớp component ra template hoặc ngược lại hoặc cả hai. Việc chúng ta cần làm là ghi cú pháp ra và Angular sẽ lo nốt phần bên dưới.

Nhìn chung thì có thể chia các cú pháp này ra làm 3 loại dựa theo chiều kết nối:

CHIỀU KẾT NỐI
CÚ PHÁP
LOẠI (TARGET)
1 chiều từ lớp component tới template
{{biểu thức}}
[target]="biểu thức"
bind-target="biểu thức"
Thuộc tính
Lớp
1 chiều từ template về lớp component
(target)="câu lệnh"
on-target="câu lệnh"
Sự kiện
2 chiều
[(target)]="biểu thức"
bindon-target="biểu thức"
2 chiều

Loại (hay target) là tên các sự kiện, lớp, thuộc tính… và được bọc trong cặp dấu ngoặc vuông [], ngoặc tròn () hoặc sau các tiền tố bind-, on-, bindon-.