Hướng dẫn lập trình Angular từ đầu từng bước (part 3)

Ở hướng dẫn trước, bạn sẽ tìm hiểu kiến trúc ứng dụng Angular, cấu trúc một dự án Angular, định tuyến và điều hướng ứng dụng Angular. Nếu bạn bỏ lỡ hướng dẫn này thì có thể xem ở bài viết sau:

Hướng dẫn lập trình Angular từ đầu từng bước (part 2)
Trong hướng dẫn này, bạn sẽ tìm hiểu kiến trúc ứng dụng Angular, cấu trúc một dự án Angular, định tuyến và điều hướng ứng dụng Angular.

Điều hướng ứng dụng bằng Angular Router

Angular có một module dành riêng cho điều hướng và định tuyến là RouterModule. Với module này, bạn có thể tạo các tuyến, cho phép bạn di chuyển từ phần này của ứng dụng sang phần khác hoặc từ view này sang view khác.

Để các tuyến hoạt động, bạn cần một liên kết hoặc phần tử trong giao diện người dùng để ánh xạ các hành động (thường là nhấp vào các phần tử) đến các tuyến (đường dẫn URL).

Chúng tôi sử dụng directive routerLink cho mục đích này. Ví dụ: khi người dùng nhấp vào tên danh mục trong giao diện người dùng, thông qua directive routerLink Angular biết rằng nó cần điều hướng đến url sau: http://localhost:4200/questions/about/category-name

<a class="list-title" [routerLink]="['/questions/about', category.slug]">{{category.title}}</a>

Tiếp theo, bạn sẽ cần ánh xạ các đường dẫn URL đến các component. Trong cùng thư mục với module gốc, hãy tạo tệp cấu hình có tên app.routes.ts (nếu bạn chưa có) với mã sau.

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

export const routes: Routes = [
    {
        path: '',
        component: CategoriesComponent,
        resolve: {
            data: CategoriesResolver
        }
    },
    {
        path: 'questions/about/:categorySlug',
        component: CategoryQuestionsComponent,
        resolve: {
            data: CategoryQuestionsResolver
        }
    },
    {
        path: 'question/:questionSlug',
        component: QuestionAnswersComponent,
        resolve: {
            data: QuestionAnswersResolver
        }
    }
];

Đối với mỗi tuyến, chúng tôi cung cấp một đường dẫn (còn được gọi là URL) và thành phần sẽ được hiển thị tại đường dẫn đó. Chuỗi trống cho đường dẫn CategoriesComponent có nghĩa là CategoriesComponent sẽ được hiển thị khi ở đường dẫn gốc.

Lưu ý rằng đối với mỗi tuyến chúng tôi cũng có cách giải quyết. Sử dụng giải pháp trong các tuyến điều hướng của chúng tôi cho phép chúng tôi tìm nạp trước dữ liệu của component trước khi tuyến được kích hoạt. Sử dụng các resolver là một thực hành rất tốt để đảm bảo rằng tất cả dữ liệu cần thiết đã sẵn sàng cho các component của chúng ta sử dụng và tránh hiển thị một component trống trong khi chờ dữ liệu.

Ví dụ, chúng ta sử dụng CategoriesResolver để tìm nạp danh sách các danh mục. Khi các danh mục đã sẵn sàng, chúng ta kích hoạt tuyến. Xin lưu ý rằng nếu quá trình giải quyết Observable không hoàn tất, quá trình điều hướng sẽ không tiếp tục.

Cuối cùng, module gốc cũng phải biết các tuyến mà chúng tôi đã định nghĩa ở trên. Thêm tham chiếu đến các tuyến trong thuộc tính import của AppModule.

import { routes } from './app.routes';

imports: [
    RouterModule.forRoot(routes,
      { useHash: false }
    )
  ],

Lưu ý cách chúng tôi sử dụng các phương thức forRoot (hoặc forChild cuối cùng) trên RouterModule (tài liệu giải thích sự khác biệt một cách chi tiết, nhưng bây giờ chỉ cần biết rằng forRoot chỉ nên được gọi một lần trong ứng dụng của bạn cho các tuyến cấp cao nhất).

Angular Material Component để nâng cao UI/UX

Có một số thư viện cung cấp các component cấp cao cho phép bạn nhanh chóng tạo giao diện đẹp cho ứng dụng của mình. Chúng bao gồm các phương thức, cửa sổ bật lên, thẻ, danh sách, menu, v.v. Chúng là các phần tử giao diện người dùng có thể tái sử dụng đóng vai trò là khối xây dựng cho ứng dụng dành cho thiết bị di động của bạn, được tạo thành từ HTML, CSS và đôi khi là JavaScript.

Hai trong số các thư viện thành phần UI được sử dụng nhiều nhất là Angular Material và ngx-bootstrap. Angular Material là thư viện Angular UI chính thức và cung cấp rất nhiều component.

