Daily Archives: 28/03/2017

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 đó.