Author Archives: Phở Code

Angular – Theo dõi trạng thái form với ngModel

Trong bài trước chúng ta đã tìm hiểu qua cách liên kết dữ liệu hai chiều với ngModel, ngModel không những thực hiện chức năng đó mà còn có khả năng kiểm tra trạng thái của các phần tử control trong form, chẳng hạn như nội dung textbox thay đổi, người dùng click vào…v.v

Mỗi khi có sự thay đổi trên các phần tử trong form, ngModel sẽ đổi tên class của các phần tử đó, cụ thể:

Trạng thái

TRẠNG THÁI TÊN CLASS (TRUE) TÊN CLASS (FALSE)
Người dùng click vào control ng-touched ng-untouched
Người dùng thay đổi dữ liệu của control ng-dirty ng-pristine
Dữ liệu của control không hợp lệ ng-valid ng-invalid

Chúng ta có thể lấy tên các lớp này thông qua biến trong template.

Bây giờ chúng ta sẽ dùng form customer đã làm trong bài trước để ví dụ, sửa lại file customer-form.component.html như sau:

<div class="container">
    <h1>Customer</h1>
    <form>
        <div class="form-group">
            <label for="name">Name</label>
            <input type="text" 
                class="form-control"  
                id="name" 
                [(ngModel)]="customer1.name" 
                name="cus1_name" 
                required 
                #cus_class>           
                <p>Classes: {{cus_class.className}}</p>
        </div>
    ....     
    </form>
</div>

Chúng ta khai báo biến template cho thẻ <input> là #cus_class, sau đó lấy thuộc tính className của biến này ra và hiển thị.

Lưu file lại, Angular sẽ reload lại trình duyệt, chúng ta có thể thấy tên các lớp của control này, ngay cả các control khác cũng thế, nếu muốn rõ hơn bạn có thể dùng chức năng insect của Google Chrome để xem.

Bạn thử click vào một ô textbox, sau đó click ra ngoài, gõ thêm kí tự vào ô, xóa nội dung trong ô đó… thì sẽ thấy tên các class này thay đổi tùy theo từng trạng thái.

Chúng ta có thể dùng các lớp này để thay đổi màu sắc, hiển thị text để thông báo cho người dùng, ví dụ chúng ta tạo file CSS có tên customer-form.css trong thư mục src như sau:

.ng-valid:required, .ng-valid.required {
    border-left: 5px solid #42A948;
}

.ng-invalid:not(form) {
    border-left: 5px solid #a94442;
}

Ở đây chúng ta thay đổi màu sắc viền bên trái (border-left) cho 2 lớp ng-invalidng-valid. Khi người dùng xóa trống ô textbox có yêu cầu required thì viền trái sẽ chuyển thành màu đỏ.

Tiếp theo chúng ta phải thêm dòng code <link> tới file này trong file index.html:

<!DOCTYPE html>
<html>
    <head>
    <title>Angular QuickStart</title>
        <base href="/">
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="styles.css">
        <link rel="stylesheet" href="customer-form.css">
 
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> 
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css"> 
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
 
        <!-- Polyfill(s) for older browsers -->
        <script src="node_modules/core-js/client/shim.min.js"></script> 

        <script src="node_modules/zone.js/dist/zone.js"></script> 
        <script src="node_modules/systemjs/dist/system.src.js"></script> 

        <script src="systemjs.config.js"></script> 
        <script>            
            System.import('main.js').catch(function(err){ console.error(err); })
        </script> 
    </head>
<body>
    <my-app>Loading AppComponent content here ...</my-app>
</body>
</html>

Chúng ta có thể hiển thị thêm dòng chữ thông báo lỗi cho người dùng thay vì chỉ thay đổi màu sắc một cách đơn độc, như thế người dùng sẽ không biết rõ sai sót chỗ nào, chúng ta sửa lại file template như sau:

<div class="container">
    <h1>Customer</h1>
    <form>
        <div class="form-group">
            <label for="name">Name</label>
            <input type="text" 
                   class="form-control" 
                   id="name" 
                   [(ngModel)]="customer1.name" 
                   name="cus1_name" 
                   required 
                   #cus_class="ngModel">        
            <div [hidden]="cus_class.valid || cus_class.pristine" 
                 class="alert alert-danger">
                Name is required
            </div>
        </div>
    ...
    </form>
</div>

Chúng ta gán giá trị cho biến template #cus_classngModel, sau đó viết thêm một đoạn <div></div> nữa để hiển thị thông báo ‘Name is required’ nếu người dùng bỏ trống ô <input>.

Lý do ở đây chúng ta gán #cus_classngModel là vì mỗi chỉ thị trong Angular có một thuộc tính là exportAs, thuộc tính này chẳng qua là một đối tượng của chỉ thị đó nhưng chúng ta có thể sử dụng ở bất cứ đâu, và giá trị của thuộc tính này trong chỉ thị ngModel cũng là “ngModel" luôn.

Ở đoạn code trên chúng ta quy định thẻ <div></div> mới có được hiển thị hay không thông qua thuộc tính hidden, và chúng ta gắn giá trị của biến template vào thuộc tính hidden này.

Submit form

Form hiện tại của chúng ta chưa thực hiện chức năng submit, để có thể submit được thì chúng ta sử dụng chỉ thị ngSubmit, chúng ta sửa lại file customer-form.component.html như sau:

<div class="container">
    <h1>Customer</h1>
    <form (ngSubmit)="onSubmit()" #customerForm="ngForm"> 
        ...
    </form>
</div>

Chúng ta gán thuộc tính (ngSubmit) là phương thức onSubmit(), trong lớp CustomerFormComponent chúng ta khai báo thêm onSubmit().

import { Component } from '@angular/core';
import { Customer } from './customer';

@Component({
    moduleId: module.id,
    selector: 'customer', 
    templateUrl: './customer-form.component.html'
})
export class CustomerFormComponent {
 
