Bài viết được dịch từ: barbarianmeetscoding.com

Đây là bài thứ 2 trong loạt bài viết: "Bắt đầu với Angular 2 từng bước một". Bạn có thể xem lại bài viết khác ở đây:

Ở bài đầu tiên, các bạn đã học cách bắt đầu với một ứng dụng Angular 2 và TypeScript và cũng đã viết component đầu tiên của mình. Trong bài viết này bạn sẽ học về services và dependency injection.

Trong bài viết trước chúng ta có một PeopleListComponent nơi khởi tạo một mảng các nhân vật chính của Star Wars. Nhưng component không nên biết hoặc quan tâm tới nơi lấy dữ liệu có thể là local storage, database, web service, ...

Đó là lý do tại sao chúng ta tách phần logic truy cập dữ liệu vào một service, sau đó tiêm vào component thông qua hàm khởi tạo của nó. Chia ứng dụng thành các phần nhỏ hơn với một trách nhiệm duy nhất sẽ giúp cho ứng dụng dễ bảo trì, tái sử dụng và kiểm thử hơn.

Code mẫu

Bạn có thể tải toàn bộ code mẫu ở GitHub repo.

Xem lại PeopleListComponent 

PeopleListComponent hiện nay trông như thế này:

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

@Component({
  selector: 'people-list',
  template: `
  <!-- this is the new syntax for ng-repeat -->
  <ul>
    <li *ngFor="let person of people">
        {{person.name}}
    </li>
  </ul>
  `
})
export class PeopleListComponent{
  people: Person[] = [
    {name: 'Luke Skywalker', height: 177, weight: 70},
    {name: 'Darth Vader', height: 200, weight: 100},
    {name: 'Han Solo', height: 185, weight: 85},
  ];

}

Nhiệm vụ của chúng ta là tách logic truy cập dữ liệu thành một service riêng là PeopleService, cái sẽ chịu trách nhiệm nhận một mảng các nhân vật từ bất cứ đâu và cung cấp cho phần còn lại của ứng dụng.

Tham khảo các khóa học lập trình online, onlab, và thực tập lập trình tại TechMaster

Angular 2 service là gì?

Mọi ứng dụng đều bao gồm rất nhiều hệ thống con với những nhiệm vụ khác nhau như: logging, truy cập dữ liệu, caching, ... Phụ thuộc vào kiểu ứng dụng bạn đang xây dựng hoặc framework bạn đang sử dụng, mà có những cách khác nhau để biểu diễn các hệ thống con này.

Angular 2 sử dụng khái niệm services, một service của Angular 2 là một class đóng gói một vài chức năng và cung cấp nó cho phần còn lại của ứng dụng.

Nếu bạn đã làm việc với Anguar 1, thì bạn sẽ thích sự đơn giản của Angular 2: không còn service/factory/constant/provicer. Trong Angular 2 chỉ có duy nhất services, và chúng chỉ là các class ES6.

Tạo PeopleService

Hãy tạo PeopleService trong một file mới people.service.ts. Nó sẽ như thế này:

import { Person } from './person';

export class PeopleService{
  getAll() : Person[] {
    return [
      {name: 'Luke Skywalker', height: 177, weight: 70},
      {name: 'Darth Vader', height: 200, weight: 100},
      {name: 'Han Solo', height: 185, weight: 85},
    ];
  }

}

Như bạn thấy, nó chỉ một class ES6 với một phương thức getAll trả về một danh sách các nhân vật.

Tiêm PeopleService vào PeopleListComponent

Bây giờ chúng ta có một service để lấy dữ liệu về các nhân vật Star Wars, chúng ta có thể sử dụng nó trong PeopleListComponent. Chúng ta sẽ tận dụng lợi thế của dependency injection và tiêm service thông qua hàm khởi tạo của component.

Dependency injection là gì?

Các ứng dụng trong thế giới thật làm rất nhiều thứ.

Để quản lý các hệ thống phức tạp này, chúng ta chia chúng thành các hệ thống con nhỏ hơn chỉ làm một thứ  hoặc một tập hợp nhỏ của vài thứ.

Và kết quả là chúng ta có một hệ thống đơn giản hơn, được tạo thành từ các component nhỏ phụ thuộc lẫn nhau. Chúng ta có thể sử dụng toán tử new để khởi tạo chúng, nhưng chúng ta muốn các dependency biết càng ít về nhau càng tốt. Chúng ta muốn các hệ thống con phụ thuộc vào abstraction chứ không phải là một concrete implementations.

Tại sao? Bởi vì đó là cách tạo ra các hệ thống linh hoạt, dễ mở rộng và mạnh mẽ hơn. Nơi các thay đổi trong concrete implementations không ảnh hưởng tới các phần khác của hệ thống.

Bạn có thể tìm hiểu thêm về SOLID, quy tắc dependency inversion và dependency injection ở đây.

Sử dụng Angular 2 DI để tiêm PeopleService vào PeopleListComponent

Chúng ta bắt đầu bằng cách import PeopleService module từ people.service:

import { PeopleService } from './people.service';

Chúng ta tiêm nó tới PeopleListComponent thông qua hàm khởi tạo:

export class PeopleListComponent{
  people: Person[] = [];

