- Authentication State: Track whether a user is logged in and their role (e.g., admin).
- Session Storage: Use local storage to persist login state across page refreshes.
- Services: Manage authentication logic in a dedicated service (e.g., AuthService).
- Route Protection: Use guards to restrict access to routes based on authentication state.
- User logs in via a login form (e.g., username/password).
- Validate credentials (e.g., against a service or API).
- Store authentication state in local storage.
- Use route guards to protect restricted routes.
- Allow logout to clear the state.
Route Guards (CanActivate, CanDeactivate)Angular’s route guards control navigation by allowing or denying access to routes based on conditions.CanActivate
- Determines if a route can be accessed.
- Used to restrict routes to authenticated users or specific roles.
- Returns true (allow navigation) or false (deny navigation, redirect elsewhere).
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(private router: Router) {}
canActivate(): boolean {
const isAuthenticated = !!localStorage.getItem('authToken');
if (!isAuthenticated) {
this.router.navigate(['/login']);
return false;
}
return true;
}
}- Determines if a user can leave a route (e.g., to prevent unsaved changes).
- Useful for forms or components with unsaved data.
- Returns true (allow navigation) or false (block navigation, e.g., show a confirmation dialog).
import { Injectable } from '@angular/core';
import { CanDeactivate } from '@angular/router';
import { RegisterComponent } from '../components/register/register.component';
@Injectable({
providedIn: 'root'
})
export class CanDeactivateGuard implements CanDeactivate<RegisterComponent> {
canDeactivate(component: RegisterComponent): boolean {
if (component.registerForm.dirty && !component.registerForm.submitted) {
return confirm('You have unsaved changes. Are you sure you want to leave?');
}
return true;
}
}Simple Login/Logout System with Local StorageWe’ll create an AuthService to handle login, logout, and authentication state, storing the logged-in user in local storage. The system will:
- Validate admin credentials (from UserService).
- Store the authenticated user’s state.
- Provide login/logout functionality.
- Update the UI (e.g., HeaderComponent) to reflect authentication state.
// src/app/services/auth.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { User } from '../models/user.model';
import { StorageService } from './storage.service';
import { UserService } from './user.service';
@Injectable({
providedIn: 'root'
})
export class AuthService {
private currentUserSubject = new BehaviorSubject<User | null>(null);
currentUser$ = this.currentUserSubject.asObservable();
constructor(
private storageService: StorageService,
private userService: UserService
) {
const storedUser = this.storageService.getLocalStorage('currentUser');
if (storedUser) {
this.currentUserSubject.next(storedUser);
}
}
login(username: string, password: string): boolean {
const isValid = this.userService.validateAdmin(username, password);
if (isValid) {
const user: User = {
id: 0,
userName: username,
emailId: `${username}@example.com`,
role: 'admin'
};
this.currentUserSubject.next(user);
this.storageService.setLocalStorage('currentUser', user);
return true;
}
return false;
}
logout() {
this.currentUserSubject.next(null);
this.storageService.clearLocalStorage('currentUser');
}
isAuthenticated(): boolean {
return !!this.currentUserSubject.getValue();
}
isAdmin(): boolean {
const user = this.currentUserSubject.getValue();
return user?.role === 'admin';
}
}- Uses BehaviorSubject to manage the current user state reactively.
- Integrates with StorageService to persist the user in local storage.
- login validates credentials via UserService and sets the user state.
- logout clears the user state and storage.
- isAuthenticated and isAdmin check the authentication state and role.
Restricting Admin RouteWe’ll create an AuthGuard to restrict the /admin route to authenticated admin users, redirecting unauthenticated users to the login page.Example: AuthGuard
// src/app/guards/auth.guard.ts
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { AuthService } from '../services/auth.service';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(
private authService: AuthService,
private router: Router
) {}
canActivate(): boolean {
if (this.authService.isAuthenticated() && this.authService.isAdmin()) {
return true;
}
this.router.navigate(['/login']);
return false;
}
}- Checks if the user is authenticated and has the admin role.
- Redirects to /login if the conditions are not met.
Hands-On Project: Restrict /admin Access to Authenticated AdminsLet’s enhance your angular-routing-demo project by:
- Creating an AuthService for login/logout functionality.
- Implementing an AuthGuard to restrict /admin access.
- Updating the LoginComponent to use the AuthService.
- Adding logout functionality to the HeaderComponent.
- Optionally applying a CanDeactivate guard to the RegisterComponent for unsaved changes.
- Maintaining integration with freeapi.miniprojectideas.com, StateService, and Angular Material from Modules 9 and 10.
ng new angular-routing-demo --style=scss
cd angular-routing-demo
ng add @angular/material
ng g component components/user
ng g component components/admin
ng g component components/user-details
ng g component components/not-found
ng g component components/admin-dashboard
ng g component components/admin-settings
ng g component components/register
ng g component components/login
ng g component components/user-info
ng g component components/header
ng g component components/footer
ng g pipe pipes/filter-by-role
ng g service services/user
ng g service services/storage
ng g service services/state
ng g service services/auth
ng g guard guards/auth
ng g guard guards/can-deactivate- Select the Indigo/Pink theme during ng add @angular/material.
- Choose CanActivate for AuthGuard and CanDeactivate for CanDeactivateGuard when prompted.
// src/styles.scss
@import '@angular/material/theming';
@include mat-core();
$primary: mat-palette($mat-indigo);
$accent: mat-palette($mat-pink, A200, A100, A400);
$theme: mat-light-theme($primary, $accent);
@include angular-material-theme($theme);
:root {
--primary-color: #3f51b5;
--accent-color: #ff4081;
--background-color: #f5f5f5;
--text-color: #333;
}
body {
margin: 0;
font-family: Roboto, 'Helvetica Neue', sans-serif;
background-color: var(--background-color);
color: var(--text-color);
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}// src/app/services/auth.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { User } from '../models/user.model';
import { StorageService } from './storage.service';
import { UserService } from './user.service';
@Injectable({
providedIn: 'root'
})
export class AuthService {
private currentUserSubject = new BehaviorSubject<User | null>(null);
currentUser$ = this.currentUserSubject.asObservable();
constructor(
private storageService: StorageService,
private userService: UserService
) {
const storedUser = this.storageService.getLocalStorage('currentUser');
if (storedUser) {
this.currentUserSubject.next(storedUser);
}
}
login(username: string, password: string): boolean {
const isValid = this.userService.validateAdmin(username, password);
if (isValid) {
const user: User = {
id: 0,
userName: username,
emailId: `${username}@example.com`,
role: 'admin'
};
this.currentUserSubject.next(user);
this.storageService.setLocalStorage('currentUser', user);
return true;
}
return false;
}
logout() {
this.currentUserSubject.next(null);
this.storageService.clearLocalStorage('currentUser');
}
isAuthenticated(): boolean {
return !!this.currentUserSubject.getValue();
}
isAdmin(): boolean {
const user = this.currentUserSubject.getValue();
return user?.role === 'admin';
}
}// src/app/guards/auth.guard.ts
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { AuthService } from '../services/auth.service';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(
private authService: AuthService,
private router: Router
) {}
canActivate(): boolean {
if (this.authService.isAuthenticated() && this.authService.isAdmin()) {
return true;
}
this.router.navigate(['/login']);
return false;
}
}// src/app/guards/can-deactivate.guard.ts
import { Injectable } from '@angular/core';
import { CanDeactivate } from '@angular/router';
import { RegisterComponent } from '../components/register/register.component';
@Injectable({
providedIn: 'root'
})
export class CanDeactivateGuard implements CanDeactivate<RegisterComponent> {
canDeactivate(component: RegisterComponent): boolean {
if (component.registerForm.dirty && !component.registerForm.submitted) {
return confirm('You have unsaved changes. Are you sure you want to leave?');
}
return true;
}
}// src/app/models/user.model.ts
export interface User {
id: number;
userName: string;
emailId: string;
role: string;
}// src/app/services/state.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { User } from '../models/user.model';
import { StorageService } from './storage.service';
@Injectable({
providedIn: 'root'
})
export class StateService {
private selectedUserSubject = new BehaviorSubject<User | null>(null);
selectedUser$ = this.selectedUserSubject.asObservable();
constructor(private storageService: StorageService) {
const storedUser = this.storageService.getLocalStorage('selectedUser');
if (storedUser) {
this.selectedUserSubject.next(storedUser);
}
}
setSelectedUser(user: User | null) {
this.selectedUserSubject.next(user);
if (user) {
this.storageService.setLocalStorage('selectedUser', user);
} else {
this.storageService.clearLocalStorage('selectedUser');
}
}
getCurrentUser() {
return this.selectedUserSubject.getValue();
}
}// src/app/services/storage.service.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class StorageService {
setLocalStorage(key: string, value: any) {
localStorage.setItem(key, JSON.stringify(value));
}
getLocalStorage(key: string) {
const data = localStorage.getItem(key);
return data ? JSON.parse(data) : null;
}
clearLocalStorage(key: string) {
localStorage.removeItem(key);
}
}// src/app/services/user.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { catchError, finalize } from 'rxjs/operators';
import { User } from '../models/user.model';
@Injectable({
providedIn: 'root'
})
export class UserService {
private apiUrl = 'https://freeapi.miniprojectideas.com/api/User/GetAllUsers';
private localUsers: User[] = [
{ id: 1, userName: 'Alice', emailId: 'alice@example.com', role: 'admin' },
{ id: 2, userName: 'Bob', emailId: 'bob@example.com', role: 'user' },
{ id: 3, userName: 'Charlie', emailId: 'charlie@example.com', role: 'admin' },
{ id: 4, userName: 'David', emailId: 'david@example.com', role: 'user' }
];
private admins = [
{ username: 'admin1', password: 'admin123' },
{ username: 'admin2', password: 'admin456' }
];
constructor(private http: HttpClient) {}
getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.apiUrl).pipe(
catchError(error => {
console.error('Error fetching users from API:', error);
return of(this.localUsers);
})
);
}
getUserById(id: number): Observable<User | undefined> {
return this.http.get<User>(`${this.apiUrl}/${id}`).pipe(
catchError(error => {
console.error('Error fetching user by ID:', error);
const localUser = this.localUsers.find(user => user.id === id);
return of(localUser);
})
);
}
addUser(user: User): Observable<any> {
return this.http.post(this.apiUrl, user).pipe(
catchError(error => {
console.error('Error adding user:', error);
this.localUsers.push(user);
return of(user);
})
);
}
validateAdmin(username: string, password: string): boolean {
return this.admins.some(admin => admin.username === username && admin.password === password);
}
}// src/app/app.routes.ts
import { Routes } from '@angular/router';
import { UserComponent } from './components/user/user.component';
import { AdminComponent } from './components/admin/admin.component';
import { UserDetailsComponent } from './components/user-details/user-details.component';
import { NotFoundComponent } from './components/not-found/not-found.component';
import { AdminDashboardComponent } from './components/admin-dashboard/admin-dashboard.component';
import { AdminSettingsComponent } from './components/admin-settings/admin-settings.component';
import { RegisterComponent } from './components/register/register.component';
import { LoginComponent } from './components/login/login.component';
import { AuthGuard } from './guards/auth.guard';
import { CanDeactivateGuard } from './guards/can-deactivate.guard';
export const routes: Routes = [
{
path: 'user',
component: UserComponent,
children: [
{ path: ':id', component: UserDetailsComponent }
]
},
{ path: 'register', component: RegisterComponent, canDeactivate: [CanDeactivateGuard] },
{ path: 'login', component: LoginComponent },
{
path: 'admin',
component: AdminComponent,
canActivate: [AuthGuard],
children: [
{ path: 'dashboard', component: AdminDashboardComponent },
{ path: 'settings', component: AdminSettingsComponent },
{ path: '', redirectTo: 'dashboard', pathMatch: 'full' }
]
},
{ path: '', redirectTo: '/user', pathMatch: 'full' },
{ path: '**', component: NotFoundComponent }
];// src/main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { AppComponent } from './app/app.component';
import { routes } from './app/app.routes';
import { provideAnimations } from '@angular/platform-browser/animations';
bootstrapApplication(AppComponent, {
providers: [
provideRouter(routes),
provideHttpClient(),
provideAnimations()
]
}).catch(err => console.error(err));// src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { Router, NavigationStart, NavigationEnd, RouterOutlet } from '@angular/router';
import { HeaderComponent } from './components/header/header.component';
import { FooterComponent } from './components/footer/footer.component';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
standalone: true,
imports: [RouterOutlet, HeaderComponent, FooterComponent]
})
export class AppComponent implements OnInit {
constructor(private router: Router) {}
ngOnInit() {
this.router.events.subscribe(event => {
if (event instanceof NavigationStart) {
console.log('Navigation started:', event.url);
} else if (event instanceof NavigationEnd) {
console.log('Navigation ended:', event.url);
}
});
}
}// src/app/app.component.html
<app-header></app-header>
<main class="container">
<router-outlet></router-outlet>
</main>
<app-footer></app-footer>// src/app/app.component.scss
main.container {
min-height: calc(100vh - 128px); /* Adjust for header/footer height */
}// src/app/components/header/header.component.ts
import { Component } from '@angular/core';
import { RouterLink, RouterLinkActive, Router } from '@angular/router';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatButtonModule } from '@angular/material/button';
import { AuthService } from '../../services/auth.service';
import { User } from '../../models/user.model';
@Component({
selector: 'app-header',
template: `
<mat-toolbar color="primary">
<span>Angular Routing Demo</span>
<nav>
<a mat-button routerLink="/user" routerLinkActive="active">User Dashboard</a>
<a mat-button routerLink="/register" routerLinkActive="active">Register</a>
<a mat-button *ngIf="!(currentUser$ | async)" routerLink="/login" routerLinkActive="active">Login</a>
<span *ngIf="currentUser$ | async as user">Welcome, {{ user.userName }}</span>
<button mat-button *ngIf="currentUser$ | async" (click)="logout()">Logout</button>
<a mat-button routerLink="/admin" routerLinkActive="active">Admin Panel</a>
</nav>
</mat-toolbar>
`,
styleUrls: ['./header.component.scss'],
standalone: true,
imports: [RouterLink, RouterLinkActive, MatToolbarModule, MatButtonModule]
})
export class HeaderComponent {
currentUser$ = this.authService.currentUser$;
constructor(
private authService: AuthService,
private router: Router
) {}
logout() {
this.authService.logout();
this.router.navigate(['/user']);
}
}// src/app/components/header/header.component.scss
mat-toolbar {
display: flex;
justify-content: space-between;
padding: 0 20px;
nav {
display: flex;
align-items: center;
a, button, span {
margin-left: 10px;
}
a.active {
font-weight: bold;
border-bottom: 2px solid white;
}
}
}// src/app/components/footer/footer.component.ts
import { Component } from '@angular/core';
import { MatToolbarModule } from '@angular/material/toolbar';
@Component({
selector: 'app-footer',
template: `
<mat-toolbar color="primary">
<span>© 2025 Angular Routing Demo</span>
</mat-toolbar>
`,
styleUrls: ['./footer.component.scss'],
standalone: true,
imports: [MatToolbarModule]
})
export class FooterComponent {}// src/app/components/footer/footer.component.scss
mat-toolbar {
justify-content: center;
padding: 10px 0;
}// src/app/components/admin/admin.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { RouterOutlet, RouterLink, Router } from '@angular/router';
import { Observable, of } from 'rxjs';
import { catchError, finalize } from 'rxjs/operators';
import { FilterByRolePipe } from '../../pipes/filter-by-role.pipe';
import { UserService } from '../../services/user.service';
import { StateService } from '../../services/state.service';
import { User } from '../../models/user.model';
import { MatCardModule } from '@angular/material/card';
import { MatButtonModule } from '@angular/material/button';
import { MatSelectModule } from '@angular/material/select';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
@Component({
selector: 'app-admin',
templateUrl: './admin.component.html',
styleUrls: ['./admin.component.scss'],
standalone: true,
imports: [
FormsModule,
RouterOutlet,
RouterLink,
FilterByRolePipe,
MatCardModule,
MatButtonModule,
MatSelectModule,
MatInputModule,
MatFormFieldModule
]
})
export class AdminComponent implements OnInit, OnDestroy {
users$: Observable<User[]>;
filterRole = 'admin';
isHighlighted = false;
isLoading = false;
error: string | null = null;
selectedUser$ = this.stateService.selectedUser$;
constructor(
private userService: UserService,
private stateService: StateService,
private router: Router
) {
this.users$ = of([]);
}
ngOnInit() {
this.isLoading = true;
this.users$ = this.userService.getUsers().pipe(
catchError(err => {
this.error = 'Failed to load users: ' + err.message;
this.isLoading = false;
return of([]);
}),
finalize(() => this.isLoading = false)
);
}
ngOnDestroy() {
console.log('AdminComponent: Destroyed');
}
toggleHighlight() {
this.isHighlighted = !this.isHighlighted;
}
setFilterRole(role: string) {
this.filterRole = role;
}
logSearch(value: string) {
console.log('Search:', value);
}
selectUser(user: User) {
this.stateService.setSelectedUser(user);
this.router.navigate(['/user']);
}
clearSelectedUser() {
this.stateService.setSelectedUser(null);
}
}// src/app/components/admin/admin.component.html
<mat-card class="container">
<mat-card-header>
<mat-card-title>Admin Panel</mat-card-title>
</mat-card-header>
<mat-card-content>
<nav>
<a mat-button routerLink="dashboard" routerLinkActive="active">Dashboard</a>
<a mat-button routerLink="settings" routerLinkActive="active">Settings</a>
</nav>
<router-outlet></router-outlet>
<h3>Admin Users List</h3>
<div *ngIf="error" class="error">{{ error }}</div>
<div *ngIf="isLoading" class="loading">Loading users...</div>
<div *ngIf="users$ | async as users; else loading">
<mat-form-field appearance="fill">
<mat-label>Filter by Role</mat-label>
<mat-select [(ngModel)]="filterRole" (ngModelChange)="setFilterRole($event)">
<mat-option value="all">All</mat-option>
<mat-option value="admin">Admin</mat-option>
<mat-option value="user">User</mat-option>
</mat-select>
</mat-form-field>
<button mat-raised-button color="primary" (click)="toggleHighlight()">Toggle Highlight</button>
<mat-card *ngFor="let user of users | filterByRole:filterRole" [ngClass]="{'highlight': isHighlighted && user.role === 'admin'}">
<mat-card-content>
{{ user.userName | uppercase }} ({{ user.role }}) - {{ user.emailId }}
<button mat-raised-button color="accent" (click)="selectUser(user)">Select</button>
</mat-card-content>
</mat-card>
<div *ngIf="selectedUser$ | async as selectedUser">
<p>Selected: {{ selectedUser.userName }}</p>
<button mat-raised-button color="warn" (click)="clearSelectedUser()">Clear Selection</button>
</div>
<mat-form-field appearance="fill">
<mat-label>Search users</mat-label>
<input matInput #searchInput>
<button mat-button (click)="logSearch(searchInput.value)">Search</button>
</mat-form-field>
</div>
<ng-template #loading>Loading users...</ng-template>
</mat-card-content>
</mat-card>// src/app/components/admin/admin.component.scss
.container {
margin: 20px;
}
mat-card {
margin: 10px 0;
padding: 10px;
}
.highlight {
background-color: #e0f7fa;
}
nav {
margin-bottom: 20px;
a {
margin-right: 10px;
&.active {
font-weight: bold;
border-bottom: 2px solid var(--primary-color);
}
}
}
.loading {
text-align: center;
color: var(--primary-color);
}
.error {
text-align: center;
color: red;
}
button {
margin-left: 10px;
}// src/app/components/user/user.component.ts
import { Component, OnInit } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { Observable, of } from 'rxjs';
import { catchError, finalize } from 'rxjs/operators';
import { UserService } from '../../services/user.service';
import { StateService } from '../../services/state.service';
import { UserInfoComponent } from '../user-info/user-info.component';
import { User } from '../../models/user.model';
import { MatCardModule } from '@angular/material/card';
import { MatButtonModule } from '@angular/material/button';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatListModule } from '@angular/material/list';
@Component({
selector: 'app-user',
templateUrl: './user.component.html',
styleUrls: ['./user.component.scss'],
standalone: true,
imports: [
FormsModule,
RouterLink,
UserInfoComponent,
MatCardModule,
MatButtonModule,
MatInputModule,
MatFormFieldModule,
MatListModule
]
})
export class UserComponent implements OnInit {
searchQuery = '';
users$: Observable<User[]>;
isLoading = false;
error: string | null = null;
selectedUser$ = this.stateService.selectedUser$;
constructor(
private route: ActivatedRoute,
private router: Router,
private userService: UserService,
private stateService: StateService
) {
this.users$ = of([]);
}
ngOnInit() {
this.isLoading = true;
this.users$ = this.userService.getUsers().pipe(
catchError(err => {
this.error = 'Failed to load users: ' + err.message;
this.isLoading = false;
return of([]);
}),
finalize(() => this.isLoading = false)
);
this.route.queryParamMap.subscribe(params => {
this.searchQuery = params.get('search') || '';
});
}
updateQuery() {
this.router.navigate(['/user'], { queryParams: { search: this.searchQuery } });
}
clearSelectedUser() {
this.stateService.setSelectedUser(null);
}
}// src/app/components/user/user.component.html
<mat-card class="container">
<mat-card-header>
<mat-card-title>User Dashboard</mat-card-title>
</mat-card-header>
<mat-card-content>
<div *ngIf="error" class="error">{{ error }}</div>
<div *ngIf="isLoading" class="loading">Loading users...</div>
<mat-form-field appearance="fill">
<mat-label>Search users</mat-label>
<input matInput [(ngModel)]="searchQuery" (ngModelChange)="updateQuery()">
</mat-form-field>
<p>Search Query: {{ searchQuery }}</p>
<app-user-info [user]="selectedUser$ | async" (clearUser)="clearSelectedUser()"></app-user-info>
<router-outlet></router-outlet>
<mat-list *ngIf="users$ | async as users; else loading">
<mat-list-item *ngFor="let user of users">
<a mat-button routerLink="/user/{{ user.id }}">{{ user.userName }} ({{ user.role }}) - {{ user.emailId }}</a>
</mat-list-item>
</mat-list>
<ng-template #loading>Loading users...</ng-template>
</mat-card-content>
</mat-card>// src/app/components/user/user.component.scss
.container {
margin: 20px;
}
mat-list {
margin-top: 20px;
}
mat-list-item {
margin: 5px 0;
a {
text-decoration: none;
color: var(--primary-color);
&:hover {
text-decoration: underline;
}
}
}
.loading {
text-align: center;
color: var(--primary-color);
}
.error {
text-align: center;
color: red;
}// src/app/components/user-info/user-info.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { User } from '../../models/user.model';
import { StateService } from '../../services/state.service';
import { MatCardModule } from '@angular/material/card';
import { MatButtonModule } from '@angular/material/button';
@Component({
selector: 'app-user-info',
template: `
<mat-card *ngIf="user">
<mat-card-content>
<p>Selected User: {{ user.userName }} ({{ user.role }})</p>
<button mat-raised-button color="warn" (click)="clearSelection()">Clear Selection</button>
</mat-card-content>
</mat-card>
`,
styleUrls: ['./user-info.component.scss'],
standalone: true,
imports: [MatCardModule, MatButtonModule]
})
export class UserInfoComponent {
@Input() user: User | null = null;
@Output() clearUser = new EventEmitter<void>();
constructor(private stateService: StateService) {}
clearSelection() {
this.stateService.setSelectedUser(null);
this.clearUser.emit();
}
}// src/app/components/user-info/user-info.component.scss
mat-card {
margin: 10px 0;
padding: 10px;
}// src/app/components/user-details/user-details.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, RouterLink } from '@angular/router';
import { Observable, of } from 'rxjs';
import { catchError, finalize } from 'rxjs/operators';
import { UserService } from '../../services/user.service';
import { User } from '../../models/user.model';
import { MatCardModule } from '@angular/material/card';
import { MatButtonModule } from '@angular/material/button';
@Component({
selector: 'app-user-details',
templateUrl: './user-details.component.html',
styleUrls: ['./user-details.component.scss'],
standalone: true,
imports: [RouterLink, MatCardModule, MatButtonModule]
})
export class UserDetailsComponent implements OnInit {
user$: Observable<User | undefined>;
isLoading = false;
error: string | null = null;
constructor(
private route: ActivatedRoute,
private userService: UserService
) {
this.user$ = of(undefined);
}
ngOnInit() {
this.isLoading = true;
this.route.paramMap.subscribe(params => {
const id = Number(params.get('id'));
this.user$ = this.userService.getUserById(id).pipe(
catchError(err => {
this.error = 'Failed to load user: ' + err.message;
this.isLoading = false;
return of(undefined);
}),
finalize(() => this.isLoading = false)
);
});
}
}// src/app/components/user-details/user-details.component.html
<mat-card class="container">
<mat-card-header>
<mat-card-title>User Details</mat-card-title>
</mat-card-header>
<mat-card-content>
<div *ngIf="error" class="error">{{ error }}</div>
<div *ngIf="isLoading" class="loading">Loading user...</div>
<div *ngIf="user$ | async as user; else noUser">
<p>Name: {{ user.userName }}</p>
<p>Email: {{ user.emailId }}</p>
<p>Role: {{ user.role | uppercase }}</p>
</div>
<ng-template #noUser><p>User not found</p></ng-template>
<a mat-button routerLink="/user">Back to User Dashboard</a>
</mat-card-content>
</mat-card>// src/app/components/user-details/user-details.component.scss
.container {
margin: 20px;
}
.loading {
text-align: center;
color: var(--primary-color);
}
.error {
text-align: center;
color: red;
}// src/app/components/register/register.component.ts
import { Component } from '@angular/core';
import { ReactiveFormsModule, FormGroup, FormControl, Validators } from '@angular/forms';
import { Router, RouterLink } from '@angular/router';
import { UserService } from '../../services/user.service';
import { noSpacesValidator } from '../../validators/no-spaces.validator';
import { MatCardModule } from '@angular/material/card';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
@Component({
selector: 'app-register',
templateUrl: './register.component.html',
styleUrls: ['./register.component.scss'],
standalone: true,
imports: [
ReactiveFormsModule,
RouterLink,
MatCardModule,
MatButtonModule,
MatFormFieldModule,
MatInputModule,
MatSelectModule
]
})
export class RegisterComponent {
registerForm = new FormGroup({
userName: new FormControl('', [Validators.required, Validators.minLength(3), noSpacesValidator()]),
emailId: new FormControl('', [Validators.required, Validators.email]),
role: new FormControl('user', [Validators.required])
});
isLoading = false;
error: string | null = null;
constructor(private userService: UserService, private router: Router) {}
onSubmit() {
if (this.registerForm.valid) {
this.isLoading = true;
const formValue = this.registerForm.value;
const newUser = {
id: Math.max(...this.localUsers.map(u => u.id), 0) + 1,
userName: formValue.userName || '',
emailId: formValue.emailId || '',
role: formValue.role || 'user'
};
this.userService.addUser(newUser).subscribe({
next: () => {
this.registerForm.reset();
this.registerForm.markAsPristine();
this.router.navigate(['/user']);
},
error: err => {
this.error = 'Failed to register user: ' + err.message;
this.isLoading = false;
},
complete: () => {
this.isLoading = false;
}
});
}
}
private get localUsers(): User[] {
let users: User[] = [];
this.userService.getUsers().subscribe({
next: (data) => users = data,
error: (err) => console.error('Error fetching users:', err)
});
return users;
}
}// src/app/components/register/register.component.html
<mat-card class="container">
<mat-card-header>
<mat-card-title>User Registration</mat-card-title>
</mat-card-header>
<mat-card-content>
<div *ngIf="error" class="error">{{ error }}</div>
<div *ngIf="isLoading" class="loading">Registering user...</div>
<form [formGroup]="registerForm" (ngSubmit)="onSubmit()">
<mat-form-field appearance="fill">
<mat-label>Username</mat-label>
<input matInput formControlName="userName">
<mat-error *ngIf="registerForm.get('userName')?.hasError('required') && registerForm.get('userName')?.touched">
Username is required
</mat-error>
<mat-error *ngIf="registerForm.get('userName')?.hasError('minlength') && registerForm.get('userName')?.touched">
Username must be at least 3 characters
</mat-error>
<mat-error *ngIf="registerForm.get('userName')?.hasError('noSpaces') && registerForm.get('userName')?.touched">
Username cannot contain spaces
</mat-error>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Email</mat-label>
<input matInput formControlName="emailId">
<mat-error *ngIf="registerForm.get('emailId')?.hasError('required') && registerForm.get('emailId')?.touched">
Email is required
</mat-error>
<mat-error *ngIf="registerForm.get('emailId')?.hasError('email') && registerForm.get('emailId')?.touched">
Invalid email format
</mat-error>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Role</mat-label>
<mat-select formControlName="role">
<mat-option value="user">User</mat-option>
<mat-option value="admin">Admin</mat-option>
</mat-select>
</mat-form-field>
<button mat-raised-button color="primary" type="submit" [disabled]="registerForm.invalid || isLoading">Register</button>
</form>
<a mat-button routerLink="/user">Back to User Dashboard</a>
</mat-card-content>
</mat-card>// src/app/components/register/register.component.scss
.container {
margin: 20px;
}
form {
display: flex;
flex-direction: column;
gap: 10px;
}
mat-form-field {
width: 100%;
max-width: 400px;
}
.error {
color: red;
font-size: 0.9em;
}
.loading {
text-align: center;
color: var(--primary-color);
}// src/app/components/login/login.component.ts
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { Router, RouterLink } from '@angular/router';
import { AuthService } from '../../services/auth.service';
import { MatCardModule } from '@angular/material/card';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss'],
standalone: true,
imports: [
FormsModule,
RouterLink,
MatCardModule,
MatButtonModule,
MatFormFieldModule,
MatInputModule
]
})
export class LoginComponent {
username = '';
password = '';
errorMessage = '';
constructor(
private authService: AuthService,
private router: Router
) {}
onSubmit() {
if (this.authService.login(this.username, this.password)) {
this.router.navigate(['/admin']);
} else {
this.errorMessage = 'Invalid username or password';
}
}
}// src/app/components/login/login.component.html
<mat-card class="container">
<mat-card-header>
<mat-card-title>Admin Login</mat-card-title>
</mat-card-header>
<mat-card-content>
<form (ngSubmit)="onSubmit()">
<mat-form-field appearance="fill">
<mat-label>Username</mat-label>
<input matInput [(ngModel)]="username" name="username" required>
<mat-error *ngIf="form.username?.errors?.['required'] && form.username?.touched">
Username is required
</mat-error>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Password</mat-label>
<input matInput [(ngModel)]="password" name="password" type="password" required>
<mat-error *ngIf="form.password?.errors?.['required'] && form.password?.touched">
Password is required
</mat-error>
</mat-form-field>
<div *ngIf="errorMessage" class="error">{{ errorMessage }}</div>
<button mat-raised-button color="primary" type="submit" [disabled]="!form.valid">Login</button>
</form>
<a mat-button routerLink="/user">Back to User Dashboard</a>
</mat-card-content>
</mat-card>// src/app/components/login/login.component.scss
.container {
margin: 20px;
}
form {
display: flex;
flex-direction: column;
gap: 10px;
}
mat-form-field {
width: 100%;
max-width: 400px;
}
.error {
color: red;
font-size: 0.9em;
}// src/app/components/admin-dashboard/admin-dashboard.component.ts
import { Component } from '@angular/core';
import { MatCardModule } from '@angular/material/card';
@Component({
selector: 'app-admin-dashboard',
template: `
<mat-card class="container">
<mat-card-header>
<mat-card-title>Admin Dashboard</mat-card-title>
</mat-card-header>
<mat-card-content>
<p>Welcome to the Admin Dashboard!</p>
</mat-card-content>
</mat-card>
`,
styleUrls: ['./admin-dashboard.component.scss'],
standalone: true,
imports: [MatCardModule]
})
export class AdminDashboardComponent {}// src/app/components/admin-dashboard/admin-dashboard.component.scss
.container {
margin: 20px;
}// src/app/components/admin-settings/admin-settings.component.ts
import { Component } from '@angular/core';
import { MatCardModule } from '@angular/material/card';
@Component({
selector: 'app-admin-settings',
template: `
<mat-card class="container">
<mat-card-header>
<mat-card-title>Admin Settings</mat-card-title>
</mat-card-header>
<mat-card-content>
<p>Configure settings here.</p>
</mat-card-content>
</mat-card>
`,
styleUrls: ['./admin-settings.component.scss'],
standalone: true,
imports: [MatCardModule]
})
export class AdminSettingsComponent {}// src/app/components/admin-settings/admin-settings.component.scss
.container {
margin: 20px;
}// src/app/components/not-found/not-found.component.ts
import { Component } from '@angular/core';
import { RouterLink } from '@angular/router';
import { MatCardModule } from '@angular/material/card';
import { MatButtonModule } from '@angular/material/button';
@Component({
selector: 'app-not-found',
template: `
<mat-card class="container">
<mat-card-header>
<mat-card-title>404 - Page Not Found</mat-card-title>
</mat-card-header>
<mat-card-content>
<a mat-button routerLink="/user">Go back to User Dashboard</a>
</mat-card-content>
</mat-card>
`,
styleUrls: ['./not-found.component.scss'],
standalone: true,
imports: [RouterLink, MatCardModule, MatButtonModule]
})
export class NotFoundComponent {}// src/app/components/not-found/not-found.component.scss
.container {
margin: 20px;
text-align: center;
}// src/app/pipes/filter-by-role.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
import { User } from '../models/user.model';
@Pipe({
name: 'filterByRole',
standalone: true
})
export class FilterByRolePipe implements PipeTransform {
transform(users: User[], role: string): User[] {
if (!users || role === 'all') return users;
return users.filter(user => user.role === role);
}
}// src/app/validators/no-spaces.validator.ts
import { AbstractControl, ValidatorFn } from '@angular/forms';
export function noSpacesValidator(): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } | null => {
const hasSpaces = control.value?.includes(' ');
return hasSpaces ? { noSpaces: true } : null;
};
}- Run ng serve and visit http://localhost:4200.
- Test features:
- Login (/login):
- Log in with admin1/admin123. Verify redirection to /admin.
- Try invalid credentials (e.g., wrong/wrong). Confirm error message.
- Admin Route (/admin):
- Attempt to access /admin without logging in. Confirm redirection to /login.
- Log in as admin and access /admin. Verify access to dashboard and settings.
- Logout (Header):
- Log in, then click “Logout” in the header. Confirm redirection to /user and cleared user state (check local storage).
- Register (/register):
- Enter form data, then try navigating away without submitting. Confirm the CanDeactivate prompt.
- Submit a valid form and verify the new user appears in the user list.
- User Dashboard (/user):
- Select a user and verify state management.
- User Details (/user/:id):
- View user details.
- 404 Page (/invalid):
- Confirm the Not Found page appears.
- Login (/login):
Troubleshooting Common ErrorsTS2305: Module '"./app/app"' has no exported member 'App'Cause: Incorrect import in main.ts.Fix:
// src/main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter, provideHttpClient } from '@angular/router';
import { AppComponent } from './app/app.component';
import { routes } from './app/app.routes';
import { provideAnimations } from '@angular/platform-browser/animations';
bootstrapApplication(AppComponent, {
providers: [
provideRouter(routes),
provideHttpClient(),
provideAnimations()
]
}).catch(err => console.error(err));- Ensure AppComponent is exported in app.component.ts.
- Verify file paths.
- Run ng serve.
- Ensure AuthGuard is applied to the /admin route in app.routes.ts.
- Verify AuthService returns correct authentication state.
- Check isAdmin() logic in AuthService.
- Ensure JSON.stringify and JSON.parse are used in StorageService.
- Verify setLocalStorage and clearLocalStorage are called correctly.
Best Practices for Authentication & Guards
- Centralize Auth Logic: Use AuthService for all authentication tasks.
- Secure Storage: For production, use secure methods (e.g., JWT with HttpOnly cookies) instead of local storage.
- Granular Guards: Create separate guards for different roles or conditions.
- Clean Logout: Clear all relevant state and storage on logout.
- User Feedback: Show loading states and error messages for login failures.
- Accessibility: Ensure login forms are accessible with ARIA labels.
No comments:
Post a Comment
Thanks for your valuable comment...........
Md. Mominul Islam