    jobList = ['Software Developer', 'Tester', 'Project Manager', 'Business Analyst']; 
    customer1 = new Customer(1, 'Pho Coder', 24, this.jobList[0]);  
    onSubmit() { }
}

Ở đây chúng ta chưa xử lý gì nhiều.

Ngoài ra ở trên form chúng ta còn khai báo biến template là #customerForm với giá trị là ngForm, và đây là giá trị của thuộc tính exportAs trong chỉ thị ngForm, tuy nhiên ở đây khác với ngModel là chúng ta chưa khai báo dòng nào tương tự như [(ngForm)]="..." cả, lý do chúng ta vẫn dùng được exportAs của ngForm là vì mỗi khi chúng ta khai bào một thẻ <form> thì Angular sẽ tự động khai báo thuộc tính ngForm cho thẻ <form> luôn.

Chúng ta có thể dùng biến template này để điều khiển nhiều thứ trong form, chẳng hạn như vô hiệu hóa nút Submit khi form không hợp lệ, form không hợp lệ khi có một phần tử trong form không hợp lệ. Ví dụ:

<div class="container">
    <h1>Customer</h1>
    <form (ngSubmit)="onSubmit()" #customerForm="ngForm">
        ....
        <button type="submit" 
                class="btn btn-success" 
                [disabled]="!customerForm.form.valid">
            Submit
        </button>
     </form>
</div>

Chúng ta gán thuộc tính disabled là biểu thức !customerForm.form.valid, và nút "Submit" sẽ không thể click được khi có một phần tử trong form không hợp lệ, chẳng hạn như ô input bỏ trống.

Angular – Liên kết dữ liệu với Form

Angular cho phép chúng ta kết nối dữ liệu từ form tới các đối tượng trong class.

Tạo form

Để ví dụ thì đầu tiên chúng ta sẽ tạo một form trước đã, ở đây chúng ta sẽ tạo form điền những thông tin thông thường của mô hình khách hàng.

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

Sau đó trong thư mục src/app, chúng ta tạo một file có tên customer.ts như sau:

export class Customer{
    constructor(
        public id: number,
        public name: string,
        public age: number,
        public job: string
    ){}
}

Lớp Customer chỉ có một phương thức khởi tạo là constructor() và vài thuộc tính đơn giản.

Tiếp theo chúng ta tạo file customer-form.component.ts trong thư mục src/app như sau:

import { Component } from '@angular/core';
import { Customer } from './customer';

@Component({
    moduleId: module.id,
    selector: 'customer', 
    templateUrl: './customer-form.component.html'
})
export class CustomerFormComponent { 
    jobList = ['Software Developer', 'Tester', 'Project Manager', 'Business Analyst'];
    customer1 = new Customer(1, 'Pho Coder', 24, this.jobList[0]);
}

Ở đây chúng ta định nghĩa lớp component là CustomerFormComponent, lớp này có selector là customer, template lấy từ file khác là customer-form.component.html (chúng ta sẽ viết sau) chứ không code trực tiếp ở đây nữa, để có thể tham chiếu đến file template theo đường dẫn tương đối thì chúng ta phải khai báo tham số moduleId module.id.

Ngoài ra ở đây chúng ta còn import lớp Customer đã định nghĩa ở trên, tạo một mảng jobList lưu danh sách nghề nghiệp, rồi tạo một đối tượng Customercustomer1 với các giá trị đơn giản, trong đó thuộc tính job lấy từ mảng jobList.

Bây giờ chúng ta tạo file template customer-form.component.html cùng thư mục src/app như sau:

<div class="container">
    <h1>Customer</h1> 
    <form> 
        <div class="form-group">
            <label for="name">Name</label>
            <input type="text" class="form-control" id="name" required>
        </div>
 
        <div class="form-group">
            <label for="age">Age</label>
            <input type="text" class="form-control" id="name" required>
        </div>
 
        <div class="form-group">
            <label for="job">Job</label>
            <select class="form-control" id="job" required>
                <option *ngFor="let job of jobList" [value]="job">{{job}}</option>
            </select>
        </div>
 
        <button type="submit" class="btn btn-success">Submit</button>
     </form>
 </div>

Chúng ta dùng form để hiển thị các trường text, button, combobox…v.v

Ở trường dành cho jobList, chúng ta dùng chỉ thị *ngFor để lặp mảng jobList trong lớp CustomerFormComponent, rồi lấy từng phần tử mảng đó ra làm các item cho thẻ <select>. Cú pháp của *ngFor cũng khá đơn giản, bạn có thể tự suy ra cú pháp là:

let <tên_biến_lặp> of <tên_mảng>

Rồi lấy giá trị thông qua <tên_biến_lặp>.

Ngoài ở nếu bạn để ý thì ở đây chúng ta dùng các lớp CSS của thư viện Bootstrap để làm “màu mè” cho form.

Do đó bây giờ chúng ta chèn thêm đoạn code sử dụng Bootstrap vào file index.html ở trong thư mục src như sau:

<!DOCTYPE html>
<html>
    <head>
    <title>Angular QuickStart</title>
    <base href="/">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="styles.css">

    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">    
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css">    
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
    
    <!-- Polyfill(s) for older browsers -->
    <script src="node_modules/core-js/client/shim.min.js"></script>

    <script src="node_modules/zone.js/dist/zone.js"></script>
    <script src="node_modules/systemjs/dist/system.src.js"></script>

    <script src="systemjs.config.js"></script>
    <script>
        System.import('main.js').catch(function(err){ console.error(err); });
    </script>
</head>

<body>
    <my-app>Loading AppComponent content here ...</my-app>
</body>
</html>

Bây giờ chúng ta khai báo form này trong lớp AppComponent, chúng ta sửa file app.component.ts trong thư mục src/app như sau:

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

@Component({
    selector: 'my-app',
    template: `
        <customer></customer>
    `,
})
export class AppComponent { }

Cuối cùng chúng ta khai báo lớp CustomerFormComponent trong lớp AppModule, chúng ta sửa flle app.module.ts trong thư mục src/app như sau:

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

