Guards and Resolvers (Phần 2)
- 14-01-2024
- Toanngo92
- 0 Comments
Mục lục
Route Guards
Route Guards là những bảo vệ đường dẫn giúp quyết định liệu chúng ta có thể chuyển hướng đến một URL cụ thể hay không. Nếu tất cả các guard đều trả về true, quá trình chuyển hướng sẽ tiếp tục. Nếu có bất kỳ guard nào trả về false, quá trình chuyển hướng sẽ bị hủy. Nếu một guard trả về một UrlTree, chuyển hướng hiện tại sẽ bị hủy và một chuyển hướng mới sẽ được bắt đầu đến UrlTree mà guard đã trả về.
Angular Router cung cấp một số loại guard để xử lý các tình huống khác nhau:
- CanActivate (Kích hoạt components):
canActivate
: Kiểm tra xem có thể kích hoạt route hiện tại hay không.
- CanActivateChild (Kích hoạt components con):
canActivateChild
: Kiểm tra xem có thể kích hoạt các components con của route hiện tại hay không.
- CanDeactivate (Hủy kích hoạt components):
canDeactivate
: Kiểm tra xem có thể hủy kích hoạt route hiện tại hay không. Thường được sử dụng để kiểm tra trước khi rời khỏi một trang, ví dụ như khi người dùng đang chỉnh sửa một mục và muốn xác nhận rằng họ muốn rời khỏi trang.
- CanLoad (Load children – Tải components lazy loading):
canLoad
: Kiểm tra xem có thể tải các components lazy loading hay không, trước khi chúng được tải. Thường được sử dụng để kiểm soát việc tải các modules lười biếng (lazy-loaded modules).
Chẳng hạn, trong bài viết ngày 30, chúng ta đã tìm hiểu về CanActivate
để kiểm tra xem có thể kích hoạt các components hay không. Còn khi chúng ta muốn kiểm tra xem có được phép hủy kích hoạt (deactivate) các components hay không, chúng ta có thể sử dụng CanDeactivate
.
CanDeactivate
Giả sử chúng ta đang có chức năng chỉnh sửa bài viết và khách hàng yêu cầu thêm một tính năng mới: khi người dùng đã thay đổi thông tin mà chưa lưu lại và muốn chuyển sang một trang khác, hệ thống sẽ hỏi xác nhận từ người dùng liệu họ thực sự muốn rời khỏi trang hay không.
Trong tình huống này, chúng ta có thể sử dụng CanDeactivate
guard để kiểm tra trước khi hủy kích hoạt (deactivate) một component, tức là kiểm tra xem người dùng có thể rời khỏi trang chỉnh sửa bài viết hay không.
Bắt đầu bằng cách thêm một service và triển khai CanDeactivate guard như sau:
import { Injectable } from "@angular/core";
import {
ActivatedRouteSnapshot,
RouterStateSnapshot,
CanDeactivate,
UrlTree,
} from "@angular/router";
import { Observable } from "rxjs";
import { ArticleEditComponent } from "./article-edit/article-edit.component";
@Injectable({
providedIn: "root",
})
export class CanLeaveEditGuard
implements CanDeactivate<ArticleEditComponent>
{
canDeactivate(
component: ArticleEditComponent,
currentRoute: ActivatedRouteSnapshot,
currentState: RouterStateSnapshot,
nextState?: RouterStateSnapshot
):
| Observable<boolean | UrlTree>
| Promise<boolean | UrlTree>
| boolean
| UrlTree {
return !component.isEditing; // Replace with actual logic
}
}
Sau đó, chúng ta thêm guard vào cấu hình routing như sau:
const routes: Routes = [
{
path: "article",
component: ArticleComponent,
children: [
// Các cấu hình khác
{
path: ":slug/edit",
component: ArticleEditComponent,
canActivate: [CanEditArticleGuard],
canDeactivate: [CanLeaveEditGuard], // Đây là một mảng, có thể có nhiều guards
},
],
},
];
Khi người dùng thực hiện chuyển hướng ra khỏi màn hình chỉnh sửa, Angular Router sẽ thực thi CanLeaveEditGuard.canDeactivate
. Chúng ta có thể kiểm tra các điều kiện cần thiết để quyết định liệu có thể chuyển hướng hay không.
Để tăng khả năng sử dụng lại của guard, chúng ta có thể sử dụng một interface như sau:
export interface CheckDeactivate {
checkDeactivate(
currentRoute: ActivatedRouteSnapshot,
currentState: RouterStateSnapshot,
nextState?: RouterStateSnapshot
):
| Observable<boolean | UrlTree>
| Promise<boolean | UrlTree>
| boolean
| UrlTree;
}
Sau đó, component sẽ chịu trách nhiệm triển khai logic như sau:
@Injectable({
providedIn: "root",
})
export class CanLeaveEditGuard
implements CanDeactivate<CheckDeactivate>
{
canDeactivate(
component: CheckDeactivate,
currentRoute: ActivatedRouteSnapshot,
currentState: RouterStateSnapshot,
nextState?: RouterStateSnapshot
):
| Observable<boolean | UrlTree>
| Promise<boolean | UrlTree>
| boolean
| UrlTree {
return component.checkDeactivate(currentRoute, currentState, nextState);
}
}
Với cách làm này, chúng ta có thể kiểm tra điều kiện và hiển thị một hộp thoại xác nhận để hỏi người dùng liệu họ thực sự muốn rời khỏi trang hay không.
CanLoad
Đối với các lazy load route, chúng ta có thể kiểm tra trước tại phía frontend để xác định liệu có thể tải trang đó về hay không, dựa trên điều kiện nào đó. Một ví dụ thực tế có thể là ứng dụng của chúng ta có một phần quản lý dành cho admin. Đối với người dùng thông thường, không cần thiết phải tải toàn bộ mã nguồn của route /admin
về. Lúc này, CanLoad
sẽ trở thành một guard hữu ích để chúng ta sử dụng.
Dưới đây là một ví dụ với CanLoad guard:
import { Injectable } from "@angular/core";
import {
CanLoad,
UrlSegment,
Route,
RouterStateSnapshot,
UrlTree,
} from "@angular/router";
import { Observable } from "rxjs";
@Injectable({
providedIn: "root",
})
export class CanLoadAdminGuard implements CanLoad {
canLoad(
route: Route,
segments: UrlSegment[]
):
| Observable<boolean | UrlTree>
| Promise<boolean | UrlTree>
| boolean
| UrlTree {
return true; // Replace with actual logic
}
}
Và khi cấu hình routing, chúng ta sử dụng CanLoad guard như sau:
const routes: Routes = [
{
path: "admin",
loadChildren: () =>
import("./admin/admin.module").then((m) => m.AdminModule),
canLoad: [CanLoadAdminGuard], // Đây là một mảng, có thể có nhiều guards
},
{
path: "",
redirectTo: "article",
pathMatch: "full",
},
];
Trong ví dụ này, chúng ta có thể triển khai các logic kiểm tra để xem liệu người dùng có quyền admin hay không. Ví dụ:
@Injectable({
providedIn: "root",
})
export class CanLoadAdminGuard implements CanLoad {
constructor(private userService: UserService) {}
canLoad(
route: Route,
segments: UrlSegment[]
):
| Observable<boolean | UrlTree>
| Promise<boolean | UrlTree>
| boolean
| UrlTree {
return this.userService.currentUser.isAdmin; // Replace with actual logic
}
}
Guard sẽ kiểm tra xem người dùng có quyền admin hay không, và dựa vào kết quả, quyết định liệu route /admin
sẽ được tải về hay không.