canActivate
/ canActivateChild
/ canMatch
(或 canLoad
) 的差別與使用時機/projects/:slug/edit
)/projects/:slug
前先把單筆專案載入好canActivate
:要進入這個路由前檢查。canActivateChild
:要進入這個路由的子路由前檢查。canMatch
(v15+ 取代多數 canLoad
場景):是否允許配對到某 lazy route。常用於 Lazy Module 的權限判斷。route.data
)。使用者一進頁就看到完整內容,不用再「跳著載」。/projects/:slug/edit
)src/app/services/auth.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class AuthService {
// 假登入狀態;實務上會接 token / user profile
private _isLoggedIn = new BehaviorSubject<boolean>(false);
isLoggedIn$ = this._isLoggedIn.asObservable();
get isLoggedIn() { return this._isLoggedIn.value; }
login() { this._isLoggedIn.next(true); }
logout() { this._isLoggedIn.next(false); }
}
src/app/guards/auth.guard.ts
import { Injectable } from '@angular/core';
import { CanActivate, Router, UrlTree } from '@angular/router';
import { AuthService } from '../services/auth.service';
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
constructor(private auth: AuthService, private router: Router) {}
canActivate(): boolean | UrlTree {
// 已登入 → 放行;未登入 → 導回列表(也可導到 /login)
return this.auth.isLoggedIn ? true : this.router.parseUrl('/projects');
}
}
如果你的 Projects 是 Lazy Module,想在整個 lazy 路由層級就擋掉,改用 canMatch(或舊版 canLoad)也可以。
(以下範例在 projects-routing.module.ts
,延續 Day 16 的 slug 版路由結構)
import { Routes } from '@angular/router';
import { ProjectsComponent } from './projects.component';
import { ProjectDetailComponent } from './project-detail.component';
import { ProjectEditComponent } from './project-edit.component'; // 你可以先做個空元件
import { AuthGuard } from '../guards/auth.guard';
import { ProjectResolver } from '../resolvers/project.resolver';
export const routes: Routes = [
{ path: '', component: ProjectsComponent },
{
path: ':slug',
component: ProjectDetailComponent,
resolve: { project: ProjectResolver } // 等等 B 段會實作
},
{
path: ':slug/edit',
component: ProjectEditComponent,
canActivate: [AuthGuard], // 受保護頁面
resolve: { project: ProjectResolver }
}
];
src/app/resolvers/project.resolver.ts
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot, Router, UrlTree } from '@angular/router';
import { Observable, of } from 'rxjs';
import { switchMap, map, catchError } from 'rxjs/operators';
import { ProjectsDataService } from '../services/projects-data.service';
import { Project } from '../models/project.model';
@Injectable({ providedIn: 'root' })
export class ProjectResolver implements Resolve<Project | UrlTree> {
constructor(private projects: ProjectsDataService, private router: Router) {}
resolve(route: ActivatedRouteSnapshot): Observable<Project | UrlTree> {
const slug = route.paramMap.get('slug') ?? '';
if (!slug) return of(this.router.parseUrl('/projects'));
return this.projects.getBySlug$(slug).pipe(
map(p => p ? p : this.router.parseUrl('/projects')), // 找不到 → 導回列表(或導 404)
catchError(() => of(this.router.parseUrl('/projects')))
);
}
}
你也可以改成導到 /not-found,或回傳 null 然後在 component 判斷顯示 404 區塊。這裡示範「直接在 Resolver 做導向」。
route.data
src/app/components/project-detail/project-detail.component.ts
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Project } from '../../models/project.model';
import { map } from 'rxjs/operators';
@Component({
selector: 'app-project-detail',
templateUrl: './project-detail.component.html'
})
export class ProjectDetailComponent {
project$ = this.route.data.pipe(map(data => data['project'] as Project));
constructor(private route: ActivatedRoute) {}
}
project-detail.component.html
<div class="container section" *ngIf="project$ | async as project">
<h2>{{ project.title }}</h2>
<p class="muted">{{ project.tech }}</p>
<div class="gallery" *ngIf="project.images?.length">
<img *ngFor="let src of project.images" [src]="src" alt="專案截圖" width="400" />
</div>
<p>{{ project.desc }}</p>
<div class="actions">
<a class="btn" [href]="project.demo" target="_blank">Live Demo</a>
<a class="btn btn-outline" [href]="project.repo" target="_blank">GitHub</a>
</div>
<br />
<a routerLink="/projects" class="btn btn-outline">返回列表</a>
</div>
✅ 重點:component 不再自己打服務、處理 loading/error;資料在路由階段就準備好了。
如果你想用 404:
ng g c components/not-found
app-routing.module.ts
{ path: 'not-found', component: NotFoundComponent },
{ path: '**', redirectTo: 'not-found' }
把 Resolver 的導向改成 this.router.parseUrl('/not-found')
即可。
假設 projects
是 Lazy 模組,要整包保護(例如只有登入者才能看):
app-routing.module.ts
{
path: 'projects',
canMatch: [AuthGuard], // 或 canLoad(舊)
loadChildren: () => import('./projects/projects.module').then(m => m.ProjectsModule)
}
Guard 內回傳 boolean | UrlTree
;未登入時導回 /
或 /login
。
/projects/:slug
在進頁前先由 Resolver 取得單筆資料,畫面更順暢。/projects/:slug/edit
由 AuthGuard 保護,未登入者被導走。return this.projects.getBySlug$(...)
外還做 subscribe()
Observable<Project | UrlTree>
;不要自己 subscribe
router.navigate(...)
然後回 void
true/false
或 UrlTree
(推薦 UrlTree
,更語義化)canActivate
只在已載入後生效;要在「載入前就擋掉」,請用 canMatch
(或舊 canLoad
)我們來做 狀態管理(使用 Service + BehaviorSubject 打造輕量 store):
localStorage
)