import { AppComponent } from './app.component';
import { CustomerFormComponent } from './customer-form.component';

@NgModule({
    imports: [ BrowserModule, FormsModule ],
    declarations: [ AppComponent, CustomerFormComponent ],
    bootstrap: [ AppComponent ]
})
export class AppModule { }

Ở đây chúng ta còn import thêm một module nữa là FormsModule, module này hỗ trợ một số tính năng để kết nối dữ liệu giữa template và class.

Bây giờ chúng ta có thể chạy project và trang web sẽ có giao diện form như thế này:

Liên kết dữ liệu với form

Chúng ta có thể liên kết các trường trong form với một thuộc tính nào đó trong lớp component tương ứng.

Để làm việc đó thì chúng ta sửa lại file template customer-form.component.html như sau:

<div class="container">
    <h1>Customer</h1> 
    <form> 
        <div class="form-group">
            <label for="name">Name</label>
            <input type="text" class="form-control" id="name" [(ngModel)]="customer1.name" name="cus1_name" required> 
        </div>
 
        <div class="form-group">
            <label for="age">Age</label>
            <input type="text" class="form-control" id="name" [(ngModel)]="customer1.age" name="cus1_age" required>
        </div>
 
        <div class="form-group">
            <label for="job">Job</label>
            <select class="form-control" id="job" [(ngModel)]="customer1.job" name="cus1_job" required>
                <option *ngFor="let job of jobList" [value]="job">{{job}}</option>
            </select>
        </div>
 
        <button type="submit" class="btn btn-success">Submit</button>
     </form>
 </div>

Chúng ta dùng cú pháp [(ngModel)]="..." trong các thẻ <input> hoặc <select> để kết nối dữ liệu của thẻ đó với một biến thuộc tính nào đó, ở đây chúng ta kết nối đến các thuộc tính name, agejob của đối tượng customer1.

Ngoài ra khi dùng ngModel thì thẻ đó phải khai báo thuộc tính name nữa, giá trị cho thuộc tính này đặt là gì cũng được, không nhất thiết phải là cus1_name, cus1_age

Lưu file này lại, Angular sẽ tự reload lại trang web và bạn sẽ thấy form trên trang web đã tự động điền các giá trị tương ứng với đối tượng customer1.

ngModel có tính chất 2 chiều, tức là nếu chúng ta chỉnh sửa gì trên form thì giá trị của đối tượng customer1 cũng sẽ thay đổi theo.Chúng ta có thể kiểm chứng điều này bằng cách in giá trị của đối tượng customer1 lên như sau:

...
<div class="form-group">
    <label for="name">Name</label>
    <input type="text" class="form-control" id="name" [(ngModel)]="customer1.name" name="cus1_name" required> 
    {{ customer1.name }}
</div>
...

Angular – Xử lý sự kiện

Khi người dùng tương tác với trang web thì sẽ phát sinh sự kiện, những sự kiện thường thấy nhất là click chuột, gõ phím, tải trang web…v.v  Đây là các sự kiện DOM (DOM Event), chúng ta có thể lắng nghe các sự kiện đó và tương tác lại với người dùng.

Bắt sự kiện

Trong Javascript ES5 thì chúng ta viết code bắt sự kiện như sau:

<button onclick="onClick()">

Còn trong TypeScript thì chúng ta viết đoạn code bắt sự kiện như sau:

<button (click)="onClick()">

Trong đó (click) là tên sự kiện, ở đây là sự kiện thả click chuột, onClick() là tên phương thức sẽ xử lý sự kiện đó, phương thức này chúng ta viết trong các lớp Component.

Ví dụ, bạn tạo một project mới từ quickstart, sau đó sửa trong file app.component.ts như sau:

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

@Component({
    selector: 'my-app',
    template: `
        <button (click)="onClick()">Click here</button>
        {{ message }}
    `
})
export class AppComponent {  
    message = "";

    onClick() {
        this.message = "Clicked";
    }
}

Rất đơn giản, chúng ta khai báo trong lớp AppComponent một thuộc tính là message, và khi click thì phương thức onClick() sẽ được gọi, trong đó chúng ta gán thuộc tính message giá trị là “Clicked”. Giá trị của thuộc tính này sẽ được hiển thị trên template.

Lấy dữ liệu từ sự kiện từ đối tượng $event

Có một số sự kiện sẽ mang theo cả dữ liệu, chẳng hạn như click chuột thì có thể lấy được tọa độ chuột, bấm phím thì lấy được kí tự phím vừa bấm… v.v Chúng ta có thể lấy được các dữ liệu đó.

Ví dụ chúng ta sửa lại lớp AppComponent như sau:

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

@Component({
    selector: 'my-app',
    template: `
        Type here: <input (keyup)="onKey($event)">        
        <p>You typed: {{values}}</p>
    `
})
export class AppComponent { 
    values = "";
    onKey(event: any) {
        this.values = event.target.value;
    }
}

Ở đây chúng ta khai báo template là một the <input>, với sự kiện keyup xử lý bằng phương thức onKey(). Dữ liệu sẽ được truyền vào tham số $event.

Khi người dùng nhấn và thả một phím, sự kiễn keyup sẽ xảy ra, Angular sẽ truyền đối tượng sự kiện DOM là biến $event vào làm tham số của phương thức onKey().

Tùy vào sự kiện là gì mà đối tượng $event sẽ có những thông tin khác nhau.

Tất cả các đối tượng sự kiện DOM đều có một thuộc tính là target, thuộc tính này tham chiếu đến thẻ đã phát sinh ra sự kiện đó, ở đây là <input>, và chúng ta có thể lấy thuộc tính value của thẻ này để lấy nội dung của thẻ.

Trong phương thức onKey() ở đây chúng ta gán giá trị cho biến values là giá trị trong event.target.value.

Trong đoạn code trên chúng ta khai báo kiểu dữ liệu của $eventany, tức là Angular sẽ tự động chuyển đổi sang kiểu dữ liệu DOM thích hợp, việc code như thế sẽ đơn giản và tránh các lỗi ngớ ngẩn.