Mặt khác, ngx-bootstrap cung cấp một loạt các component Angular được tạo trên nền tảng Twitter Bootstrap framework.

Trong hướng dẫn Angular này, chúng ta sẽ sử dụng Angular Material, nhưng hãy thoải mái chọn cái phù hợp nhất với nhu cầu của bạn vì chúng đều siêu hoàn chỉnh và mạnh mẽ.

Trong ứng dụng Angular này, chúng ta có các bố cục khác nhau. Đối với mỗi view, chúng ta cần các component giao diện người dùng khác nhau. Đây là danh sách ngắn với các component quan trọng nhất mà chúng tôi đã sử dụng cho mỗi view và liên kết đến các chi tiết cụ thể của việc triển khai view đó.

View Categories:

  • Danh sách hiển thị các khái niệm Angular khác nhau mà bạn cần tìm hiểu.
  • Material Component: Component liệt kê danh sách danh mục, component chip cho các thẻ danh mục.

View Question:

  • View để hiển thị tất cả các câu hỏi của một danh mục cụ thể.
  • Material Component: Component liệt kê danh sách câu hỏi, component button, component hộp thoại cho các modal.

View Answer:

  • View để hiển thị tất cả các câu trả lời của một câu hỏi cụ thể.
  • Material Component: Component liệt kê danh sách câu trả lời, component button, component hộp thoại cho các modal.

Modal Câu hỏi mới và Câu trả lời mới:

  • Các modal tạo/cập nhật câu hỏi và câu trả lời.
  • Material Component: Component hộp thoại để quản lý phương thức.

Chúng tôi cũng đã sử dụng Angular Material Toolbar Component cho điều hướng breadcrumbs.

Vui lòng tìm hiểu thư viện các component giao diện người dùng mà Angular Material có trong trang tài liệu thành phần của họ.

Tạo backend cho ứng dụng Angular

Các lựa chọn thay thế khác nhau cho tích hợp dữ liệu backend API

Chìa khóa cho một ứng dụng đang phát triển là tạo ra các dịch vụ có thể sử dụng lại để quản lý tất cả các cuộc gọi dữ liệu đến backend của bạn.

Như bạn có thể đã biết, có nhiều cách để xử lý dữ liệu và triển khai backend. Trong hướng dẫn này, chúng tôi sẽ giải thích cách sử dụng dữ liệu từ tệp json tĩnh với dữ liệu giả. Trong hướng dẫn tìm hiểu cách xây dựng ứng dụng ngăn xếp MEAN, bạn sẽ tìm hiểu cách xây dựng và sử dụng dữ liệu từ REST API với Loopback (một framework node.js hoàn toàn phù hợp với REST API) và MongoDB (để lưu trữ dữ liệu).

Cả hai triển khai (json tĩnh và backend API từ xa) đều cần phải quan tâm về khía cạnh vấn đề của ứng dụng, cách xử lý các cuộc gọi dữ liệu. Điều này hoạt động giống nhau và độc lập về cách bạn triển khai backend. Chúng ta sẽ nói về các model và service và cách chúng làm việc cùng nhau để đạt được điều này.

Chúng tôi khuyến khích việc sử dụng các model kết hợp với các service để xử lý dữ liệu từ backend đến luồng trình bày.

Domain Model

Mô hình miền (domain model) rất quan trọng để xác định và thực thi logic nghiệp vụ trong các ứng dụng và đặc biệt có liên quan khi các ứng dụng trở nên lớn hơn và nhiều người làm việc trên chúng hơn.

Đồng thời, điều quan trọng là chúng ta phải giữ cho các ứng dụng của mình DRY và có thể bảo trì bằng cách chuyển logic ra khỏi các component và vào các lớp (model) riêng biệt có thể được gọi.

Một cách tiếp cận module như thế này, làm cho business logic của ứng dụng của chúng ta có thể tái sử dụng.

Data Service

Angular cho phép bạn tạo nhiều dịch vụ dữ liệu (data service) có thể tái sử dụng và đưa chúng vào các component cần chúng. Cấu trúc lại quyền truy cập dữ liệu vào một service riêng biệt, giữ cho component gọn gàng và tập trung vào việc hỗ trợ view. Nó cũng giúp kiểm thử đơn vị (unit testing) component dễ dàng hơn bằng cách sử dụng mock service.

Trong trường hợp của chúng tôi, chúng tôi đã định nghĩa một model cho dữ liệu danh mục câu hỏi mà chúng tôi đang lấy từ tệp json tĩnh. Model này được sử dụng bởi categories.service.ts.

//in category.model.ts
export class CategoryModel {
  slug: string;
  title: string;
  image: string;
  description: string;
  tags: Array<Object>;
}

//in categories.service.ts
getCategories(): Promise<CategoryModel[]> {
  return this.http.get("./assets/categories.json")
  .toPromise()
  .then(res => res.json() as CategoryModel[])
}

Và chúng tôi sử dụng service này ở categories.resolver.ts, nơi chúng tôi tìm nạp dữ liệu lên View Categories.

