Introduction to Angular HttpClientModuleThe HttpClientModule in Angular provides the HttpClient service for making HTTP requests to REST APIs. It’s built on RxJS Observables, enabling asynchronous data handling with a reactive programming approach. In Angular 20, HttpClientModule is deprecated in favor of the provideHttpClient() function for standalone components, aligning with modern Angular architecture.Key Features
Making GET, POST, PUT, DELETE RequestsHttpClient supports standard HTTP methods for REST API interactions:Explanation:
Observables & RxJS BasicsObservables (from RxJS) are streams of data that emit values over time, ideal for handling asynchronous operations like HTTP requests. Unlike Promises, Observables can emit multiple values and support operators for data transformation.Key RxJS ConceptsExplanation:
Handling Async Data with Async PipeThe async pipe simplifies working with Observables in templates by:Explanation:
Error Handling with catchErrorThe catchError operator (from RxJS) handles HTTP errors gracefully, ensuring a good user experience.ExampleExplanation:
Loading Indicators & UX ImprovementsLoading indicators enhance UX by showing feedback during API calls. Combine with error handling for a polished experience.ExampleExplanation:
Hands-On Project: Fetch Users from FreeAPI and Display in Admin ListLet’s extend your angular-routing-demo project to fetch users from the freeapi.miniprojectideas.com API and display them in the AdminComponent list, integrating with your existing setup (components, routing, services, forms, and pipes).Step 1: Set Up the ProjectIf not already set up:Step 2: Configure provideHttpClientSince HttpClientModule is deprecated in Angular 20, use provideHttpClient in main.ts.Explanation:Step 4: Update AppComponentStep 5: Create User ModelDefine a TypeScript interface for users based on the FreeAPI response structure.Explanation:Explanation:Explanation:Step 9: Update UserDetailsComponentStep 10: Update RegisterComponentAdd API integration for user registration.Step 11: Update LoginComponentKeep as is (no API integration needed for login).Step 12: Update Child ComponentsStep 13: Update NotFoundComponentStep 14: Update FilterByRolePipeStep 15: Update no-spaces.validator.tsStep 16: Test the Application
Troubleshooting Common ErrorsTS2305: Module '"./app/app"' has no exported member 'App'Cause: Incorrect import in main.ts.Fix:Steps:
Best Practices for API Integration
- HttpClient: Simplifies HTTP requests (GET, POST, PUT, DELETE) with type-safe responses.
- Observables: Handle asynchronous data streams using RxJS.
- Interceptors: Allow request/response modification (e.g., adding headers).
- Error Handling: Built-in mechanisms for managing API failures.
- Type Safety: Leverages TypeScript for strongly typed responses.
Making GET, POST, PUT, DELETE RequestsHttpClient supports standard HTTP methods for REST API interactions:
- GET: Retrieve data (e.g., fetch users).
- POST: Create new resources (e.g., add a user).
- PUT: Update existing resources (e.g., update user details).
- DELETE: Remove resources (e.g., delete a user).
typescript
// src/app/services/data.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class DataService {
private apiUrl = 'https://freeapi.miniprojectideas.com/api/User/GetAllUsers';
constructor(private http: HttpClient) {}
getUsers(): Observable<any[]> {
return this.http.get<any[]>(this.apiUrl);
}
}
- Inject HttpClient into a service.
- Use http.get() to fetch data, returning an Observable.
- Type the response (any[]) for flexibility (replace with a specific interface for type safety).
Observables & RxJS BasicsObservables (from RxJS) are streams of data that emit values over time, ideal for handling asynchronous operations like HTTP requests. Unlike Promises, Observables can emit multiple values and support operators for data transformation.Key RxJS Concepts
- Observable: A data stream you subscribe to.
- Subscription: Activates the Observable to receive data.
- Operators: Transform data (e.g., map, catchError).
- Async Pipe: Automatically subscribes/unsubscribes in templates.
typescript
// src/app/components/example/example.component.ts
import { Component, OnInit } from '@angular/core';
import { DataService } from '../../services/data.service';
@Component({
selector: 'app-example',
template: `
ul: *ngFor="let user of users$ | async":
li: {{ user.userName }}
`,
standalone: true
})
export class ExampleComponent implements OnInit {
users$: Observable<any[]>;
constructor(private dataService: DataService) {}
ngOnInit() {
this.users$ = this.dataService.getUsers();
}
}
- users$ holds the Observable from the service.
- The async pipe in the template subscribes to users$ and renders the data.
Handling Async Data with Async PipeThe async pipe simplifies working with Observables in templates by:
- Automatically subscribing to the Observable.
- Rendering emitted values.
- Unsubscribing when the component is destroyed to prevent memory leaks.
typescript
// Template
div: *ngIf="users$ | async as users; else loading":
ul:
li: *ngFor="let user of users": {{ user.userName }}
ng-template: #loading: Loading...
- users$ | async as users: Assigns the emitted value to users.
- *ngIf with else: Shows a loading message until data arrives.
Error Handling with catchErrorThe catchError operator (from RxJS) handles HTTP errors gracefully, ensuring a good user experience.Example
typescript
// src/app/services/data.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class DataService {
private apiUrl = 'https://freeapi.miniprojectideas.com/api/User/GetAllUsers';
constructor(private http: HttpClient) {}
getUsers(): Observable<any[]> {
return this.http.get<any[]>(this.apiUrl).pipe(
catchError(error => {
console.error('Error fetching users:', error);
return throwError(() => new Error('Failed to fetch users'));
})
);
}
}
- catchError intercepts errors in the Observable stream.
- Logs the error and rethrows a user-friendly message.
- Use in components to display error messages to users.
Loading Indicators & UX ImprovementsLoading indicators enhance UX by showing feedback during API calls. Combine with error handling for a polished experience.Example
typescript
// src/app/components/example/example.component.ts
import { Component, OnInit } from '@angular/core';
import { Observable, of } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { DataService } from '../../services/data.service';
@Component({
selector: 'app-example',
template: `
div: *ngIf="error": class="error": {{ error }}
div: *ngIf="isLoading": class="loading": Loading...
ul: *ngIf="users$ | async as users; else loading":
li: *ngFor="let user of users": {{ user.userName }}
ng-template: #loading: Loading...
`,
styles: [`
.loading { text-align: center; color: #007bff; }
.error { text-align: center; color: red; }
`],
standalone: true
})
export class ExampleComponent implements OnInit {
users$: Observable<any[]>;
isLoading = false;
error: string | null = null;
constructor(private dataService: DataService) {}
ngOnInit() {
this.isLoading = true;
this.users$ = this.dataService.getUsers().pipe(
catchError(err => {
this.error = err.message;
this.isLoading = false;
return of([]);
}),
finalize(() => this.isLoading = false)
);
}
}
- isLoading: Toggles the loading indicator.
- error: Displays error messages.
- finalize: Ensures isLoading is set to false after the request completes.
Hands-On Project: Fetch Users from FreeAPI and Display in Admin ListLet’s extend your angular-routing-demo project to fetch users from the freeapi.miniprojectideas.com API and display them in the AdminComponent list, integrating with your existing setup (components, routing, services, forms, and pipes).Step 1: Set Up the ProjectIf not already set up:
bash
ng new angular-routing-demo
cd angular-routing-demo
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 pipe pipes/filter-by-role
ng g service services/user
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';
bootstrapApplication(AppComponent, {
providers: [provideRouter(routes), provideHttpClient()]
}).catch(err => console.error(err));
- provideHttpClient() replaces HttpClientModule for standalone components.
- Add to providers in main.ts.
typescript
// 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';
export const routes: Routes = [
{ path: 'user', component: UserComponent },
{ path: 'user/:id', component: UserDetailsComponent },
{ path: 'register', component: RegisterComponent },
{ path: 'login', component: LoginComponent },
{
path: 'admin',
component: AdminComponent,
children: [
{ path: 'dashboard', component: AdminDashboardComponent },
{ path: 'settings', component: AdminSettingsComponent },
{ path: '', redirectTo: 'dashboard', pathMatch: 'full' }
]
},
{ path: '', redirectTo: '/user', pathMatch: 'full' },
{ path: '**', component: NotFoundComponent }
];
typescript
// src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { Router, NavigationStart, NavigationEnd, RouterOutlet, RouterLink } from '@angular/router';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
standalone: true,
imports: [RouterOutlet, RouterLink]
})
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);
}
});
}
}
typescript
// src/app/app.component.html
nav:
a: routerLink="/user" routerLinkActive="active": User Dashboard
a: routerLink="/register" routerLinkActive="active": Register
a: routerLink="/login" routerLinkActive="active": Login
a: routerLink="/admin" routerLinkActive="active": Admin Panel
router-outlet
css
// src/app/app.component.css
nav { margin: 20px; }
a { margin-right: 10px; text-decoration: none; }
.active { font-weight: bold; color: #007bff; }
typescript
// src/app/models/user.model.ts
export interface User {
id: number;
userName: string;
emailId: string;
role: string;
}
- Matches the API’s response fields (id, userName, emailId, role).
typescript
// src/app/services/user.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of, throwError } 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); // Fallback to local data
})
);
}
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); // Fallback to local storage
return of(user);
})
);
}
updateUser(id: number, user: User): Observable<any> {
return this.http.put(`${this.apiUrl}/${id}`, user).pipe(
catchError(error => {
console.error('Error updating user:', error);
const index = this.localUsers.findIndex(u => u.id === id);
if (index !== -1) {
this.localUsers[index] = user;
}
return of(user);
})
);
}
deleteUser(id: number): Observable<any> {
return this.http.delete(`${this.apiUrl}/${id}`).pipe(
catchError(error => {
console.error('Error deleting user:', error);
this.localUsers = this.localUsers.filter(u => u.id !== id);
return of({});
})
);
}
validateAdmin(username: string, password: string): boolean {
return this.admins.some(admin => admin.username === username && admin.password === password);
}
}
- Uses HttpClient to make GET, POST, PUT, DELETE requests to the FreeAPI.
- Falls back to localUsers on API errors for robustness.
- catchError handles errors, returning local data or an empty response.
typescript
// src/app/components/admin/admin.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { RouterOutlet, RouterLink } 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 { User } from '../../models/user.model';
@Component({
selector: 'app-admin',
templateUrl: './admin.component.html',
styleUrls: ['./admin.component.css'],
standalone: true,
imports: [FormsModule, RouterOutlet, RouterLink, FilterByRolePipe]
})
export class AdminComponent implements OnInit, OnDestroy {
users$: Observable<User[]>;
filterRole = 'admin';
isHighlighted = false;
isLoading = false;
error: string | null = null;
constructor(private userService: UserService) {}
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);
}
}
typescript
// src/app/components/admin/admin.component.html
h2: Admin Panel
nav:
a: routerLink="dashboard" routerLinkActive="active": Dashboard
a: routerLink="settings" routerLinkActive="active": Settings
router-outlet
h3: Admin Users List
div: *ngIf="error" class="error": {{ error }}
div: *ngIf="isLoading" class="loading": Loading users...
div: *ngIf="users$ | async as users; else loading":
div:
label: Filter by Role:
select: [(ngModel)]="filterRole" (change)="setFilterRole(filterRole)":
option: value="all": All
option: value="admin": Admin
option: value="user": User
button: (click)="toggleHighlight()": Toggle Highlight
ul:
li: *ngFor="let user of users | filterByRole:filterRole" [ngClass]="{'highlight': isHighlighted && user.role === 'admin'}":
{{ user.userName | uppercase }} ({{ user.role }}) - {{ user.emailId }}
input: #searchInput placeholder="Search users"
button: (click)="logSearch(searchInput.value)": Search
ng-template: #loading: Loading users...
css
// src/app/components/admin/admin.component.css
ul { list-style-type: none; padding: 0; }
li { padding: 10px; margin: 5px 0; border: 1px solid #ccc; }
.highlight { background-color: #e0f7fa; }
nav { margin: 10px 0; }
a { margin-right: 10px; text-decoration: none; }
.active { font-weight: bold; color: #007bff; }
.loading { text-align: center; color: #007bff; }
.error { text-align: center; color: red; }
- users$: Holds the Observable from UserService.getUsers().
- async pipe renders the user list.
- isLoading and error manage UX states.
- Integrates with FilterByRolePipe for filtering.
typescript
// 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 { User } from '../../models/user.model';
@Component({
selector: 'app-user',
templateUrl: './user.component.html',
styleUrls: ['./user.component.css'],
standalone: true,
imports: [FormsModule, RouterLink]
})
export class UserComponent implements OnInit {
searchQuery = '';
users$: Observable<User[]>;
isLoading = false;
error: string | null = null;
constructor(
private route: ActivatedRoute,
private router: Router,
private userService: UserService
) {}
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 } });
}
}
typescript
// src/app/components/user/user.component.html
h2: User Dashboard
div: *ngIf="error" class="error": {{ error }}
div: *ngIf="isLoading" class="loading": Loading users...
div:
label: Search users:
input: [(ngModel)]="searchQuery" placeholder="Search users"
button: (click)="updateQuery()": Search
p: Search Query: {{ searchQuery }}
ul: *ngIf="users$ | async as users; else loading":
li: *ngFor="let user of users":
a: routerLink="/user/{{ user.id }}": {{ user.userName }} ({{ user.role }}) - {{ user.emailId }}
ng-template: #loading: Loading users...
css
// src/app/components/user/user.component.css
ul { list-style-type: none; padding: 0; }
li { padding: 10px; margin: 5px 0; border: 1px solid #ccc; }
.loading { text-align: center; color: #007bff; }
.error { text-align: center; color: red; }
typescript
// 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';
@Component({
selector: 'app-user-details',
templateUrl: './user-details.component.html',
styleUrls: ['./user-details.component.css'],
standalone: true,
imports: [RouterLink]
})
export class UserDetailsComponent implements OnInit {
user$: Observable<User | undefined>;
isLoading = false;
error: string | null = null;
constructor(
private route: ActivatedRoute,
private userService: UserService
) {}
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)
);
});
}
}
typescript
// src/app/components/user-details/user-details.component.html
h2: User Details
div: *ngIf="error" class="error": {{ error }}
div: *ngIf="isLoading" class="loading": Loading user...
div: *ngIf="user$ | async as user; else noUser":
p: Name: {{ user.userName }}
p: Email: {{ user.emailId }}
p: Role: {{ user.role | uppercase }}
p: else noUser: User not found
p: Back to User Dashboard
css
// src/app/components/user-details/user-details.component.css
.loading { text-align: center; color: #007bff; }
.error { text-align: center; color: red; }
typescript
// 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 { Observable } from 'rxjs';
@Component({
selector: 'app-register',
templateUrl: './register.component.html',
styleUrls: ['./register.component.css'],
standalone: true,
imports: [ReactiveFormsModule, RouterLink]
})
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: this.userService.getUsers().length + 1,
userName: formValue.userName || '',
emailId: formValue.emailId || '',
role: formValue.role || 'user'
};
this.userService.addUser(newUser).subscribe({
next: () => {
this.registerForm.reset();
this.router.navigate(['/user']);
},
error: err => {
this.error = 'Failed to register user: ' + err.message;
this.isLoading = false;
},
complete: () => {
this.isLoading = false;
}
});
}
}
}
typescript
// src/app/components/register/register.component.html
h2: User Registration
div: *ngIf="error" class="error": {{ error }}
div: *ngIf="isLoading" class="loading": Registering user...
form: [formGroup]="registerForm" (ngSubmit)="onSubmit()":
div:
label: Username:
input: formControlName="userName"
div: *ngIf="registerForm.get('userName')?.hasError('required') && registerForm.get('userName')?.touched":
Username is required
div: *ngIf="registerForm.get('userName')?.hasError('minlength') && registerForm.get('userName')?.touched":
Username must be at least 3 characters
div: *ngIf="registerForm.get('userName')?.hasError('noSpaces') && registerForm.get('userName')?.touched":
Username cannot contain spaces
div:
label: Email:
input: formControlName="emailId"
div: *ngIf="registerForm.get('emailId')?.hasError('required') && registerForm.get('emailId')?.touched":
Email is required
div: *ngIf="registerForm.get('emailId')?.hasError('email') && registerForm.get('emailId')?.touched":
Invalid email format
div:
label: Role:
select: formControlName="role":
option: value="user": User
option: value="admin": Admin
button: type="submit" [disabled]="registerForm.invalid || isLoading": Register
p: Back to User Dashboard
css
// src/app/components/register/register.component.css
form { margin: 20px; padding: 20px; border: 1px solid #ccc; }
input, select { padding: 5px; margin: 5px; width: 200px; }
div { margin-bottom: 10px; }
.error { color: red; font-size: 0.9em; }
.loading { text-align: center; color: #007bff; }
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 { UserService } from '../../services/user.service';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css'],
standalone: true,
imports: [FormsModule, RouterLink]
})
export class LoginComponent {
username = '';
password = '';
errorMessage = '';
constructor(private userService: UserService, private router: Router) {}
onSubmit() {
if (this.userService.validateAdmin(this.username, this.password)) {
this.router.navigate(['/admin']);
} else {
this.errorMessage = 'Invalid username or password';
}
}
}
typescript
// src/app/components/login/login.component.html
h2: Admin Login
form: (ngSubmit)="onSubmit()":
div:
label: Username:
input: [(ngModel)]="username" name="username" required
div: *ngIf="form.username?.errors?.['required'] && form.username?.touched": Username is required
div:
label: Password:
input: [(ngModel)]="password" name="password" type="password" required
div: *ngIf="form.password?.errors?.['required'] && form.password?.touched": Password is required
div: *ngIf="errorMessage" class="error": {{ errorMessage }}
button: type="submit" [disabled]="!form.valid": Login
p: Back to User Dashboard
css
// src/app/components/login/login.component.css
form { margin: 20px; padding: 20px; border: 1px solid #ccc; }
input { padding: 5px; margin: 5px; width: 200px; }
div { margin-bottom: 10px; }
.error { color: red; font-size: 0.9em; }
typescript
// src/app/components/admin-dashboard/admin-dashboard.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-admin-dashboard',
template: `
h3: Admin Dashboard
p: Welcome to the Admin Dashboard!
`,
standalone: true
})
export class AdminDashboardComponent {}
typescript
// src/app/components/admin-settings/admin-settings.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-admin-settings',
template: `
h3: Admin Settings
p: Configure settings here.
`,
standalone: true
})
export class AdminSettingsComponent {}
typescript
// src/app/components/not-found/not-found.component.ts
import { Component } from '@angular/core';
import { RouterLink } from '@angular/router';
@Component({
selector: 'app-not-found',
template: `
h2: 404 - Page Not Found
p: Go back to User Dashboard
`,
standalone: true,
imports: [RouterLink]
})
export class NotFoundComponent {}
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/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:
- Admin Panel (/admin):
- Verify users are fetched from freeapi.miniprojectideas.com and displayed.
- Check loading indicator and error messages (simulate by changing apiUrl to an invalid endpoint).
- Filter users by role and toggle highlighting.
- Register (/register):
- Submit a new user and verify it appears in the User and Admin lists.
- Test error handling for invalid inputs or API failures.
- User Dashboard (/user):
- View fetched users and navigate to details.
- User Details (/user/:id):
- View user details or “User not found”.
- Login (/login):
- Log in with admin1/admin123 to access /admin.
- 404 Page (/invalid):
- Confirm the Not Found page appears.
- Admin Panel (/admin):
Troubleshooting Common ErrorsTS2305: Module '"./app/app"' has no exported member 'App'Cause: Incorrect import in main.ts.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';
bootstrapApplication(AppComponent, {
providers: [provideRouter(routes), provideHttpClient()]
}).catch(err => console.error(err));
- Ensure AppComponent is exported in app.component.ts.
- Verify file paths.
- Run ng serve.
- Add provideHttpClient() to providers in main.ts.
- Since freeapi.miniprojectideas.com allows CORS, this shouldn’t occur. If it does, use a proxy in development:json
// angular.json "serve": { "options": { "proxyConfig": "proxy.conf.json" } }
json// proxy.conf.json { "/api": { "target": "https://freeapi.miniprojectideas.com", "secure": true, "changeOrigin": true, "pathRewrite": { "^/api": "" } } }
- Update UserService to use /api as the base URL.
Best Practices for API Integration
- Use Services: Encapsulate API logic in services for modularity.
- Type Safety: Define interfaces for API responses (e.g., User model).
- Error Handling: Use catchError and display user-friendly messages.
- Loading States: Show indicators during API calls.
- Async Pipe: Prefer over manual subscriptions to avoid memory leaks.
- Security: Use HTTPS and handle authentication properly (not implemented here due to FreeAPI limitations).
0 comments:
Post a Comment