+3

Angular 2 căn bản - Phần 4: Routing

Ở phần trước, chúng ta đã nói về data binding, và trong phần cuối series này mình sẽ trình bày về routing, hay nói cách khác là việc điều hướng giữa các component. Hiện tại app của chúng ta đã có 2 component, đó là PeopleListComponent, dùng để hiển thị một list các user và PersonDetailComponent để hiển thị chi tiết cho user đó. Thế nhưng, lúc này nếu đưa trang web cho một người bất kỳ sử dụng, họ sẽ thắc mắc: "Cái gì đây, tại sao lại có quá nhiều cái hiển thị vậy??? Tại sao đường dẫn không hề thay đổi???" Tuy nhiên, bạn hãy bình tĩnh an ủi họ: "Đừng lo, tôi có cách để trang web hiển thị như bạn muốn 😃" Bắt đầu thực hiện thôi.

Angular 2 routing

Angular 2 cho phép bạn chia ứng dụng thành các view nhỏ và bạn có thể điều hướng giữa các view đó. Routing cho phép người dùng gọi tới các component khác nhau dựa trên URL mà họ gõ trên trình duyệt. Nếu mở file package.json bạn sẽ thấy nó được khai báo trong dependencies:

  "devDependencies": {
    "@angular/cli": "1.1.3",
    "@angular/compiler-cli": "^4.0.0",
    "@angular/language-service": "^4.0.0",
    "@types/jasmine": "2.5.45",
    "@types/node": "~6.0.60",
    "codelyzer": "~3.0.1",
    "jasmine-core": "~2.6.2",
    "jasmine-spec-reporter": "~4.1.0",
    "karma": "~1.7.0",
    "karma-chrome-launcher": "~2.1.1",
    
    "karma-cli": "~1.0.1",
    "karma-coverage-istanbul-reporter": "^1.2.1",
    "karma-jasmine": "~1.1.0",
    "karma-jasmine-html-reporter": "^0.2.2",
    "protractor": "~5.1.2",
    "ts-node": "~3.0.4",
    "tslint": "~5.3.2",
    "typescript": "~2.3.3"
  }

Thiết lập PeopleList route

Chúng ta sẽ thiết lập route trong Angular 2 thông qua một file config bao gồm:

  1. Một mảng Routes chứa các route
  2. Một export cung cấp config route cho các component. Chúng ta sẽ bắt đầu bằng cách tạo một file app.routes.ts trong thư mục app và import interface Routes từ module @angular/router
import { Routes } from '@angular/router';

Sau đó ta định nghĩa route mặc định cho ứng dụng, ở đây là danh sách các người dùng, Để thực hiện điều này, ta import PeopleListComponent:

import { PeopleListComponent } from './people-list/people-list.component';

Và tạo mảng Routes

// Route config let's you map routes to components
const routes: Routes = [
  // map '/people' to the people list component
  {
    path: 'people',
    component: PeopleListComponent,
  },
  // map '/' to '/people' as our default route
  {
    path: '',
    redirectTo: '/people',
    pathMatch: 'full'
  },
];

Như các bạn có thể thấy ở trên, mỗi routes sẽ map một path ('person') với một component ('PeopleListComponent'). Chúng ta nói cho Angular biết rằng đây là route mặc định bằng cách tạo một config phụ để map một path rỗng tới path person

import { Routes, RouterModule }  from '@angular/router';

và export nó ra

export const appRouterModule = RouterModule.forRoot(routes);

File app.routes.ts của chúng ta lúc này như sau:

import { Routes, RouterModule } from '@angular/router';
import { PeopleListComponent } from "./people-list/people-list.component";

const routes: Routes = [
  {
    path: 'people',
    component: PeopleListComponent,
  },
  {
    path: '',
    redirectTo: '/people',
    pathMatch: 'full'
  },
];

export const appRouterModule = RouterModule.forRoot(routes);

Bây giờ chúng ta có thể update ứng dụng để sử dụng route vừa được định nghĩa ở trên trong file config. Ta sẽ import vào trong file module app.module.ts:

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

import { AppComponent } from './app.component';
import { PeopleListComponent } from './people-list/people-list.component';
import { PeopleService } from './people.service';
import { PersonDetailsComponent } from './person-details/person-details.component';

import { appRouterModule } from "./app.routes";

@NgModule({
  declarations: [
    AppComponent,
    PeopleListComponent,
    PersonDetailsComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    appRouterModule
  ],
  providers: [PeopleService],
  bootstrap: [AppComponent]
})
export class AppModule { }

