Angular – Service và Dependency Injection


Được đăng vào ngày 25/03/2017 | 0 bình luận
Angular – Service và Dependency Injection
5 (100%) 6 votes

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à idname.

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.

Được đăng vào ngày 25/03/2017