Skip to content

Commit 4118067

Browse files
committed
Answer:66
1 parent 5dfb2c6 commit 4118067

6 files changed

Lines changed: 55 additions & 75 deletions

File tree

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,10 @@
1-
import { Injectable } from '@angular/core';
2-
import { CanActivate, Router } from '@angular/router';
1+
import { inject } from '@angular/core';
2+
import { CanActivateFn, Router } from '@angular/router';
33
import { AuthService } from './auth.service';
44

5-
@Injectable({ providedIn: 'root' })
6-
export class AdminGuard implements CanActivate {
7-
constructor(
8-
private authService: AuthService,
9-
private router: Router,
10-
) {}
5+
export const adminGuard: CanActivateFn = () => {
6+
const authService = inject(AuthService);
7+
const router = inject(Router);
118

12-
canActivate(): boolean {
13-
if (this.authService.isLoggedIn && this.authService.role === 'admin') {
14-
return true;
15-
}
16-
this.router.navigate(['/']);
17-
return false;
18-
}
19-
}
9+
return authService.isAdmin() || router.createUrlTree(['/']);
10+
};
Lines changed: 25 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,54 @@
11
import { TestBed } from '@angular/core/testing';
2-
import { provideRouter } from '@angular/router';
3-
import { page, userEvent } from 'vitest/browser';
4-
import { AdminComponent } from './admin.component';
5-
import { AdminGuard } from './admin.guard';
6-
import { AuthGuard } from './auth.guard';
2+
import { Router, provideRouter } from '@angular/router';
3+
import { AppComponent } from './app.component';
74
import { AuthService } from './auth.service';
8-
import { DashboardComponent } from './dashboard.component';
9-
import { HomeComponent } from './home.component';
105
import { routes } from './app.routes';
11-
import { AppComponent } from './app.component';
126

