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à id
và name
.
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
.