Chúng ta có thể chỉ rõ ra kiểu dữ liệu như sau:

export class AppComponent { 
    values = "";
    onKey(event: KeyboardEvent) {
        this.values = (<HTMLInputElement>event.target).value;
    }
}

Ở đây chúng ta chỉ rõ kiểu dữ liệu của $eventKeyboardEvent, tức là sự kiện từ bàn phím. Bạn có thể xem danh sách các kiểu dữ liệu sự kiện DOM ở đây.

Lấy dữ liệu trong template

Thay vì khai báo phương thức cho sự kiện, chúng ta có thể tham chiếu đến dữ liệu của sự kiện đó ngay trong template như sau:

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

@Component({
    selector: 'my-app',
    template: `
        Type here: <input #box (keyup)="0">       
        <p>You typed: {{box.value}}</p>
    `
})
export class AppComponent { }

Để làm việc này thì chúng ta khai báo tên biến cho template, bằng cách ghi tên kèm với dấu #, ở đây chúng ta khai báo là #box. Ở phần bắt sự kiện chúng ta điền vào là 0, và chúng ta có thể dùng tên biến template đó để lấy giá trị của chính thẻ đó mà không cần phải dùng tới phương thức.

Lọc sự kiện

Đôi khi chúng ta chỉ cần muốn bắt một giá trị cụ thể, chẳng hạn như phím Enter, chúng ta có thể bắt giá trị đó thông qua thuộc tính $event.keyCode. Ví dụ:

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

@Component({
    selector: 'my-app',
    template: `
        Type here: <input #box (keyup.enter)="onEnter(box.value)">        
        <p>You typed: {{ value }}</p>
    `
})
export class AppComponent { 
 
    value = "";
    onEnter(value: string) {
        this.value = value;
    }
}

Ở đây sự kiện sẽ xảy ra khi người dùng gõ phím Enter, chúng ta truyền vào giá trị của #box.value rồi gán giá trị đó vào biến value để hiển thị.

Angular – Tạo Component

Trong phần này chúng ta sẽ tìm hiểu cách tạo một Component.

Đầu tiên bạn tạo một project như bình thường, có thể dùng project quickstart cũng được, và bạn đặt tên là gì cũng được, chạy project này như bình thường và được tương tự như hình dưới.

Tạo component

Chúng ta đã biết Component là một lớp và có phần khai báo @Component ở trước phần định nghĩa lớp đó, khi tạo một project Angular thì một module có thể có nhiều component dùng hiển thị các chức năng khác nhau.

Bây giờ chúng ta tạo một file có tên new.component.ts trong thư mục src/app với nội dung như sau:

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

@Component({
    selector: 'my-component',
    template: `
        <h2>Testing</h2>
    `
})
export class MyComponent{ 

}

Đây là một lớp component bình thường như bao lớp khác, ở đây chúng ta đặt tên selector là my-component. Lưu ý thường chúng ta đặt tên file có đuôi là *.component.ts cho dễ quản lý.

Để sử dụng component mới này thì chúng ta chỉ cần gọi selector của lớp đó trong template của lớp AppComponent là được, chúng ta sửa lại file app.component.ts như sau:

import { Component } from '@angular/core';
import { MyComponent} from './my.component';

@Component({
    selector: 'my-app',
    template: `        
        <h1>Testing npm</h1>
        <my-component>Loading</my-component>
    `
})
export class AppComponent { name = 'Angular'; }

Trong tham số template, chúng ta chỉ cần khai báo thêm selector của lớp MyComponent<my-component></my-component>, giữa 2 thẻ này có thể để trống.

Ở đầu file chúng ta phải import lớp MyComponent, nếu không Angular sẽ báo lỗi.

Cuối cùng chúng ta phải khai báo lớp MyComponent này trong AppModule nữa thì mới hiển thị được, 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 { MyComponent } from './my.component';

@NgModule({
    imports: [ BrowserModule ],
    declarations: [ AppComponent, MyComponent ],
    bootstrap: [ AppComponent ]
})
export class AppModule { }

Chúng ta chỉ cần thêm dòng import và khai báo trong mảng declarations là xong.

Angular – Hiển thị dữ liệu

Từ bài này chúng ta sẽ bắt đầu đi vào tìm hiểu cách sử dụng Angular 2.

Tạo project Angular

Chúng ta tạo một project bằng cách lấy bộ code mẫu từ trang Github của Angular về như trong bài Cài đặt đã hướng dẫn, đầu tiên bạn mở Command Prompt (cmd) lên, sau đó gõ lệnh sau:

C:\Project>git clone https://github.com/angular/quickstart.git displaying_data

Lệnh trên sẽ yêu cầu git tạo một thư mục với tên displaying_data, sau đó lấy bộ code từ project quickstart về từ trang Github của Angular.

Sau đó bạn gõ các lệnh sau:

C:\Project>cd displaying_data
...
C:\Project\displaying_data>npm install
...

Lệnh cd displaying_data sẽ di chuyển thư mục hiện tại trong Command Prompt vào thư mục displaying_data, sau đó lệnh npm install sẽ tải và cài các module cần thiết từ hệ thống npm về.

Vậy là chúng ta tạo project xong.

Lưu ý là trong thư mục này có rất nhiều file không cần thiết, chúng ta có thể xóa chúng đi bằng cách chạy 3 lệnh sau:

C:\Project\displaying_data>for /f %i in (non-essential-files.txt) do del %i /F /S /Q
...
C:\Project\displaying_data>rd .git /s /q
...
C:\Project\displaying_data>rd e2e /s /q
...

Trích xuất dữ liệu và hiển thị

Ở đây chúng ta sẽ khai báo biến trong lớp component và hiển thị lên màn hình.

Chúng ta sửa lại file app.component.ts trong thư mục src\app như sau:

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

@Component({
    selector: 'my-app',
    template: `
              <h1>{{ title }}</h1>
              <h2>My favorite idol is: {{myIdol}}<h2>
              `     
})
export class AppComponent { 
    title = "My idol list";
    myIdol = "Steve Jobs";
}

 