13-
describe('Auth Guards', () => {
7+
describe('Functional Auth Guards', () => {
148
beforeEach(() => {
159
TestBed.configureTestingModule({
1610
providers: [provideRouter(routes)],
1711
});
1812
TestBed.createComponent(AppComponent);
1913
});
2014

21-
describe('AuthGuard', () => {
22-
it('Then should redirect to / when not logged in', async () => {
23-
const guard = TestBed.inject(AuthGuard);
24-
const result = guard.canActivate();
25-
expect(result).toBeFalsy();
15+
describe('authGuard', () => {
16+
it('should redirect to / when not logged in', async () => {
17+
const router = TestBed.inject(Router);
18+
await router.navigate(['/dashboard']);
19+
expect(router.url).toBe('/');
2620
});
2721

28-
it('Then should allow access when logged in', async () => {
22+
it('should allow navigation when logged in', async () => {
2923
const authService = TestBed.inject(AuthService);
24+
const router = TestBed.inject(Router);
3025
authService.login('user');
31-
const guard = TestBed.inject(AuthGuard);
32-
const result = guard.canActivate();
33-
expect(result).toBe(true);
26+
await router.navigate(['/dashboard']);
27+
expect(router.url).toBe('/dashboard');
3428
});
3529
});
3630

37-
describe('AdminGuard', () => {
38-
it('Then should redirect to / when not logged in', async () => {
39-
const guard = TestBed.inject(AdminGuard);
40-
const result = guard.canActivate();
41-
expect(result).toBeFalsy();
31+
describe('adminGuard', () => {
32+
it('should redirect to / when not logged in', async () => {
33+
const router = TestBed.inject(Router);
34+
await router.navigate(['/admin']);
35+
expect(router.url).toBe('/');
4236
});
4337

44-
it('Then should redirect to / when logged in as user', async () => {
38+
it('should redirect to / when logged in as user', async () => {
4539
const authService = TestBed.inject(AuthService);
40+
const router = TestBed.inject(Router);
4641
authService.login('user');
47-
const guard = TestBed.inject(AdminGuard);
48-
const result = guard.canActivate();
49-
expect(result).toBeFalsy();
42+
await router.navigate(['/admin']);
43+
expect(router.url).toBe('/');
5044
});
5145

52-
it('Then should allow access when logged in as admin', async () => {
46+
it('should allow navigation when logged in as admin', async () => {
5347
const authService = TestBed.inject(AuthService);
48+
const router = TestBed.inject(Router);
5449
authService.login('admin');
55-
const guard = TestBed.inject(AdminGuard);
56-
const result = guard.canActivate();
57-
expect(result).toBe(true);
50+
await router.navigate(['/admin']);
51+
expect(router.url).toBe('/admin');
5852
});
5953
});
6054
});
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { Routes } from '@angular/router';
2-
import { AdminGuard } from './admin.guard';
2+
import { adminGuard } from './admin.guard';
33
import { AdminComponent } from './admin.component';
4-
import { AuthGuard } from './auth.guard';
4+
import { authGuard } from './auth.guard';
55
import { DashboardComponent } from './dashboard.component';
66
import { HomeComponent } from './home.component';
77

88
export const routes: Routes = [
99
{ path: '', component: HomeComponent },
10-
{ path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] },
11-
{ path: 'admin', component: AdminComponent, canActivate: [AuthGuard, AdminGuard] },
10+
{ path: 'dashboard', component: DashboardComponent, canActivate: [authGuard] },
11+
{ path: 'admin', component: AdminComponent, canActivate: [authGuard, adminGuard] },
1212
];
Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,10 @@
1-
import { Injectable } from '@angular/core';
2-
import { CanActivate, Router } from '@angular/router';
1+
import { inject } from '@angular/core';
2+
import { CanActivateFn, Router } from '@angular/router';
33
import { AuthService } from './auth.service';
44

5-
@Injectable({ providedIn: 'root' })
6-
export class AuthGuard implements CanActivate {
7-
constructor(
8-
private authService: AuthService,
9-
private router: Router,
10-
) {}
5+
export const authGuard: CanActivateFn = () => {
6+
const authService = inject(AuthService);
7+
const router = inject(Router);
118

12-
canActivate(): boolean {
13-
if (this.authService.isLoggedIn) {
14-
return true;
15-
}
16-
this.router.navigate(['/']);
17-
return false;
18-
}
19-
}
9+
return authService.isLoggedIn() || router.createUrlTree(['/']);
10+
};
Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
1-
import { Injectable } from '@angular/core';
1+
import { computed, Injectable, signal } from '@angular/core';
22

33
@Injectable({ providedIn: 'root' })
44
export class AuthService {
5-
isLoggedIn = false;
6-
role: 'user' | 'admin' = 'user';
5+
private readonly _isLoggedIn = signal(false);
6+
private readonly _role = signal<'user' | 'admin'>('user');
7+
8+
readonly isLoggedIn = this._isLoggedIn.asReadonly();
9+
readonly role = this._role.asReadonly();
10+
readonly isAdmin = computed(() => this._isLoggedIn() && this._role() === 'admin');
711

812
login(role: 'user' | 'admin'): void {
9-
this.isLoggedIn = true;
10-
this.role = role;
13+
this._isLoggedIn.set(true);
14+
this._role.set(role);
1115
}
1216

1317
logout(): void {
14-
this.isLoggedIn = false;
15-
this.role = 'user';
18+
this._isLoggedIn.set(false);
19+
this._role.set('user');
1620
}
1721
}

apps/angular/66-functional-auth-guard/src/app/home.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { AuthService } from './auth.service';
1111
<p class="text-gray-600">
1212
Status:
1313
<span class="font-semibold">
14-
{{ authService.isLoggedIn ? 'Logged in as ' + authService.role : 'Not logged in' }}
14+
{{ authService.isLoggedIn() ? 'Logged in as ' + authService.role() : 'Not logged in' }}
1515
</span>
1616
</p>
1717

0 commit comments

Comments
 (0)