//in categories.resolver.ts
import { Injectable } from '@angular/core';
import { Resolve } from "@angular/router";
import { CategoriesService } from "../services/categories.service";

@Injectable()
export class CategoriesResolver implements Resolve<any> {

  constructor(private categoriesService: CategoriesService) { }

  resolve() {
    return new Promise((resolve, reject) => {

      let breadcrumbs = [
        { url: '/', label: 'Categories' }
      ];

      //get categories from local json file
      this.categoriesService.getCategories()
      .then(
        categories => {
          return resolve({
            categories: categories,
            breadcrumbs: breadcrumbs
          });
        },
        err => {
          return resolve(null);
        }
      )
    });
  }
}

Mỗi khi chúng ta thêm một service mới, hãy nhớ rằng Angular injector sẽ không biết cách tạo service đó theo mặc định. Nếu chúng ta chạy mã của mình ngay bây giờ, Angular sẽ báo lỗi. Sau khi tạo các service, chúng ta phải hướng dẫn Angular injector cách tạo service đó bằng cách đăng ký Service provider.

Theo trang tài liệu Angular về dependency injection, có hai cách để đăng ký Service provider: trong chính Component hoặc trong Module (NgModule). Trong trường hợp của chúng tôi, chúng tôi đăng ký tất cả các dịch vụ trong app.module.ts

//in app.module.ts
@NgModule({
  declarations: [
    AppComponent,
    CategoriesComponent,
    CategoryQuestionsComponent,
    NewQuestionModalComponent,
    NewAnswerModalComponent,
    UpdateAnswerModalComponent,
    QuestionAnswersComponent,
    DeleteQuestionModalComponent,
    DeleteAnswerModalComponent
  ],
  imports: [
    RouterModule.forRoot(routes,
      { useHash: false }
    ),
    SharedModule
  ],
  entryComponents: [

  ],
  providers: [
    CategoriesService,
    QuestionsService,
    AnswersService,
    CategoryQuestionsResolver,
    CategoriesResolver,
    QuestionAnswersResolver
  ],
  bootstrap: [AppComponent]
})

export class AppModule { }

Một lưu ý nhỏ về tầm quan trọng của Dependency Injection từ nguyên tắc kiến ​​trúc phần mềm: Hãy nhớ rằng chúng ta vừa đề cập rằng chúng ta "inject" các data service vào các component cần chúng? Khái niệm này được gọi là Dependency Injection.

Vì sao nên làm như vậy thay vì khởi tạo trực tiếp service trong component:

  • Component của chúng ta phải biết cách khởi tạo Service. Nếu chúng ta thay đổi phương thức khởi tạo của Service, chúng ta sẽ phải tìm mọi nơi chúng ta khởi tạo service và sửa chữa nó. Chạy xung quanh mã để sửa chữa dễ xảy ra lỗi và làm tăng thêm gánh nặng kiểm thử.
  • Chúng ta tạo ra một thể hiện mới của service mỗi khi chúng ta sử dụng service. Điều gì sẽ xảy ra nếu dịch vụ lưu kết quả vào bộ nhớ cache và chia sẻ bộ nhớ cache đó với những người khác? Chúng ta không thể làm điều đó.
  • Chúng ta đang gắn chặt Component (nơi chúng tôi mới cung cấp dịch vụ) vào việc triển khai Service cụ thể. Sẽ rất khó để chuyển đổi các triển khai cho các tình huống khác nhau. Chúng ta có thể hoạt động ngoại tuyến không? Chúng ta sẽ cần các phiên bản giả lập khác nhau đang được thử nghiệm chứ? Không dễ.
AngularTypeScript
Bài Viết Liên Quan:
Kiến trúc của Angular 8
Trung Nguyen 26/03/2021
Kiến trúc của Angular 8

Trong hướng dẫn này bạn sẽ tìm hiểu về cách tạo ứng dụng Angular 8 đầu tiên và kiến trúc của Angular 8.

Giới thiệu Angular 8
Trung Nguyen 26/03/2021
Giới thiệu Angular 8

Angular 8 là một web framework dựa trên TypeScript để xây dựng các ứng dụng web và di động.

Hướng dẫn lập trình Angular từ đầu từng bước (part 2)
Trung Nguyen 22/03/2021
Hướng dẫn lập trình Angular từ đầu từng bước (part 2)

Trong hướng dẫn này, bạn sẽ tìm hiểu kiến trúc ứng dụng Angular, cấu trúc một dự án Angular, định tuyến và điều hướng ứng dụng Angular.

Hướng dẫn lập trình Angular từ đầu từng bước (part 1)
Trung Nguyen 21/03/2021
Hướng dẫn lập trình Angular từ đầu từng bước (part 1)

Bạn tìm hiểu Angular là gì, so sánh các phiên bản Angular, thiết lập môi trường lập trình Angular và đã tạo ứng dụng Angular đơn giản đầu tiên.