Trong file này chúng ta định nghĩa một lớp ComponentAppComponent, lớp này có 2 thuộc tính là titlemyIdol.

template: `        
          <h1>{{ title }}</h1>
          <h2>My favorite idol is: {{ myIdol }}
          <h2>
          `   

Tham số template trong phần khai báo @Component là đoạn code HTML sẽ được hiển thị lên màn hình, ở đây chúng ta hiển thị dữ liệu của 2 thuộc tính titlemyIdol.

Lưu ý: các biến sẽ được bọc trong cặp dấu ngoặc nhọn {{}}Chuỗi được bọc trong dấu huyền `` có thể ghi trên nhiều dòng.

Sau đó bạn có thể chạy lệnh npm start để chạy project rồi.

C:\Project\displaying-data>npm start

> angular-quickstart@1.0.0 prestart C:\Project\displaying-data
> npm run build

> angular-quickstart@1.0.0 build C:\Project\displaying-data>
> tsc -p src/

> angular-quickstart@1.0.0 start C:\Project\displaying-data>
> concurrently "npm run build:watch" "npm run serve"
...

Ở đây chúng ta không làm công việc gì như tạo đối tượng cả, Angular sẽ tạo giùm cho chúng ta. Tham số selector trong phần khai báo @Component ở trên định nghĩa tên thẻ sẽ được gọi trong file HTML, ở đây đã được đặt sẵn là my-app, trong file src\index.html có sẵn đoạn code tạo thẻ <my-app></my-app> để tham chiếu đến component này rồi.

Khi chúng ta chạy lệnh npm start, npm sẽ khởi động project từ file main.ts, đoạn code trong này sẽ tạo đối tượng từ lớp AppModule trong file app.module.ts, trong lớp AppModule này lại tham chiếu đến lớp AppComponent mà chúng ta vừa sửa ở trên.

Duyệt dữ liệu với *ngFor

Chúng ta dùng chỉ thị *ngFor để thực hiện việc duyệt các kiểu dữ liệu danh sách (như mảng). Ví dụ chúng ta sửa lại file app.component.ts như sau:

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

@Component({
    selector: 'my-app', 
    template: `
              <h1>{{ title }}</h1>
              <h2>My favorite idol is: {{ myIdol }}</h2>
              There are some more:
              <ul>                  
                  <li *ngFor="let idol of idols">
                      {{ idol }}
                  </li>
              </ul>
              `
})
export class AppComponent { 
    title = "My idol list";
    idols = [ 'Steve Jobs', 'Bill Gates', 'Mark Zuckerberg', 'Linus Torvalds' ];
    myIdol = this.idols[0];
}

Trong lớp AppComponent chúng ta có một mảng là idols, thuộc tính myIdol là giá trị của phần tử đầu tiên trong mảng.

Trong tham số template, chúng ta duyệt mảng idols và hiển thị trong thẻ <ul></ul>. Ở đây chúng ta dùng thuộc tính *ngFor="let idol of idols" để thực hiện việc duyệt mảng.

Biểu thức điều kiện

Chúng ta dùng chỉ thị *ngIf để thực hiện biểu thức điều kiến, ví dụ chúng ta sửa lại đoạn code app.component.ts như sau:

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

@Component({
    selector: 'my-app', 
    template: `              
              <h1>{{ title }}</h1>
              <h2>My favorite idol is: {{ myIdol }}</h2>
             <p *ngIf="idols.length > 1">There are some more:</p>
              <ul>                  
                  <li *ngFor="let idol of idols">
                      {{ idol }}
                  </li>
              </ul>
              `
})
export class AppComponent { 
    title = "My idol list";
    idols = [ 'Steve Jobs', 'Bill Gates', 'Mark Zuckerberg', 'Linus Torvalds' ];
    myIdol = this.idols[0];
}

Theo sau *ngIf là một câu lệnh so sánh hay bất cứ một biểu thức là mà có trả về giá trị true hoặc false, nếu true thì chuỗi trong cặp thẻ <p></p> mới được hiển thị.

Angular – Root Module

Các lớp module có trách nhiệm gắn kết các thành phần lại với nhau, mỗi ứng dụng Angular sẽ có ít nhất một module, trong đó có một module gốc (root) luôn được chạy trước tiên khi chúng ta khởi động ứng dụng, thông thường chúng ta sẽ đặt tên nó là AppModule.

Trong thư mục quickstart/src mà chúng ta đã cài đặt cũng có một file như vậy tên là app.module.ts:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';

@NgModule({
    imports: [ BrowserModule ],
    declarations: [ AppComponent ],
    bootstrap: [ AppComponent ]
})
export class AppModule { }

Trong đoạn code trên, ba dòng đầu tiên là các dòng import thư viện, kế đến là đoạn khai báo clas AppModule, với phần khai báo decorator @NgModule.

Từ khóa @NgModule báo cho Angular biết đây là một class module trong Angular. Các tham số trong phần khai báo @NgModule giúp Angular biết cách dịch và chạy ứng dụng như thế nào, trong đó:

  • imports: đây là một mảng dùng để khai báo tên các module khác sẽ được dùng bởi module này, ở đoạn code trên chúng ta chỉ khai báo 1 module là BrowserModule.
  • declarations: đây cũng là một mảng chứa tên các lớp thuộc về module này.
  • bootstrap: tên lớp View sẽ được dùng để hiển thị lên màn hình đầu tiên, chỉ có AppModule (hay root module) mới khai báo tham số này.

Chúng ta sẽ lần lượt tìm hiểu 3 tham số trên.

Imports

Module là tính năng cho phép Angular tách các thành phần có chung quan hệ với nhau thành các đơn vị độc lập. Rất nhiều tính năng trong Angular được tổ chức thành các module. Chẳng hạn như dịch vụ HTTP trong Angular được gói thành HttpModule. Tính năng router thì ở trong RouterModule. Chúng ta cũng có thể viết module riêng.

