What You’ll Build:
Project SetupPrerequisites
Source CodeBelow is the complete source code for the user-admin-dashboard project, organized by file. The app uses JSONPlaceholder (https://jsonplaceholder.typicode.com/users) for user data, Angular Material for UI, and local storage for authentication.1. Environment Files2. Models3. ServicesAuthServiceUserServiceStorageService4. GuardsAuthGuard5. Pipes6. App Routing7. Main Entry Point8. App Component9. Reusable ComponentsHeaderComponentFooterComponentSidebarComponent10. Feature ComponentsLoginComponentUserDashboardComponentAdminDashboardComponentUserListComponentUserFormComponentNotFoundComponent11. Global Styles
Implementation DetailsLogin System
DeploymentBuildLocal Server
Testing the Application
TroubleshootingTS2305: Module '"./app/app"' has no exported member 'App'Fix:CORS Issues with JSONPlaceholder
Best Practices
- Login System: Authenticate users with a simple username/password form.
- User CRUD: Create, read, update, and delete users via JSONPlaceholder.
- Role-Based Routing: Restrict /admin to authenticated admin users.
- API Integration: Fetch and manage user data from JSONPlaceholder.
- Reusable Components: Header, footer, and sidebar for consistent UI.
- Lifecycle Hooks, Forms, Services: Implement robust application logic.
Project SetupPrerequisites
- Node.js (v18+ recommended)
- Angular CLI (npm install -g @angular/cli)
- Code editor (e.g., VS Code)
- Basic understanding of Angular concepts from previous modules
bash
ng new user-admin-dashboard --style=scss --routing
cd user-admin-dashboard
ng add @angular/material
- Select the Indigo/Pink theme.
- Enable typography and animations.
bash
ng g component components/login
ng g component components/user-dashboard
ng g component components/admin-dashboard
ng g component components/user-list
ng g component components/user-form
ng g component components/header
ng g component components/footer
ng g component components/sidebar
ng g component components/not-found
ng g service services/auth
ng g service services/user
ng g service services/storage
ng g guard guards/auth
ng g pipe pipes/filter-by-role
- Choose CanActivate for AuthGuard.
bash
npm install
Source CodeBelow is the complete source code for the user-admin-dashboard project, organized by file. The app uses JSONPlaceholder (https://jsonplaceholder.typicode.com/users) for user data, Angular Material for UI, and local storage for authentication.1. Environment Files
typescript
// src/environments/environment.ts (Development)
export const environment = {
production: false,
apiUrl: 'https://jsonplaceholder.typicode.com/users'
};
typescript
// src/environments/environment.prod.ts (Production)
export const environment = {
production: true,
apiUrl: 'https://jsonplaceholder.typicode.com/users'
};
typescript
// src/app/models/user.model.ts
export interface User {
id: number;
name: string;
email: string;
role: string;
}
typescript
// 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';
@Injectable({
providedIn: 'root'
})
export class AuthService {
private currentUserSubject = new BehaviorSubject<User | null>(null);
currentUser$ = this.currentUserSubject.asObservable();
constructor(private storageService: StorageService) {
const storedUser = this.storageService.getLocalStorage('currentUser');
if (storedUser) {
this.currentUserSubject.next(storedUser);
}
}
login(username: string, password: string): boolean {
// Mock admin credentials (replace with API call in production)
const admins = [
{ username: 'admin1', password: 'admin123' },
{ username: 'admin2', password: 'admin456' }
];
const isValid = admins.some(admin => admin.username === username && admin.password === password);
if (isValid) {
const user: User = {
id: 0,
name: username,
email: `${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';
}
}
typescript
// src/app/services/user.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { User } from '../models/user.model';
import { environment } from '../../environments/environment';
@Injectable({
providedIn: 'root'
})
export class UserService {
private apiUrl = environment.apiUrl;
constructor(private http: HttpClient) {}
getUsers(): Observable<User[]> {
return this.http.get<any[]>(this.apiUrl).pipe(
map(users => users.map(user => ({
id: user.id,
name: user.name,
email: user.email,
role: user.id % 2 === 0 ? 'user' : 'admin' // Mock role assignment
}))),
catchError(error => {
console.error('Error fetching users:', error);
return of([]);
})
);
}
getUserById(id: number): Observable<User | undefined> {
return this.http.get<any>(`${this.apiUrl}/${id}`).pipe(
map(user => ({
id: user.id,
name: user.name,
email: user.email,
role: user.id % 2 === 0 ? 'user' : 'admin'
})),
catchError(error => {
console.error('Error fetching user:', error);
return of(undefined);
})
);
}
addUser(user: User): Observable<User> {
return this.http.post<User>(this.apiUrl, user).pipe(
catchError(error => {
console.error('Error adding user:', error);
return of(user); // Simulate success for JSONPlaceholder
})
);
}
updateUser(user: User): Observable<User> {
return this.http.put<User>(`${this.apiUrl}/${user.id}`, user).pipe(
catchError(error => {
console.error('Error updating user:', error);
return of(user); // Simulate success
})
);
}
deleteUser(id: number): Observable<void> {
return this.http.delete<void>(`${this.apiUrl}/${id}`).pipe(
catchError(error => {
console.error('Error deleting user:', error);
return of(undefined);
})
);
}
}
typescript
// 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);
}
}
typescript
// 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;
}
}
typescript
// 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);
}
}
typescript
// src/app/app.routes.ts
import { Routes } from '@angular/router';
import { LoginComponent } from './components/login/login.component';
import { UserDashboardComponent } from './components/user-dashboard/user-dashboard.component';
import { AdminDashboardComponent } from './components/admin-dashboard/admin-dashboard.component';
import { NotFoundComponent } from './components/not-found/not-found.component';
import { AuthGuard } from './guards/auth.guard';
export const routes: Routes = [
{ path: 'login', component: LoginComponent },
{ path: 'user', component: UserDashboardComponent },
{ path: 'admin', component: AdminDashboardComponent, canActivate: [AuthGuard] },
{ path: '', redirectTo: '/user', pathMatch: 'full' },
{ path: '**', component: NotFoundComponent }
];
typescript
// 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));
typescript
// src/app/app.component.ts
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { HeaderComponent } from './components/header/header.component';
import { FooterComponent } from './components/footer/footer.component';
import { SidebarComponent } from './components/sidebar/sidebar.component';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
standalone: true,
imports: [RouterOutlet, HeaderComponent, FooterComponent, SidebarComponent]
})
export class AppComponent {}
typescript
// src/app/app.component.html
<app-header></app-header>
<div class="container">
<app-sidebar></app-sidebar>
<main>
<router-outlet></router-outlet>
</main>
</div>
<app-footer></app-footer>
scss
// src/app/app.component.scss
.container {
display: flex;
min-height: calc(100vh - 128px);
}
main {
flex: 1;
padding: 20px;
}
typescript
// 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>User/Admin Dashboard</span>
<nav>
<a mat-button routerLink="/user" routerLinkActive="active">User Dashboard</a>
<a mat-button *ngIf="!(currentUser$ | async)" routerLink="/login" routerLinkActive="active">Login</a>
<span *ngIf="currentUser$ | async as user">Welcome, {{ user.name }}</span>
<button mat-button *ngIf="currentUser$ | async" (click)="logout()">Logout</button>
<a mat-button routerLink="/admin" routerLinkActive="active">Admin Dashboard</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']);
}
}
scss
// 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;
}
}
}
typescript
// 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 User/Admin Dashboard</span>
</mat-toolbar>
`,
styleUrls: ['./footer.component.scss'],
standalone: true,
imports: [MatToolbarModule]
})
export class FooterComponent {}
scss
// src/app/components/footer/footer.component.scss
mat-toolbar {
justify-content: center;
padding: 10px 0;
}
typescript
// src/app/components/sidebar/sidebar.component.ts
import { Component } from '@angular/core';
import { RouterLink, RouterLinkActive } from '@angular/router';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatListModule } from '@angular/material/list';
import { AuthService } from '../../services/auth.service';
@Component({
selector: 'app-sidebar',
template: `
<mat-sidenav-container>
<mat-sidenav mode="side" opened>
<mat-nav-list>
<a mat-list-item routerLink="/user" routerLinkActive="active">User Dashboard</a>
<a mat-list-item routerLink="/admin" routerLinkActive="active" *ngIf="authService.isAdmin()">Admin Dashboard</a>
<a mat-list-item routerLink="/login" routerLinkActive="active" *ngIf="!authService.isAuthenticated()">Login</a>
</mat-nav-list>
</mat-sidenav>
</mat-sidenav-container>
`,
styleUrls: ['./sidebar.component.scss'],
standalone: true,
imports: [RouterLink, RouterLinkActive, MatSidenavModule, MatListModule]
})
export class SidebarComponent {
constructor(public authService: AuthService) {}
}
scss
// src/app/components/sidebar/sidebar.component.scss
mat-sidenav {
width: 200px;
background-color: #f5f5f5;
padding: 20px;
mat-nav-list {
a {
color: #333;
&.active {
font-weight: bold;
background-color: #e0f7fa;
}
}
}
}
typescript
// 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';
}
}
}
typescript
// 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>
scss
// src/app/components/login/login.component.scss
.container {
margin: 20px;
max-width: 400px;
}
form {
display: flex;
flex-direction: column;
gap: 10px;
}
mat-form-field {
width: 100%;
}
.error {
color: red;
font-size: 0.9em;
}
typescript
// src/app/components/user-dashboard/user-dashboard.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { 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';
import { MatListModule } from '@angular/material/list';
import { UserListComponent } from '../user-list/user-list.component';
@Component({
selector: 'app-user-dashboard',
template: `
<mat-card class="container">
<mat-card-header>
<mat-card-title>User Dashboard</mat-card-title>
</mat-card-header>
<mat-card-content>
<app-user-list [users]="users$ | async"></app-user-list>
<div *ngIf="isLoading" class="loading">Loading users...</div>
<div *ngIf="error" class="error">{{ error }}</div>
</mat-card-content>
</mat-card>
`,
styleUrls: ['./user-dashboard.component.scss'],
standalone: true,
imports: [RouterLink, MatCardModule, MatButtonModule, MatListModule, UserListComponent]
})
export class UserDashboardComponent implements OnInit, OnDestroy {
users$: Observable<User[]>;
isLoading = false;
error: string | null = null;
constructor(private userService: UserService) {
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('UserDashboardComponent: Destroyed');
}
}
scss
// src/app/components/user-dashboard/user-dashboard.component.scss
.container {
margin: 20px;
}
.loading {
text-align: center;
color: #3f51b5;
}
.error {
text-align: center;
color: red;
}
typescript
// src/app/components/admin-dashboard/admin-dashboard.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { 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';
import { MatSelectModule } from '@angular/material/select';
import { MatFormFieldModule } from '@angular/material/form-field';
import { UserListComponent } from '../user-list/user-list.component';
import { UserFormComponent } from '../user-form/user-form.component';
import { FilterByRolePipe } from '../../pipes/filter-by-role.pipe';
import { FormsModule } from '@angular/forms';
@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>
<h3>Add New User</h3>
<app-user-form (userSaved)="addUser($event)"></app-user-form>
<h3>User List</h3>
<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>
<app-user-list [users]="(users$ | async | filterByRole:filterRole)" (editUser)="editUser($event)" (deleteUser)="deleteUser($event)"></app-user-list>
<div *ngIf="isLoading" class="loading">Loading users...</div>
<div *ngIf="error" class="error">{{ error }}</div>
</mat-card-content>
</mat-card>
`,
styleUrls: ['./admin-dashboard.component.scss'],
standalone: true,
imports: [
RouterLink,
MatCardModule,
MatButtonModule,
MatSelectModule,
MatFormFieldModule,
UserListComponent,
UserFormComponent,
FilterByRolePipe,
FormsModule
]
})
export class AdminDashboardComponent implements OnInit, OnDestroy {
users$: Observable<User[]>;
filterRole = 'all';
isLoading = false;
error: string | null = null;
constructor(private userService: UserService) {
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('AdminDashboardComponent: Destroyed');
}
setFilterRole(role: string) {
this.filterRole = role;
}
addUser(user: User) {
this.isLoading = true;
this.userService.addUser(user).subscribe({
next: () => {
this.users$ = this.userService.getUsers();
},
error: err => {
this.error = 'Failed to add user: ' + err.message;
this.isLoading = false;
},
complete: () => this.isLoading = false
});
}
editUser(user: User) {
this.isLoading = true;
this.userService.updateUser(user).subscribe({
next: () => {
this.users$ = this.userService.getUsers();
},
error: err => {
this.error = 'Failed to update user: ' + err.message;
this.isLoading = false;
},
complete: () => this.isLoading = false
});
}
deleteUser(id: number) {
this.isLoading = true;
this.userService.deleteUser(id).subscribe({
next: () => {
this.users$ = this.userService.getUsers();
},
error: err => {
this.error = 'Failed to delete user: ' + err.message;
this.isLoading = false;
},
complete: () => this.isLoading = false
});
}
}
scss
// src/app/components/admin-dashboard/admin-dashboard.component.scss
.container {
margin: 20px;
}
mat-form-field {
margin-bottom: 20px;
}
.loading {
text-align: center;
color: #3f51b5;
}
.error {
text-align: center;
color: red;
}
typescript
// src/app/components/user-list/user-list.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { User } from '../../models/user.model';
import { MatListModule } from '@angular/material/list';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { AuthService } from '../../services/auth.service';
@Component({
selector: 'app-user-list',
template: `
<mat-list>
<mat-list-item *ngFor="let user of users">
<span>{{ user.name }} ({{ user.role }}) - {{ user.email }}</span>
<button mat-raised-button color="accent" *ngIf="authService.isAdmin()" (click)="editUser.emit(user)">Edit</button>
<button mat-raised-button color="warn" *ngIf="authService.isAdmin()" (click)="deleteUser.emit(user.id)">Delete</button>
</mat-list-item>
</mat-list>
`,
styleUrls: ['./user-list.component.scss'],
standalone: true,
imports: [MatListModule, MatButtonModule, MatCardModule]
})
export class UserListComponent {
@Input() users: User[] = [];
@Output() editUser = new EventEmitter<User>();
@Output() deleteUser = new EventEmitter<number>();
constructor(public authService: AuthService) {}
}
scss
// src/app/components/user-list/user-list.component.scss
mat-list-item {
margin: 5px 0;
span {
flex: 1;
}
button {
margin-left: 10px;
}
}
typescript
// src/app/components/user-form/user-form.component.ts
import { Component, EventEmitter, Output } from '@angular/core';
import { ReactiveFormsModule, FormGroup, FormControl, Validators } from '@angular/forms';
import { User } from '../../models/user.model';
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-user-form',
template: `
<mat-card>
<mat-card-content>
<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
<mat-form-field appearance="fill">
<mat-label>Name</mat-label>
<input matInput formControlName="name">
<mat-error *ngIf="userForm.get('name')?.hasError('required') && userForm.get('name')?.touched">
Name is required
</mat-error>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Email</mat-label>
<input matInput formControlName="email">
<mat-error *ngIf="userForm.get('email')?.hasError('required') && userForm.get('email')?.touched">
Email is required
</mat-error>
<mat-error *ngIf="userForm.get('email')?.hasError('email') && userForm.get('email')?.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]="userForm.invalid">Save</button>
</form>
</mat-card-content>
</mat-card>
`,
styleUrls: ['./user-form.component.scss'],
standalone: true,
imports: [
ReactiveFormsModule,
MatCardModule,
MatButtonModule,
MatFormFieldModule,
MatInputModule,
MatSelectModule
]
})
export class UserFormComponent {
@Output() userSaved = new EventEmitter<User>();
userForm = new FormGroup({
id: new FormControl(0),
name: new FormControl('', [Validators.required]),
email: new FormControl('', [Validators.required, Validators.email]),
role: new FormControl('user', [Validators.required])
});
onSubmit() {
if (this.userForm.valid) {
const user: User = {
id: this.userForm.value.id || Math.floor(Math.random() * 1000), // Simulate new ID
name: this.userForm.value.name || '',
email: this.userForm.value.email || '',
role: this.userForm.value.role || 'user'
};
this.userSaved.emit(user);
this.userForm.reset({ id: 0, name: '', email: '', role: 'user' });
}
}
}
scss
// src/app/components/user-form/user-form.component.scss
mat-card {
margin: 20px 0;
padding: 10px;
}
form {
display: flex;
flex-direction: column;
gap: 10px;
}
mat-form-field {
width: 100%;
max-width: 400px;
}
typescript
// 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 {}
scss
// src/app/components/not-found/not-found.component.scss
.container {
margin: 20px;
text-align: center;
}
scss
// 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);
}
Implementation DetailsLogin System
- Component: LoginComponent uses a template-driven form to authenticate users.
- Service: AuthService validates credentials and stores the user in local storage.
- Behavior: Successful login redirects to /admin; failures show an error.
- Components:
- UserListComponent: Displays users with edit/delete buttons for admins.
- UserFormComponent: Handles add/edit forms for users.
- Service: UserService interacts with JSONPlaceholder for CRUD operations.
- Behavior:
- Create: UserFormComponent emits new user data to AdminDashboardComponent.
- Read: UserService fetches users for both dashboards.
- Update: Edit buttons trigger form population and updates via UserService.
- Delete: Delete buttons trigger UserService.deleteUser.
- Guard: AuthGuard restricts /admin to authenticated admins.
- Behavior: Unauthenticated users or non-admins are redirected to /login.
- Endpoint: JSONPlaceholder (https://jsonplaceholder.typicode.com/users).
- Service: UserService maps API responses to the User model and handles errors.
- Behavior: Fetches users, supports CRUD operations (simulated for POST/PUT/DELETE due to JSONPlaceholder’s mock nature).
- Header: Displays navigation and login/logout controls.
- Footer: Shows copyright information.
- Sidebar: Provides navigation links, conditionally showing admin routes.
- Behavior: Consistent UI across routes, responsive design with Angular Material.
- OnInit: Initializes data fetching in UserDashboardComponent and AdminDashboardComponent.
- OnDestroy: Logs component destruction for debugging.
- Template-Driven: Used in LoginComponent for simplicity.
- Reactive: Used in UserFormComponent for robust validation.
- AuthService: Manages authentication state.
- UserService: Handles API interactions.
- StorageService: Abstracts local storage operations.
DeploymentBuild
bash
ng build --prod
- Install http-server:bash
npm install -g http-server
- Serve:bash
cd dist/user-admin-dashboard http-server -p 8080
- Visit http://localhost:8080.
- Install angular-cli-ghpages:bash
npm install -g angular-cli-ghpages
- Build:bash
ng build --prod --base-href /user-admin-dashboard/
- Deploy:bash
ngh --dir=dist/user-admin-dashboard
- Enable GitHub Pages in the repository settings.
- Install Firebase CLI:bash
npm install -g firebase-tools
- Initialize:bash
firebase init hosting
- Build and deploy:bash
ng build --prod firebase deploy
Testing the Application
- Run ng serve and visit http://localhost:4200.
- Test features:
- Login (/login): Log in with admin1/admin123. Verify redirection to /admin.
- Admin Dashboard (/admin): Access restricted to admins. Add, edit, delete users.
- User Dashboard (/user): View user list (no edit/delete for non-admins).
- Logout (Header): Log out and verify redirection to /user.
- Sidebar: Confirm admin routes are hidden for non-admins.
- API: Verify user data loads from JSONPlaceholder.
- 404 (/invalid): Check the Not Found page.
TroubleshootingTS2305: Module '"./app/app"' has no exported member 'App'Fix:
typescript
// 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));
- JSONPlaceholder supports CORS, but if issues arise, use a proxy during development:json
// angular.json "serve": { "options": { "proxyConfig": "proxy.conf.json" } }
json// proxy.conf.json { "/users": { "target": "https://jsonplaceholder.typicode.com", "secure": true, "changeOrigin": true } }
- Ensure baseHref is set correctly for GitHub Pages.
- Verify Firebase rewrites in firebase.json for SPA routing.
Best Practices
- Environment Files: Use environment.ts for API URLs.
- Services: Centralize API and auth logic.
- Guards: Restrict routes based on roles.
- Reusable Components: Keep header, footer, and sidebar consistent.
- Forms: Use reactive forms for complex validation.
- Lifecycle Hooks: Clean up subscriptions in ngOnDestroy.
- Deployment: Test locally before deploying.
0 comments:
Post a Comment