  // this shorthand syntax automatically creates and
  // initializes a new private member in the class
  constructor(private _peopleService : PeopleService){
    this.people = _peopleService.getAll();
  }
}

Chúng ta sử dụng cú pháp rút gọn của TypeScript để khai báo trực tiếp một thành viên private của PeopleListComponent, private _peopleService trong hàm khởi tạo tương đương với:

export class PeopleListComponent {
  private _peopleService: PeopleService;
  people: Person[] = [];

  constructor(_peopleService : PeopleService){
    this._peopleService = _peopleService;
    this.people = this._peopleService.getAll();
  }
}

Chúng ta đã xong, toàn bộ PeopleListComponent bây giờ sẽ như thế này:

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

@Component({
  selector: 'people-list',
  template: `
  <!-- this is the new syntax for ng-repeat -->
  <ul>
    <li *ngFor="let person of people">
        {{person.name}}
    </li>
  </ul>
  `
})
export class PeopleListComponent {
  people: Person[] = [];
  constructor(private _peopleService : PeopleService){
    this.people = _peopleService.getAll();
  }
}

Nếu bạn gõ lệnh npm start bạn sẽ thấy ứng dụng không làm việc.

Nếu sử dụng dev tool của trình duyệt bạn sẽ thấy lỗi:

No provider for PeopleService! (PeopleListComponent -> PeopleService)

Angular 2 không biết cách tiêm PeopleService vào PeopleListComponent. Hãy xem cách làm điều đó trong phần tiếp theo.

Đăng ký service của bạn với Angular 2

Để đăng ký một service với Angular 2, bạn sử dụng thuộc tính providers trong decorator Component. Ví dụ, để PeopleService có sẵn trong toàn bộ ứng dụng. Chúng ta có thể đăng ký trong component AppComponent. Chúng ta bắt đầu bằng cách import service từ module của nó:

import { PeopleService } from './people.service';

Và thêm thuộc tính providers trong decorator Component:

import { Component } from '@angular/core';
import { PeopleService } from './people.service';

@Component({
  selector: 'my-app',
  template: `
  <h1>  {{title}} </h1>
  <people-list></people-list>
  `,
  // HERE! This registers the PeopleService 
  // now Angular 2 knows to inject it when required
  providers: [PeopleService]
})
export class AppComponent {
  title:string = 'Star Wars Peoplez!';
}

Bạn cũng có thể đăng ký các service ở mức module

Bạn cũng có thể, đăng ký các service để sử dụng trong toàn bộ module. Thông qua thuộc tính providers của decorator NgModule:

import { PeopleService } from './people.service';

@NgModule({
  imports: [ BrowserModule, routing ],
  declarations: [ AppComponent, PeopleListComponent, PersonDetailsComponent],
  bootstrap: [ AppComponent ],
  providers: [ PeopleService] // You can put your services here!
})
export class AppModule { }

Khi nào thì, bạn làm như thế này? Câu trả lời là bất cứ khi nào bạn muốn sử dụng cùng một instance của một service cho toàn bộ ứng dụng của mình.

Tận dụng vòng đời của Angular 2 component

Một cải tiến của Angular 2 so với Angular 1 là vòng đời (lifecycle) của component. Ví dụ, chúng ta có thể làm cho hàm khởi tạo của PeopleListComponent gọn gàng hơn bằng cách khởi tạo dữ liệu trong ngOnInit của component:

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

@Component({
  selector: 'people-list',
  template: `
  <!-- this is the new syntax for ng-repeat -->
  <ul>
    <li *ngFor="let person of people">
        {{person.name}}
    </li>
  </ul>
  `
})
export class PeopleListComponent implements OnInit{
  people: Person[] = [];
  constructor(private _peopleService : PeopleService){ }

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

Điều này rất hữu ích khi viết unit test vì component sẽ không làm bất kỳ điều gì khi chúng ta khởi tạo nó.

Cho phép dependency injection trong service của bạn

Chúng ta đã biết cách tiêm các service vào một component, nhưng làm thế nào để tiêm các service vào một service khác? Chúng ta làm tương tự với component bằng cách tiêm dependency trong hàm khởi tạo và đăng ký trong thuộc tính providers của component gốc. Ngoài ra, bạn cần decorate service với decorator Injectable giống như ví dụ dưới đây:

import { Injectable } from '@angular/core';
import { Person } from './person';
import { SomeOtherService } from './some-other.service';

@Injectable()
export class PeopleService{
  constructor(private _someOtherService: SomeOtherService){}

  getAll() : Person[] {
    return [
      {name: 'Luke Skywalker', height: 177, weight: 70},
      {name: 'Darth Vader', height: 200, weight: 100},
      {name: 'Han Solo', height: 185, weight: 85},
    ];
  }

}

Tại sao không sử dụng decorator Injectable trong component? Bởi vì decorator Component cho phép dependency injection trực tiếp.

Các bạn có thể đọc thêm về Angular 2 dependency injection ở đây:

Kết luận

Bây giờ, chúng ta đã biết về component, service, và dependency injection. Trong bài tiếp theo, chúng ta sẽ học nhiều hơn về Angular 2 data binding bằng cách tạo một component thứ 2 giúp chúng ta hiển thị thông tin chi tiết về các nhân vật trong danh sách.