Để thêm một module vào thì chúng ta khai báo trong mảng imports.

Angular là một web framework, ứng dụng sẽ được chạy trên trình duyệt web, do đó sẽ cần dùng đến BrowserModule trong AppModule. BrowserModule nằm trong thư mục angular/platform-browser.

Chỉ có các lớp NgModule mới được khai báo trong mảng imports.

Declarations

Chúng ta khai báo cho Angular biết các lớp component thuộc về AppModule trong mảng declarations, bất cứ khi nào chúng ta tạo ra một component mới thì chúng ta khai báo trong mảng này.

Ngoài ra bất cứ một component nào được tạo ra cũng phải được khai báo trong một lớp NgModule nào đó, nếu không Angular sẽ báo lỗi.

Chúng ta sẽ tìm hiểu về 2 loại lớp là directive và pipe trong các bài sau, các lớp loại này thường được khai báo trong mảng declarations.

Chỉ có 3 loại lớp có thể khai báo trong mảng declarationscomponent, directivepipe.

Bootstrap

Ứng dụng được khởi động thông qua lớp AppModule. Khi khởi động Angular sẽ tạo ra các đối tượng được khai báo trong mảng bootstrap và lưu vào trình duyệt.

Mỗi đối tượng được tạo ra đó sẽ nằm riêng biệt với nhau, thông thường trong các lớp được khai báo sẽ tạo thêm các đối tượng con tùy vào coder.

Nhưng thông thường thì chúng ta chỉ khai báo một lớp trong mảng bootstrap thôi, và thường thì chúng ta đặt tên là AppComponent.

Khởi động ứng dụng

Có rất nhiều cách để khởi động ứng dụng.

Cách thông dụng nhất là chúng ta code đoạn code khởi động AppModule trong một file .ts riêng và đặt trong thư mục có tên src, ví dụ như sau:

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';

platformBrowserDynamic().bootstrapModule(AppModule);

Chúng ta khởi động bằng cách gọi phương thức platformBrowserDynamic().bootstrapModule(), truyền vào tham số là lớp AppModule.

Angular – Cấu trúc của một ứng dụng Angular

 

Trong thư mục quickstart mà chúng ta đã cài đặt Angular, có một thư mục project mẫu tên là app, trong thư mục này có 3 file .ts như sau:

app/
|---app.component.ts
|---app.module.ts
|---app.ts

Tất cả các project nào cũng phải có 3 file này, mỗi file thực hiện một mục đích khác nhau, tùy theo ứng dụng mà chúng sẽ phát triển như thế nào.

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

@Component({
    selector: 'my-app',
    template: `<h1>Hello {{name}}</h1>`
})
export class AppComponent { name = 'Angular'; }

File app.component.ts định nghĩa lớp AppComponent, đây là một component nhờ có phần khai báo metadata @Component, đây còn được gọi là root component, tất cả các component mà chúng ta sẽ viết đều được kế thừa từ component này.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';

@NgModule({
    imports: [ BrowserModule ],
    declarations: [ AppComponent ],
    bootstrap: [ AppComponent ]
})
export class AppModule { }

File app.module.ts định nghĩa lớp AppModule, đây là một module nhờ có phần khai báo metadata @NgModule, đây còn gọi là Root Module vì có khai báo thuộc tính bootstrap, mục đích của root module là cho Angular biết cách ứng dụng chạy như thế nào.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';

@NgModule({
    imports: [ BrowserModule ],
    declarations: [ AppComponent ],
    bootstrap: [ AppComponent ]
})
export class AppModule { }

File main.ts sẽ liên kết toàn bộ các file thành một ứng dụng hoàn chỉnh.

Angular – Kiến trúc

Một ứng dụng Angular được xây dựng từ 8 thành phần sau đây: Module, Component, Template, Metadata, Data Binding, Directive, Service, Dependency Injection.

Module

Mỗi ứng dụng Angular được gọi là một module và bản thân Angular có riêng một module dùng để quản lý các module khác có tên là Root Module hay NgModule. Root Module thường được đặt tên là AppModule, ngoài root ra thì tùy ứng dụng mà sẽ có thêm các module khác, chúng ta sẽ tìm hiểu về root module trong bài sau.

Chúng ta khai báo một module bằng cách dùng từ khóa @NgModule. Các từ khóa như @NgModule này là các hàm dùng để chỉnh sửa các lớp của Javascript. Bên trong từ khóa @NgModule chúng ta khai báo các tham số sau đây:

  • declarations: tên lớp view thuộc về module này
  • exports: danh sách tên các module hoặc component có thể sử dụng module này
  • imports: tên các module sẽ được dùng từ module này
  • providers: tên các service sẽ được dùng từ module này, chúng ta sẽ tìm hiểu về service sau
  • bootstrap: tên lớp view dành cho root module, chỉ có root module mới thiết lập tham số này

Đây là một đoạn code module trong file có tên app.module.ts đơn giản như sau:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
@NgModule({
    imports:      [ BrowserModule ],
    providers:    [ Logger ],
    declarations: [ AppComponent ],
    exports:      [ AppComponent ],
    bootstrap:    [ AppComponent ]
})
export class AppModule { }

Để gọi module trên thì chúng ta code như sau:

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';

platformBrowserDynamic().bootstrapModule(AppModule);

Component

Component có chức năng điều khiển việc hiển thị, tức là điều khiển View, vậy bạn có thể hình dung Component chính là một Controller trong mô hình MVC…v.v Ví dụ một đoạn code component:

export class HeroListComponent implements OnInit {
    heroes: Hero[];
    selectedHero: Hero;

    constructor(private service: HeroService) { }

    ngOnInit() {
        this.heroes = this.service.getHeroes();
    }

    selectHero(hero: Hero) { this.selectedHero = hero; }
}

Template

Template là một đoạn code HTML để component dựa vào đó mà hiển thị trên màn hình. Ví dụ:

