Saturday, August 16, 2025
0 comments

Master Angular 20 HTTP & API Integration: A Beginner’s Guide to HttpClient, Observables, and REST API with a Hands-On FreeAPI Demo

 



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
  • 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).
Example: GET Request
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);
  }
}
Explanation:
  • 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.
Example: Subscribing to an Observable
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();
  }
}
Explanation:
  • 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.
Example
typescript
// Template
div: *ngIf="users$ | async as users; else loading":
  ul:
    li: *ngFor="let user of users": {{ user.userName }}
ng-template: #loading: Loading...
Explanation:
  • 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'));
      })
    );
  }
}
Explanation:
  • 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)
    );
  }
}
Explanation:
  • 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
Step 2: Configure provideHttpClientSince HttpClientModule is deprecated in Angular 20, use provideHttpClient in main.ts.
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));
Explanation:
  • provideHttpClient() replaces HttpClientModule for standalone components.
  • Add to providers in main.ts.
Step 3: Update app.routes.tsEnsure routes include all components:
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 }
];
Step 4: Update AppComponent
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; }
Step 5: Create User ModelDefine a TypeScript interface for users based on the FreeAPI response structure.
typescript
// src/app/models/user.model.ts
export interface User {
  id: number;
  userName: string;
  emailId: string;
  role: string;
}
Explanation:
  • Matches the API’s response fields (id, userName, emailId, role).
Step 6: Update UserServiceModify UserService to fetch users from the FreeAPI and handle local data.
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);
  }
}
Explanation:
  • 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.
Step 7: Update AdminComponentModify AdminComponent to fetch and display users from the API using the async pipe and add loading/error states.
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; }
Explanation:
  • users$: Holds the Observable from UserService.getUsers().
  • async pipe renders the user list.
  • isLoading and error manage UX states.
  • Integrates with FilterByRolePipe for filtering.
Step 8: Update UserComponent
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; }
Step 9: Update UserDetailsComponent
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; }
Step 10: Update RegisterComponentAdd API integration for user registration.
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; }
Step 11: Update LoginComponentKeep as is (no API integration needed for login).
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; }
Step 12: Update Child Components
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 {}
Step 13: Update NotFoundComponent
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 {}
Step 14: Update FilterByRolePipe
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);
  }
}
Step 15: Update no-spaces.validator.ts
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;
  };
}
Step 16: Test the Application
  1. Run ng serve and visit http://localhost:4200.
  2. 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.

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));
Steps:
  1. Ensure AppComponent is exported in app.component.ts.
  2. Verify file paths.
  3. Run ng serve.
NullInjectorError: No provider for HttpClientCause: Missing provideHttpClient() in main.ts.Fix:
  • Add provideHttpClient() to providers in main.ts.
CORS ErrorsCause: The FreeAPI may have CORS restrictions.Fix:
  • 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:

Featured Post

Master Angular 20 Basics: A Complete Beginner’s Guide with Examples and Best Practices

Welcome to the complete Angular 20 learning roadmap ! This series takes you step by step from basics to intermediate concepts , with hands...

Subscribe

 
Toggle Footer
Top