Feature Modules, Child Routes and Services
- 14-01-2024
- Toanngo92
- 0 Comments
Mục lục
Feature Modules
Giả sử bạn đã tạo một ứng dụng ngày hôm qua và bạn muốn chia nó thành nhiều phần nhỏ hơn thay vì chỉ sử dụng một phần duy nhất. Liệu có cách nào để làm điều này trong Angular không? Có, bạn có thể sử dụng nhiều phần (NgModule) trong một ứng dụng Angular.
Để có nhiều phần chức năng (Feature Modules) và đồng thời hỗ trợ Router, bạn có thể sử dụng RouterModule.forChild. Điều này giúp bạn quản lý và tách chức năng của ứng dụng thành các module riêng biệt mà vẫn có thể tương tác với nhau.
Tách ArticleModule
Đầu tiên, chúng ta bắt đầu bằng việc tạo một NgModule mới, gọi là ArticleModule, để quản lý một số thành phần như components và services. Điều này giúp chúng ta sắp xếp và duy trì mã nguồn một cách dễ dàng hơn. Dưới đây là đoạn mã:
// Tách ArticleModule
import { NgModule } from "@angular/core";
import { CommonModule } from "@angular/common";
import { ArticleListComponent } from "./article-list/article-list.component";
import { ArticleDetailComponent } from "./article-detail/article-detail.component";
@NgModule({
imports: [CommonModule],
declarations: [ArticleListComponent, ArticleDetailComponent],
})
export class ArticleModule {}
Tiếp theo, chúng ta cần cấu hình RouterModule giống như khi chúng ta đã làm với AppRoutingModule. Tuy nhiên, lần này chúng ta sử dụng forChild thay vì forRoot. Nguyên nhân vì sao làm như vậy được giải thích ở Day 27, nhưng chúng ta sẽ không đi sâu vào đó. Dưới đây là đoạn mã:
// Tiếp theo cho ArticleModule
import { NgModule } from "@angular/core";
import { CommonModule } from "@angular/common";
import { Routes, RouterModule } from "@angular/router";
import { ArticleListComponent } from "./article-list/article-list.component";
import { ArticleDetailComponent } from "./article-detail/article-detail.component";
const routes: Routes = [
{
path: "article",
component: ArticleListComponent,
},
{
path: "article/:slug",
component: ArticleDetailComponent,
},
];
@NgModule({
imports: [CommonModule, RouterModule.forChild(routes)],
declarations: [ArticleListComponent, ArticleDetailComponent],
})
export class ArticleModule {}
Với những bước trên, chúng ta đã tạo xong một Feature Module kèm theo Router. Bây giờ, chỉ còn việc import nó vào AppModule để sử dụng. Điều này có thể thực hiện như sau:
// Import ArticleModule vào AppModule
import { ArticleModule } from "./article/article.module";
@NgModule({
imports: [
BrowserModule,
FormsModule,
ArticleModule, // <== Lưu ý thứ tự import này
AppRoutingModule,
],
declarations: [AppComponent],
bootstrap: [AppComponent],
})
export class AppModule {}
Khi này, bạn có thể truy cập ứng dụng của mình với đường dẫn “/article” để xem danh sách các bài viết (Article List).
Route config redirect
Để chuyển hướng người dùng từ một route nào đó đến một route khác, bạn có thể thiết lập cấu hình như sau:
// Route config redirect
const routes: Routes = [
{
path: "",
redirectTo: "article",
pathMatch: "full",
},
];
Khi người dùng truy cập trang web, nếu đường dẫn là trống (“”), họ sẽ tự động được chuyển hướng đến route “article”. Đối với trường hợp redirect như này, chúng ta thường nên sử dụng strategy để kiểm tra xem đường dẫn có khớp đầy đủ hay không.
Có hai chiến lược chủ yếu khi sử dụng pathMatch: “full” và “prefix”. Nếu bạn không thiết lập giá trị cho pathMatch, giá trị mặc định sẽ là “prefix”.
- Chiến lược “full”: Trong trường hợp này, chúng ta so sánh đường dẫn mà người dùng muốn chuyển hướng có khớp đầy đủ hay không, tương tự như toán tử ==. Ví dụ, nếu URL người dùng muốn là “tiepphan.com/abc/xyz”, chúng ta sẽ so sánh với đường dẫn tương ứng là “abc/xyz”. Nếu người dùng yêu cầu điều hướng đến “abc/cde”, điều kiện này sẽ không được đáp ứng.
- Chiến lược “prefix”: Trong trường hợp này, chỉ cần đường dẫn bắt đầu bằng prefix là đủ. Ví dụ, nếu người dùng muốn truy cập “web888.vn/abc/xyz”, đường dẫn “abc” cũng đáp ứng điều kiện, vì vậy mọi yêu cầu như render component cũng sẽ được thực thi.
Việc lựa chọn giữa “full” và “prefix” tùy thuộc vào yêu cầu cụ thể của ứng dụng và cách bạn muốn xử lý chuyển hướng.
Routing Module
Trong Angular Router, có một kỹ thuật hữu ích gọi là Routing Module, giúp chúng ta tách riêng phần quản lý routing thành một module độc lập. Một ứng dụng tiêu biểu cho kỹ thuật này là AppRoutingModule.
Bạn có thể áp dụng kỹ thuật tương tự cho các Feature Module của mình như sau:
// Routing Module cho Article Feature
const routes: Routes = [
{
path: "article",
component: ArticleListComponent,
},
{
path: "article/:slug",
component: ArticleDetailComponent,
},
];
@NgModule({
imports: [
CommonModule,
RouterModule.forChild(routes), // <== Cấu hình routing
],
declarations: [],
exports: [RouterModule], // <== Xuất NgModule này ra ngoài
})
export class ArticleRoutingModule {}
Ở đây, chúng ta cấu hình routing bằng cách sử dụng RouterModule.forChild(routes)
. Sau đó, chúng ta xuất NgModule này ra bên ngoài để ArticleModule có thể sử dụng các directives và components mà RouterModule cung cấp mà không cần phải import lại RouterModule mỗi lần.
Tiếp theo, khi bạn muốn sử dụng ArticleRoutingModule trong ArticleModule, bạn chỉ cần import nó như sau:
// Import ArticleRoutingModule vào ArticleModule
import { ArticleRoutingModule } from "./article-routing.module";
@NgModule({
imports: [CommonModule, ArticleRoutingModule],
declarations: [ArticleListComponent, ArticleDetailComponent],
})
export class ArticleModule {}
Như vậy, bạn đã sắp xếp và quản lý routing cho Feature Module của mình một cách gọn gàng và dễ hiểu hơn.
Child Routes
Hãy xem xét cấu hình dưới đây để thấy rằng có một phần prefix khá giống nhau. Vậy làm thế nào chúng ta có thể tổ chức cấu trúc parent-child trong routing?
Cách 1:
const routes: Routes = [
{
path: "article",
component: ArticleListComponent,
},
{
path: "article/:slug",
component: ArticleDetailComponent,
},
];
Angular Router cho phép chúng ta sử dụng cấu trúc parent-child như sau:
Cách 2:
const routes: Routes = [
{
path: "article",
children: [
{
path: "",
component: ArticleListComponent,
},
{
path: ":slug",
component: ArticleDetailComponent,
},
],
},
];
Ở đây, cấu trúc của chúng ta trở nên rõ ràng hơn, với path “article” trở thành một route cha, và các child route bên trong nó. Cách này tương đương với Cách 1, nhưng chúng ta sẽ có một cấu trúc parent + child cho path “article”.
Ngoài ra, route cha có thể kích hoạt một component, thường được gọi là layout component. Trong layout component này, chúng ta phải chứa một router-outlet, nơi mà các child component sẽ được kích hoạt.
Cách 3:
const routes: Routes = [
{
path: "article",
component: ArticleComponent, // <== component này có thể được gọi là `Layout component`
children: [
{
path: "",
component: ArticleListComponent,
},
{
path: ":slug",
component: ArticleDetailComponent,
},
],
},
];
Với cách này, route “article” có thể kích hoạt một component gọi là ArticleComponent, mà chúng ta thường xem là layout component. Trong layout component này, có một router-outlet để đánh dấu vị trí các child component sẽ được kích hoạt.
Như vậy, chúng ta có thể linh hoạt kết hợp giữa route cha và các route con trong quá trình quản lý routing trong Angular.
ActivatedRoute Service.
ActivatedRoute Service
là một công cụ quan trọng trong Angular, giúp chúng ta lấy thông tin về route liên kết với một component đang hiển thị trong một outlet. Nó giúp chúng ta điều hướng qua cây trạng thái của Router và trích xuất thông tin từ các node.
Service này mang đến một số API công khai, giúp chúng ta nắm bắt thông tin về route đang được kích hoạt và component đã được tải (kích hoạt). Điều này làm cho quá trình quản lý và trích xuất thông tin từ route trở nên linh hoạt và thuận tiện hơn trong ứng dụng Angular của chúng ta.
Retrieve params
Trong ví dụ ở bài học thứ 27, khi chúng ta muốn nhận thông tin từ các tham số (params), chúng ta đã tích hợp service này vào trong ArticleDetailComponent như sau:
export class ArticleDetailComponent implements OnInit {
article$: Observable<Article>;
constructor(private _route: ActivatedRoute, private _api: ArticleService) {}
ngOnInit(): void {
let slug = this._route.snapshot.paramMap.get("slug");
this.article$ = this._api.getArticleBySlug(slug);
}
}
Ngoài cách sử dụng snapshot
như trên, chúng ta cũng có thể sử dụng Observable
để quan sát thay đổi như sau:
export class ArticleDetailComponent implements OnInit {
article$: Observable<Article>;
constructor(private _route: ActivatedRoute, private _api: ArticleService) {}
ngOnInit(): void {
this.article$ = this._route.paramMap.pipe(
map((params) => params.get("slug")),
switchMap((slug) => this._api.getArticleBySlug(slug))
);
}
}
Nếu bạn chưa quen với RxJS và Observable, bạn có thể quay lại các bài học trước để làm quen.
Tại sao chúng ta lại sử dụng Observable ở đây thay vì snapshot? Nguyên tắc là, khi chúng ta chuyển từ một đường dẫn này sang một đường dẫn khác, Angular Router sẽ cố gắng tái sử dụng component hiện tại trước khi tạo mới một component mới, miễn là chúng không khác với config của route trước đó.
Ví dụ, nếu bạn đang ở đường dẫn /article và chuyển sang /article/bai-viet-1, Angular Router sẽ tạo mới một component vì chúng có config khác nhau. Ở đây, cả snapshot và paramMap Observable sẽ trả về giá trị slug là “bai-viet-1”.
Tuy nhiên, khi bạn chuyển từ /article/bai-viet-1 sang /article/bai-viet-2, vì chúng ta có cùng một config, Angular Router sẽ tái sử dụng component hiện tại mà không tạo mới. Lúc này, snapshot sẽ không thay đổi vì nó chỉ tạo một lần duy nhất khi component được khởi tạo, trong khi paramMap Observable sẽ phát ra một giá trị mới cho slug.
Do đó, tùy thuộc vào trường hợp cụ thể, bạn có thể sử dụng cách tiếp cận phù hợp với yêu cầu của mình. Sự so sánh giữa hai giải pháp này phụ thuộc vào cách bạn muốn xử lý dữ liệu trong các tình huống khác nhau.
Retrieve config: query params, route data, etc
Bên cạnh việc cung cấp API để truy cập các tham số, ActivatedRoute Service
còn giúp bạn truy cập/quan sát query params thông qua queryParamMap
.
Ví dụ, nếu bạn đang truy cập một URL như tiepphan.com/page/2?sort=createdDate, bạn có thể lấy giá trị của tham số “sort” thông qua snapshot.queryParamMap.get('sort')
hoặc sử dụng queryParamMap.subscribe
như sau:
// Sử dụng snapshot để lấy giá trị sort
let sortParam = this._route.snapshot.queryParamMap.get('sort');
// Hoặc sử dụng Observable để quan sát giá trị sort
this._route.queryParamMap.subscribe((query) => {
console.log(query.get("sort"));
});
Điều này giúp bạn dễ dàng trích xuất thông tin từ các query params của URL và thực hiện các hành động tương ứng trong ứng dụng Angular của bạn.
Router Service
là một dịch vụ cung cấp khả năng điều hướng và chỉnh sửa URL. Điều này giúp chúng ta có thể thao tác với địa chỉ web hoặc thậm chí thực hiện các thao tác điều hướng trong các thành phần.
Router Service
Ví dụ: Nếu bạn có một nút, khi người dùng nhấn vào, một số công việc sẽ được thực hiện và nếu thành công, bạn muốn chuyển hướng về trang chủ, bạn có thể sử dụng một trong hai phương thức sau để thực hiện điều hướng:
// Sử dụng navigateByUrl để chuyển hướng dựa trên URL cụ thể
this.router.navigateByUrl("/home");
// Hoặc sử dụng navigate để chuyển hướng thông qua các commands
this.router.navigate(["/home"]);
Ví dụ thực tế trong một component:
class SomeComponent {
constructor(private router: Router) {}
onClick() {
// Thực hiện một số công việc
// Sau đó, chuyển hướng về trang "/article"
this.router.navigate(["/article"]);
}
}
Ngoài ra, bạn cũng có thể quan sát các sự kiện của Router để thực hiện các tác vụ khác nhau. Ví dụ, để log thông tin khi một điều hướng đã hoàn tất, bạn có thể sử dụng Observable như sau:
this.router.events
.pipe(filter((e) => e instanceof NavigationEnd))
.subscribe((e) => {
console.log("Đã chuyển hướng thành công:", e);
});
Những cách tiếp cận này giúp bạn linh hoạt trong việc quản lý và thực hiện các tác vụ điều hướng trong ứng dụng Angular của mình.