<h2>Hero List</h2>
<p><i>Pick a hero from the list</i></p>
<ul>
    <li *ngFor="let hero of heroes" (click)="selectHero(hero)">
        {{hero.name}}
    </li>
</ul>
<hero-detail *ngIf="selectedHero" [hero]="selectedHero"></hero-detail>

Ngoài các thẻ HTML thông thường như <h2>, <p> thì còn có những thẻ và thuộc tính đặc biệt như *ngFor, {{hero.name}}, (click), [hero]<hero-detail>, đây là cú pháp template của Angular.

Metadata

Metadata (siêu dữ liệu) là những thông tin giúp Angular xử lý các lớp.

Trong đoạn code ví dụ về Component ở trên, đó chỉ là một lớp bình thường viết bằng TypeScript, không có sự xuất hiện của Angular trong này. Muốn Angular hiểu được đó là một lớp dành cho Angular thì chúng ta phải khai báo metadata. Ví dụ:

@Component({
    moduleId: module.id,
    selector: 'hero-list',
    templateUrl: 'hero-list.component.html',
    providers: [ HeroService ]
})
export class HeroListComponent implements OnInit {
    /* . . . */
}

Trong đó @Component là từ khóa bắt đầu định nghĩa metadata, phần định nghĩa lớp ngay sau phần metadata này là lớp component của metadata trên. Bên trong chúng ta khai báo một số thông tin cho Angular như moduleId, selector, templateUrl, providers. Chúng ta sẽ tìm hiểu về chúng sau.

Data Binding

Data Binding tức là lấy dữ liệu từ model/controller đổ vào view. Trong đoạn code ví dụ về template trên có những dòng data binding như sau:

<li>{{hero.name}}</li>
<hero-detail [hero]="selectedHero"></hero-detail>
<li (click)="selectHero(hero)"></li>

Data binding trong Angular là 2 chiều, tức là chúng ta có thể nhập dữ liệu từ view vào model/controller.

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

Directive

Directive (chỉ thị) là một lớp và có phần khai báo metadata là @Directive. 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.

Có 2 loại directive là structuralattribute.

Các structural directive có chức năng gán dữ liệu theo một quy tắc nào đó.

<li *ngFor="let cus of customer"></li>
<customer *ngIf="selectedCustomer"></customer>

Trong đoạn code trên thì *ngFor*ngIf là các structural directive.

Các attribute directive có chức năng hiển thị dữ liệu một cách trực tiếp.

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

Trong đoạn code trên thì ngModel là một attribute directive.

Service

Service là các lớp có khả năng thực hiện một số chức năng thường dùng, nói đơn giản thì chúng giống như thư viện vậy. Một số service phổ biến là: logging service, data service, message bus, tax calculator, application configuration.

Ví dụ lớp Logger cho phép chúng ta in các đoạn code báo lỗi, cảnh báo…v.v:

export class Logger {
    log(msg: any) { console.log(msg); }
    error(msg: any) { console.error(msg); }
    warn(msg: any) { console.warn(msg); }
}

Dependency injection

Dependency là các lớp/module/service được dùng thêm, Dependency injection là khả năng cho phép tạo các đối tượng lớp có đầy đủ các lớp/module/service được dùng thêm đó. Chẳng hạn như chúng ta có phương thức constructor() như sau:

constructor(private service: HeroService) { }

Tham số private service: HeroService có nghĩa là lớp này cần dùng một service có tên HeroService.

Angular có riêng một vùng bộ nhớ để lưu trữ các dependency đã được gọi, khi một module/component nào cần dùng service nào, Angular sẽ tìm trong vùng bộ nhớ đó xem có không, nếu không có thì Angular sẽ tạo một đối tượng của dependency đó và đưa vào bộ nhớ rồi trả về cho lớp đã gọi.

Khi chúng ta xây dựng root module thì chúng ta phải khai báo các dependency trong tham số providers, có như thế Angular mới có thể tìm được.

providers: [
    BackendService,
    HeroService,
    Logger
],

Hoặc khai báo ở phần @Component:

@Component({
    moduleId: module.id,
    selector: 'hero-list',
    templateUrl: 'hero-list.component.html',
    providers: [ HeroService ]
})

Angular – TypeScript – Phần 2

Trong phần này chúng ta sẽ tìm hiểu về hướng đối tượng trong TypeScript.

Class

Đề khai báo một lớp thì chúng ta dùng từ khóa class.

class Product {

}

Để tạo một đối tượng thuộc một lớp thì chúng ta dùng từ khóa new.

class Product {
}

var p: Product = new Product();

Thuộc tính

Thuộc tính là các dữ liệu “gắn” trong một đối tượng thuộc một lớp.

class Product {
    name: string;
    description: string;
    price: number;
}

Phương thức

Phương thức là các hàm của riêng một đối tượng.

class Product {
    name: string;
    description: string;
    price: number;

    getName(): string {
        return this.name;
    }
    
    setName(name) {
        this.name = name;
    }
    
    getDescription(): string {
        return this.description;
    }
    
    setDescription(description) {
        this.description = description;
    }

    getPrice(): number {
        return this.price;
    }
   
    setPrice(price) {
        this.price = price;
    }
}

Phương thức khởi tạo

Phương thức khởi tạo là phương thức sẽ được gọi khi chúng ta tạo một đối tượng bằng từ khóa new. Trong TypeScript thì phương thức khởi tạo luôn có tên là constructor(), có thể nhận vào tham số nhưng không thể có giá trị trả về.

Nếu bạn không khai báo phương thức khởi tạo thì TypeScript cũng tự định nghĩa một phương thức khởi tạo riêng (nhưng chúng ta không nhìn thấy).

class Product {
    constructor() {
        console.log("Product has been created");
    }
}
var p: Product = new Product();

Trong TypeScript thì chúng ta chỉ được phép khai báo một phương thức khởi tạo duy nhất cho một lớp, do đó thông thường chúng ta sẽ khai báo phương thức khởi tạo nhận vào tham số là toàn bộ thuộc tính của lớp đó.