Điều này không chỉ cho phép app của chúng ta truy cập vào route mà chúng ta đã định nghĩa mà còn có thể truy cập đến các service routing khác được cung cấp bởi @angular/router cũng như Router vàActiveRoutemà chúng ta đề cập đến ở phần sau Ta sử dụng directiverouter-oulet, một Routing directive trong Angular 2 để hiển thị route hoạt động. Update fileapp.component.htmlvớirouter-oulet` directive như sau:

<h1>{{title}}</h1>
<router-outlet></router-outlet>

và bây giờ mở ứng dụng của chúng ta lên xem thành quả thôi (đừng quên ng s -o). Mở trình duyệt lên và gõ:

localhost:4200

Perfect, mọi thứ đều hoạt động bình thường.

Thiết lập PersonDetail route

Tiếp theo chúng ta sẽ config route cho component PersonDetail, bắt đầu bằng cách import vào trong file app.routes.ts và khai báo một route mới:

import { Routes, RouterModule } from '@angular/router';
import { PeopleListComponent } from "./people-list/people-list.component";
import { PersonDetailsComponent } from "./person-details/person-details.component";

const routes: Routes = [
  {
    path: 'people',
    component: PeopleListComponent,
  },
  {
    path: 'person/:id',
    component: PersonDetailsComponent
  },
  {
    path: '',
    redirectTo: '/people',
    pathMatch: 'full'
  },
];

export const appRouterModule = RouterModule.forRoot(routes);

Chúng ta sẽ phân biệt mỗi người bằng cách dựa vào id, điều đó có nghĩa là ta phải update lại interface Person

export interface Person {
  id :number;
  name: string;
  weight :number;
  height :number;
}

các bạn đừng quên update cả PeopleService nhé:

import { Injectable } from '@angular/core';
import { Person } from './person';

@Injectable()
export class PeopleService {

  constructor() { }

  getAll() : Person[] {
    return [
      {id: 1, name: 'Kobayashi Taihei', height: 177, weight: 65},
      {id: 2, name: 'Vu Xuan Dung', height: 165, weight: 62},
      {id: 3, name: 'Tran Ngoc Thang', height: 173, weight: 68},
    ];
  }

}

Tạo route link

Chúng ta đã định nghĩa xong route ở phần trên và muốn người dùng có thể truy cập vào nó khi họ click vào mỗi person tương ứng. Angular 2 routing cung cấp directive [routerLink] giúp ta thực hiện điều đó một cách vô cùng đơn giản:

import { Component, OnInit } from '@angular/core';
import { Person } from '../person';
import { PeopleService } from "../people.service";

@Component({
  selector: 'app-people-list',
  template: `
  <ul>
    <li *ngFor="let person of people">
      <a [routerLink]="['/person', person.id]">
        {{person.name}}
      </a>
    </li>
  </ul>
  `,
  styleUrls: ['./people-list.component.scss']
})
export class PeopleListComponent implements OnInit {
  selectedPerson: Person;
  people: Person[];

  constructor(private peopleService: PeopleService) { }

  ngOnInit() {
    this.people = this.peopleService.getAll();
  }

  selectPerson(person:Person){
    this.selectedPerson = person;
  }
}

Tuy nhiên app của chúng ta vẫn chưa chạy được, đó là do component PersonDetailsComponent không biết làm sao để lấy được params id từ route. Vậy giờ ta phải làm sao???

Lấy parameters từ route

Angular 2 cung cấp cho ta service ActivatedRoute để thực hiện công việc này. Ta có thể import từ module @angular/routervà '"tiêm" vào trong PersonDetailsComponent thông qua constructor

import { ActivatedRoute } from '@angular/router';
export class PersonDetailsComponent{
    constructor(private peopleService: PeopleService,
               private route: ActivatedRoute){
    }   
}

và giờ ta sẽ sử dụng nó để lấy params id từ url và lấy person để hiển thị từ PeopleService. Ta thực hiện trong ngOnInit:

export class PersonDetailsComponent implements OnInit {
    person: Person;

    ngOnInit(){
        this.route.params.subscribe(params => {
          let id = Number.parseInt(params['id']);
          this.person = this.peopleService.get(id);
        });
    }
}

Chú ý rằng route.params trả về một observable, là pattern để xử lý bất đồng bộ mà ta sẽ tìm hiểu kỹ hơn ở bài viết sau. Do đó, phương thức subcribe xử lý hoàn tất một đoạn code khi quá trình bất đồng bộ xảy ra, ở đây là load route và lấy params. Để tránh xảy ra tình trạng memory leak, ta có thể unsubcribe observable route.params khi Angular hủy component PersonDetails. Ta thực hiện việc này ở hàm onDestroy:

import { Component, OnInit, OnDestroy } from '@angular/core';
ngOnDestroy(){
  this.sub.unsubscribe();
}

File PersonDetailsComponent của chúng ta sẽ như sau:

import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute } from "@angular/router";

import { PeopleService } from "../people.service";
import { Person } from "../person";

@Component({
  selector: 'app-person-details',
  template: `
  <section *ngIf="person">
    <h2>You selected: {{person.name}}</h2>
    <h3>Description</h3>
    <p>
       {{person.name}} weights {{person.weight}} and is {{person.height}} tall.
    </p>
  </section>
  `,
  styles: []
})
export class PersonDetailsComponent implements OnInit, OnDestroy {
  person: Person;
  sub: any;

  constructor(private route:ActivatedRoute,
              private peopleService:PeopleService) { }

  ngOnInit() {
    this.sub = this.route.params.subscribe(params => {
      let id = Number.parseInt(params['id']);
      this.person = this.peopleService.get(id);
    });
  }

  ngOnDestroy(): void {
    this.sub.unsubscribe();
  }
}

Đừng quên PeopleService nhé:

import { Injectable } from '@angular/core';
import { Person } from './person';

const PEOPLE : Person[] = [
      {id: 1, name: 'Kobayashi Taihei', height: 177, weight: 65},
      {id: 2, name: 'Vu Xuan Dung', height: 165, weight: 62},
      {id: 3, name: 'Tran Ngoc Thang', height: 173, weight: 68},
    ];

@Injectable()
export class PeopleService {

  getAll() : Person[] {
    return PEOPLE;
  }

  get(id: number) : Person {
    return PEOPLE.find(p => p.id === id);
  }
}

Mở browser lên tận hưởng thành quả thôi:

Quay trở lại PeopleList

Đương nhiên, với bất cứ app nào cũng vậy, nên có một button back lại trang trước. Angular cung cấp một service Router để hỗ trợ chúng ta làm việc này. Ta sẽ thêm một button và gán nó cho phương thức goToPeopleList mà ta khai báo ở phần sau trong PersonDetailsComponent

<button (click)="gotoPeoplesList()">Back to peoples list</button>

Ta inject service Router thông qua constructor của PersonDetailsComponent, và nhớ là bạn đã import từ @angular/router

import { ActivatedRoute, Router} from '@angular/router';
export class PersonDetailsComponent implements OnInit, OnDestroy {
    // other codes...
    constructor(private peopleService: PeopleService,
                private route: ActivatedRoute,
                private router: Router){
    }
}

Việc cuối cùng là định nghĩa method goToPeopleList thôi:

export class PersonDetailsComponent implements OnInit, OnDestroy {
    // other codes...

    gotoPeoplesList(){
        let link = ['/people'];
        this.router.navigate(link);
    }
}

File PersonDetailsComponent hoàn chỉnh như sau:

import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute, Router } from "@angular/router";

import { PeopleService } from "../people.service";
import { Person } from "../person";

@Component({
  selector: 'app-person-details',
  template: `
  <section *ngIf="person">
    <h2>You selected:  {{person.name}}</h2>
    <h3>Description</h3>
    <p>
       {{person.name}} weights {{person.weight}} and is {{person.height}} tall.
    </p>
  </section>

  <button (click)="gotoPeoplesList()">Back to peoples list</button>
  `,
  styles: []
})
export class PersonDetailsComponent implements OnInit, OnDestroy {
  person: Person;
  sub:any;

  constructor(private route: ActivatedRoute,
              private peopleService: PeopleService,
              private router: Router) { }

  ngOnInit() {
    this.sub = this.route.params.subscribe(params => {
      let id = Number.parseInt(params['id']);
      this.person = this.peopleService.get(id);
    });
  }

  ngOnDestroy() {
    this.sub.unsubscribe();
  }

  gotoPeoplesList(){
    let link = ['/people'];
    this.router.navigate(link);
  }
}

Bên cạnh đó, ta cũng có thể sử dụng API window.history:

gotoPeoplesList(){
    window.history.back();
}

Cảm ơn các bạn đã theo dõi. Trong bài viết sau mình sẽ nói về Form và Validations Tài liệu tham khảo: https://www.barbarianmeetscoding.com/blog/2016/03/28/getting-started-with-angular-2-step-by-step-4-routing/


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.