class Product {
    name: string;
    description: string;
    price: number;

    constructor(name: string, description: string, price: number) {
        this.name = name;  
        this.description = description;
        this.price = price;
    }
}
var p: Product = new Product("Angular 2", "Web Development with Angular 2", 59.99);

Kế thừa

Kế thừa là một trong những tính năng quan trọng của lập trình hướng đối tượng, cho phép coder tái sử dụng code.

Để một lớp kế thừa từ lớp khác thì chúng ta chỉ cần dùng từ khóa extends.

class Product {
    name: string;
    description: string;
    price: number;

    getName(): string {
        return this.name;
    }
 
    setName(name) {
        this.name = name;
    }
 
    getDescription(): string {
        return this.description;
    }
 
    setDescription(description) {
        this.description = description;
    }

    getPrice(): number {
       return this.price;
    }
 
    setPrice(price) {
        this.price = price;
    }
 
    constructor(name: string, description: string, price: number) {
        this.name = name; 
        this.description = description;
        this.price = price;
    }
}

class Book extends Product {
 
}
var b: Book = new Book("Angular 2", "Web development with Angular 2", 59.99);
console.log(b.getName(), b.getDescription(), b.getPrice());

Lớp kế thừa có thể sử dụng các thuộc tính và phương thức của lớp cha, và có thể có thuộc tính và phương thức của riêng nó.

Hàm mũi tên (Fat Arrow Function)

Mình không biết gọi là gì, trong TypeScript bạn có thể dùng từ khóa => để tạo nhanh một hàm thay vì phải viết từ khóa function như trong ES5.

ES5:

var product = ["Apple", "Lemon", "Banana"];
product.forEach(function(p) {
    console.log(p);
});

TypeScript:

var product = ["Apple", "Lemon", "Banana"];
product.forEach( (p) => console.log(p) );

Thao tác trên string

Chúng ta có thể chèn giá trị của một biến vào một string bằng cách dùng kí tự '$'.

var title = "Angular 2";
var price = 59.99;
var str = `${title} costs ${price}`;
console.log(str);

Lưu ý là bạn phải dùng dấu huyền ` để bọc chuỗi chứ không dùng dấu nháy đơn hay nháy kép gì cả.

Khi bọc chuỗi trong cặp dấu huyền ` thì chúng ta có thể viết chuỗi trên nhiều dòng.

var html = `
<html>
    <head>
        <title>Pho Code</title>
    </head>
<body>
</body>
</html>
`;

Angular – TypeScript – Phần 1

TypeScript là một ngôn ngữ có “họ hàng” với Javascript. Đây là một bản mở rộng của ECMAScript6 (viết tắt là ES6), ES6 lại là một bản mở rộng của ECMAScript5 (viết tắt là ES5), ES5 chính là Javascript mà chúng ta thường dùng.

Ở đây chúng ta sẽ tìm hiểu nhanh qua TypeScript chứ không đi sâu.

Tuy nhiên hiện tại các trình duyệt web hiện cũng chưa hỗ trợ đầy đủ cho TypeScript nên chúng ta có thể dịch code TypeScript thành code ES5 bình thường.

Ví dụ

Trong bài trước chúng ta có cài Node.js và cài TypeScript từ hệ thống npm của Node, từ đây chúng ta có thể dùng lệnh tsc để chạy trình biên dịch TypeScript. Bạn có thể dùng lệnh tsc -v để xem phiên bản TypeScript của Node.

C:\>tsc -v
Version 2.1.4

Ở đây mình dùng phiên bản 2.1.4

Các file code TypeScript sẽ có phần mở rộng là .ts, để dịch code TypeScript thành ES5 thì chúng ta dùng lệnh tsc <tên_file>.

Ví dụ dưới đây là một đoạn code TypeScript trong file example.ts:

class Person {
    first_name: string;
    last_name: string;
    age: number;
 
    constructor(first_name: string, last_name: string, age: number){
        this.first_name = first_name;
        this.last_name = last_name;
        this.age = age;
    }
 
    greet() {
        console.log("Hello", this.first_name);
    }
}

var p: Person = new Person("Pho", "Code", 23);
p.greet();

Dùng lệnh tsc example.ts chúng ta sẽ được một file example.js như sau:

var Person = (function () {
    function Person(first_name, last_name, age) {
        this.first_name = first_name;
        this.last_name = last_name;
        this.age = age;
    }
    Person.prototype.greet = function () {
        console.log("Hello", this.first_name);
    };
    return Person;
}());
var p = new Person("Pho", "Code", 23);
p.greet();

Chạy bằng node:

C:\>node example.js
Hello Pho

Các kiểu dữ liệu cơ bản

Các kiểu dữ liệu thường dùng là string, number, boolean, array, enum, any, void.

String

Đây là kiểu chuỗi kí tự.

var name: string = "Pho Code";

Number

Đây là kiểu số, trong TypeScript thì tất cả đều là số chấm động (số thực).

var age: number = 36;

Boolean

Kiểu luận lý, hay chúng ta hay gọi là kiểu true/false.

var married: boolean = true;

Array

Kiểu mảng, khi khai báo các phần tử trong mảng thì chúng ta phải chỉ ra cả kiểu dữ liệu nữa. Để tạo array thì chúng ta có thể dùng cú pháp Array<kiểu> hoặc <kiểu>[].

var jobs: Array<string> = ['Coder', 'Tester', 'Manager'];
var primes: int[] = [2, 3, 5, 7];

Enum

Đây là kiểu danh sách, phần tử trong danh sách được đánh số theo thứ tự từ 0.

enum Role {Employee, Manager, Administrator};
var role: Role = Role.Employee;

Any

Biến kiểu Any có thể nhận bất kiểu giá trị kiểu nào.

var something: any = "this is a string";
something = 1;
something = [1, 2, 3];

Void

Void chỉ đơn giản là “không có kiểu gì cả”, thương dùng cho các hàm không cần trả về một giá trị gì.

function setName(name: string): void {
    this.name = name;
}