Exemples de Framework Angular

Exemples essentiels d'Angular pour le développement web d'entreprise avec TypeScript, composants et modules

Key Facts

Category
Web Frameworks
Items
3
Format Families
sample

Sample Overview

Exemples essentiels d'Angular pour le développement web d'entreprise avec TypeScript, composants et modules This sample set belongs to Web Frameworks and can be used to test related workflows inside Elysia Tools.

💻 Hello World Angular typescript

🟢 simple ⭐⭐

Composants de base d'Angular, TypeScript et concepts fondamentaux

⏱️ 30 min 🏷️ angular, typescript, components, di
Prerequisites: TypeScript, Basic Angular concepts, Object-oriented programming
// Angular Hello World Examples

// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}

// app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <div class="app-container">
      <h1>{{ title }}</h1>
      <p>{{ description }}</p>
      <button (click)="updateMessage()">Update Message</button>
      <p>Click count: {{ clickCount }}</p>
    </div>
  `,
  styles: [`
    .app-container {
      max-width: 600px;
      margin: 0 auto;
      padding: 20px;
      text-align: center;
      font-family: Arial, sans-serif;
    }
    h1 {
      color: #3f51b5;
    }
    button {
      margin: 10px;
      padding: 10px 20px;
      background-color: #3f51b5;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
    button:hover {
      background-color: #303f9f;
    }
  `]
})
export class AppComponent {
  title = 'Hello, Angular!';
  description = 'Welcome to Angular framework';
  clickCount = 0;

  updateMessage() {
    this.clickCount++;
    this.title = `Clicked ${this.clickCount} times!`;
  }
}

// 2. Component with Inputs and Outputs
import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-greeting',
  template: `
    <div class="greeting">
      <h2>{{ greeting }}, {{ name }}!</h2>
      <p>{{ message }}</p>
      <button (click)="sayHello()">Say Hello</button>
    </div>
  `,
  styles: [`
    .greeting {
      padding: 15px;
      border: 1px solid #ddd;
      border-radius: 8px;
      margin: 10px 0;
    }
  `]
})
export class GreetingComponent {
  @Input() name: string = 'World';
  @Input() message: string = 'Welcome to Angular';
  @Output() hello = new EventEmitter<string>();

  sayHello() {
    this.hello.emit(`Hello from ${this.name}!`);
  }
}

// Usage in parent component:
// <app-greeting [name]="userName" [message]="customMessage" (hello)="handleHello($event)"></app-greeting>

// 3. Service Example
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class DataService {
  private items: any[] = [];

  getItems() {
    return this.items;
  }

  addItem(item: any) {
    this.items.push(item);
  }

  removeItem(index: number) {
    this.items.splice(index, 1);
  }

  updateItem(index: number, item: any) {
    this.items[index] = item;
  }
}

// 4. Component with Dependency Injection
import { Component, OnInit } from '@angular/core';
import { DataService } from './data.service';

@Component({
  selector: 'app-item-list',
  template: `
    <div class="item-list">
      <h3>{{ title }}</h3>
      <ul>
        <li *ngFor="let item of items; let i = index">
          {{ item.name }}
          <button (click)="removeItem(i)">Remove</button>
          <button (click)="editItem(i)">Edit</button>
        </li>
      </ul>
      <div>
        <input #newItem type="text" placeholder="Add new item">
        <button (click)="addItem(newItem.value)">Add</button>
      </div>
    </div>
  `,
  styles: [`
    .item-list {
      padding: 20px;
      border: 1px solid #ddd;
      border-radius: 8px;
    }
    ul {
      list-style: none;
      padding: 0;
    }
    li {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 8px;
      border-bottom: 1px solid #eee;
    }
    li button {
      margin-left: 10px;
    }
    div {
      margin-top: 10px;
      display: flex;
      gap: 10px;
    }
    div input {
      flex: 1;
      padding: 5px;
    }
  `]
})
export class ItemListComponent implements OnInit {
  @Input() title: string = 'Items';
  items: any[] = [];

  constructor(private dataService: DataService) {}

  ngOnInit() {
    this.items = this.dataService.getItems();
  }

  addItem(itemName: string) {
    if (itemName.trim()) {
      this.dataService.addItem({
        name: itemName.trim(),
        createdAt: new Date()
      });
      this.items = this.dataService.getItems();
    }
  }

  removeItem(index: number) {
    this.dataService.removeItem(index);
    this.items = this.dataService.getItems();
  }

  editItem(index: number) {
    const newName = prompt('Edit item name:', this.items[index].name);
    if (newName && newName.trim()) {
      this.dataService.updateItem(index, {
        ...this.items[index],
        name: newName.trim(),
        updatedAt: new Date()
      });
      this.items = this.dataService.getItems();
    }
  }
}

💻 Patterns Avancés Angular typescript

🟡 intermediate ⭐⭐⭐⭐

Patterns avancés d'Angular incluant RxJS, formulaires, routing et services HTTP

⏱️ 35 min 🏷️ angular, typescript, forms, http, rxjs
Prerequisites: Angular basics, TypeScript, RxJS concepts, HTTP client
// Angular Advanced Patterns

// 1. Reactive Forms with FormBuilder
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';

@Component({
  selector: 'app-user-form',
  template: `
    <form [formGroup]="userForm" (ngSubmit)="onSubmit()">
      <div class="form-group">
        <label for="name">Name:</label>
        <input id="name" formControlName="name" type="text">
        <div *ngIf="userForm.get('name').invalid && userForm.get('name').touched"
             class="error">
          Name is required and must be at least 2 characters
        </div>
      </div>

      <div class="form-group">
        <label for="email">Email:</label>
        <input id="email" formControlName="email" type="email">
        <div *ngIf="userForm.get('email').invalid && userForm.get('email').touched"
             class="error">
          Please enter a valid email address
        </div>
      </div>

      <div class="form-group">
        <label for="age">Age:</label>
        <input id="age" formControlName="age" type="number">
        <div *ngIf="userForm.get('age').invalid && userForm.get('age').touched"
             class="error">
          Age must be between 18 and 120
        </div>
      </div>

      <div class="form-actions">
        <button type="submit" [disabled]="!userForm.valid">Submit</button>
        <button type="button" (click)="resetForm()">Reset</button>
      </div>
    </form>

    <div *ngIf="submittedData" class="submitted-data">
      <h3>Submitted Data:</h3>
      <pre>{{ submittedData | json }}</pre>
    </div>
  `,
  styles: [`
    .form-group {
      margin-bottom: 15px;
    }
    label {
      display: block;
      margin-bottom: 5px;
      font-weight: bold;
    }
    input {
      width: 100%;
      padding: 8px;
      border: 1px solid #ddd;
      border-radius: 4px;
      font-size: 14px;
    }
    input.ng-invalid.ng-touched {
      border-color: #f44336;
    }
    .error {
      color: #f44336;
      font-size: 12px;
      margin-top: 5px;
    }
    .form-actions {
      margin-top: 20px;
    }
    .form-actions button {
      margin-right: 10px;
      padding: 10px 20px;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
    button[type="submit"]:disabled {
      background-color: #ccc;
      cursor: not-allowed;
    }
    .submitted-data {
      margin-top: 20px;
      padding: 15px;
      background-color: #f8f9fa;
      border-radius: 4px;
    }
  `]
})
export class UserFormComponent implements OnInit {
  userForm: FormGroup;
  submittedData: any = null;

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    this.userForm = this.fb.group({
      name: ['', [Validators.required, Validators.minLength(2)]],
      email: ['', [Validators.required, Validators.email]],
      age: [18, [Validators.required, Validators.min(18), Validators.max(120)]]
    });
  }

  onSubmit() {
    if (this.userForm.valid) {
      this.submittedData = this.userForm.value;
      console.log('Form submitted:', this.submittedData);
    }
  }

  resetForm() {
    this.userForm.reset();
    this.submittedData = null;
  }
}

// 2. HTTP Service with RxJS
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';

export interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private apiUrl = 'https://jsonplaceholder.typicode.com/users';

  constructor(private http: HttpClient) {}

  getUsers(): Observable<User[]> {
    return this.http.get<User[]>(this.apiUrl).pipe(
      catchError(this.handleError('getUsers', []))
    );
  }

  getUser(id: number): Observable<User> {
    return this.http.get<User>(`${this.apiUrl}/${id}`).pipe(
      catchError(this.handleError(`getUser id=${id}`, null))
    );
  }

  createUser(user: Partial<User>): Observable<User> {
    const headers = new HttpHeaders({ 'Content-Type': 'application/json' });
    return this.http.post<User>(this.apiUrl, user, { headers }).pipe(
      catchError(this.handleError('createUser', null))
    );
  }

  updateUser(id: number, user: Partial<User>): Observable<User> {
    const headers = new HttpHeaders({ 'Content-Type': 'application/json' });
    return this.http.put<User>(`${this.apiUrl}/${id}`, user, { headers }).pipe(
      catchError(this.handleError(`updateUser id=${id}`, null))
    );
  }

  deleteUser(id: number): Observable<void> {
    return this.http.delete<void>(`${this.apiUrl}/${id}`).pipe(
      catchError(this.handleError<void>(`deleteUser id=${id}`, void 0))
    );
  }

  private handleError<T>(operation = 'operation', result?: T) {
    return (error: any): Observable<T> => {
      console.error(`${operation} failed: ${error.message}`);
      return of(result as T);
    };
  }
}

// 3. Component with HTTP Service
import { Component, OnInit } from '@angular/core';
import { UserService } from './user.service';

@Component({
  selector: 'app-user-list',
  template: `
    <div class="user-list">
      <h2>User Management</h2>

      <div class="user-actions">
        <button (click)="loadUsers()">Load Users</button>
        <button (click)="showAddForm = true" [disabled]="loading">Add User</button>
      </div>

      <div *ngIf="loading" class="loading">Loading users...</div>

      <div *ngIf="error" class="error">{{ error }}</div>

      <table *ngIf="users.length > 0">
        <thead>
          <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Email</th>
            <th>Age</th>
            <th>Actions</th>
          </tr>
        </thead>
        <tbody>
          <tr *ngFor="let user of users; let i = index">
            <td>{{ user.id }}</td>
            <td>{{ user.name }}</td>
            <td>{{ user.email }}</td>
            <td>{{ user.age }}</td>
            <td>
              <button (click)="editUser(user)" class="edit-btn">Edit</button>
              <button (click)="deleteUser(user.id)" class="delete-btn">Delete</button>
            </td>
          </tr>
        </tbody>
      </table>

      <!-- Add/Edit User Modal -->
      <div *ngIf="showAddForm || editingUser" class="modal">
        <div class="modal-content">
          <h3>{{ editingUser ? 'Edit User' : 'Add New User' }}</h3>
          <form (ngSubmit)="saveUser()">
            <div class="form-group">
              <label>Name:</label>
              <input [(ngModel)]="formData.name" type="text" required>
            </div>
            <div class="form-group">
              <label>Email:</label>
              <input [(ngModel)]="formData.email" type="email" required>
            </div>
            <div class="form-group">
              <label>Age:</label>
              <input [(ngModel)]="formData.age" type="number" required>
            </div>
            <div class="form-actions">
              <button type="submit">{{ editingUser ? 'Update' : 'Create' }}</button>
              <button type="button" (click)="cancelEdit()">Cancel</button>
            </div>
          </form>
        </div>
      </div>
    </div>
  `,
  styles: [`
    .user-list {
      padding: 20px;
    }
    table {
      width: 100%;
      border-collapse: collapse;
      margin-top: 20px;
    }
    th, td {
      padding: 12px;
      text-align: left;
      border-bottom: 1px solid #ddd;
    }
    th {
      background-color: #f5f5f5;
      font-weight: bold;
    }
    .user-actions {
      margin-bottom: 20px;
    }
    .user-actions button {
      margin-right: 10px;
      padding: 8px 16px;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
    .modal {
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background-color: rgba(0,0,0,0.5);
      display: flex;
      align-items: center;
      justify-content: center;
    }
    .modal-content {
      background: white;
      padding: 30px;
      border-radius: 8px;
      max-width: 500px;
      width: 90%;
    }
    .form-group {
      margin-bottom: 15px;
    }
    .form-group label {
      display: block;
      margin-bottom: 5px;
      font-weight: bold;
    }
    .form-group input {
      width: 100%;
      padding: 8px;
      border: 1px solid #ddd;
      border-radius: 4px;
      box-sizing: border-box;
    }
    .form-actions {
      margin-top: 20px;
      text-align: right;
    }
    .form-actions button {
      margin-left: 10px;
      padding: 8px 16px;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
  `]
})
export class UserListComponent implements OnInit {
  users: User[] = [];
  loading = false;
  error = '';
  showAddForm = false;
  editingUser: User | null = null;
  formData = {
    name: '',
    email: '',
    age: 0
  };

  constructor(private userService: UserService) {}

  ngOnInit() {
    this.loadUsers();
  }

  loadUsers() {
    this.loading = true;
    this.userService.getUsers().subscribe({
      next: (users) => {
        this.users = users;
        this.loading = false;
      },
      error: (error) => {
        this.error = 'Failed to load users';
        this.loading = false;
      }
    });
  }

  saveUser() {
    if (this.editingUser) {
      this.userService.updateUser(this.editingUser.id, this.formData).subscribe({
        next: (updatedUser) => {
          const index = this.users.findIndex(u => u.id === updatedUser.id);
          if (index !== -1) {
            this.users[index] = updatedUser;
          }
          this.cancelEdit();
        },
        error: (error) => {
          this.error = 'Failed to update user';
        }
      });
    } else {
      this.userService.createUser(this.formData).subscribe({
        next: (newUser) => {
          this.users.push(newUser);
          this.cancelEdit();
        },
        error: (error) => {
          this.error = 'Failed to create user';
        }
      });
    }
  }

  editUser(user: User) {
    this.editingUser = user;
    this.showAddForm = true;
    this.formData = { ...user };
  }

  deleteUser(id: number) {
    if (confirm('Are you sure you want to delete this user?')) {
      this.userService.deleteUser(id).subscribe({
        next: () => {
          this.users = this.users.filter(u => u.id !== id);
        },
        error: (error) => {
          this.error = 'Failed to delete user';
        }
      });
    }
  }

  cancelEdit() {
    this.showAddForm = false;
    this.editingUser = null;
    this.formData = {
      name: '',
      email: '',
      age: 0
    };
  }
}

// Main App Component
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <div class="app">
      <header>
        <h1>Angular Advanced Examples</h1>
        <nav>
          <a routerLink="/">Home</a>
          <a routerLink="/users">Users</a>
        </nav>
      </header>

      <main>
        <app-hello-world></app-hello-world>
        <app-greeting name="Angular" message="Welcome to advanced patterns!"></app-greeting>
        <app-item-list title="Todo List"></app-item-list>
        <app-user-form></app-user-form>
        <app-user-list></app-user-list>
      </main>
    </div>
  `,
  styles: [`
    .app {
      min-height: 100vh;
      font-family: Arial, sans-serif;
    }
    header {
      background-color: #3f51b5;
      color: white;
      padding: 1rem;
      text-align: center;
    }
    header h1 {
      margin: 0;
    }
    nav {
      margin-top: 1rem;
    }
    nav a {
      color: white;
      text-decoration: none;
      margin: 0 1rem;
      padding: 0.5rem 1rem;
      border-radius: 4px;
      transition: background-color 0.3s;
    }
    nav a:hover {
      background-color: rgba(255, 255, 255, 0.1);
    }
    nav a.router-link-active {
      background-color: rgba(255, 255, 255, 0.2);
    }
    main {
      padding: 2rem;
      max-width: 1200px;
      margin: 0 auto;
    }
  `]
})
export class AppComponent {}

💻 Patterns Entreprise Angular typescript

🔴 complex ⭐⭐⭐⭐⭐

Patterns d'entreprise Angular incluant lazy loading, guards, interceptors et tests

⏱️ 50 min 🏷️ angular, enterprise, testing, architecture, patterns
Prerequisites: Advanced Angular, TypeScript, Testing frameworks, Enterprise patterns
// Angular Enterprise Patterns

// 1. Lazy Loading Modules
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'users', loadChildren: () => import('./users/users.module').then(m => m.UsersModule) },
  { path: 'admin', loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule), canActivate: [AdminGuard] },
  { path: '**', redirectTo: '' }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

// 2. Route Guards
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { AuthService } from './auth.service';

@Injectable({
  providedIn: 'root'
})
export class AdminGuard implements CanActivate {
  constructor(private authService: AuthService, private router: Router) {}

  canActivate(): boolean {
    if (this.authService.isAdmin()) {
      return true;
    }

    this.router.navigate(['/login']);
    return false;
  }
}

// 3. HTTP Interceptor
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler } from '@angular/common/http';
import { AuthService } from './auth.service';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(private authService: AuthService) {}

  intercept(req: HttpRequest, next: HttpHandler) {
    const authToken = this.authService.getToken();

    if (authToken) {
      req = req.clone({
        headers: req.headers.set('Authorization', `Bearer ${authToken}`)
      });
    }

    return next.handle(req);
  }
}

// 4. Error Handling Service
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

export interface ApiError {
  message: string;
  status: number;
  statusText: string;
  details: any;
}

@Injectable({
  providedIn: 'root'
})
export class ErrorHandlingService {
  constructor(private http: HttpClient) {}

  handleError(error: HttpErrorResponse): Observable<never> {
    let errorMessage = 'An unknown error occurred';

    if (error.error instanceof ErrorEvent) {
      errorMessage = `Error: ${error.error.message}`;
    } else {
      errorMessage = `Error Code: ${error.status} - ${error.message}`;
    }

    console.error(errorMessage, error);
    return throwError(() => errorMessage);
  }
}

// 5. Advanced Component with Lifecycle Hooks
import {
  Component,
  OnInit,
  OnDestroy,
  AfterViewInit,
  OnChanges,
  SimpleChanges
} from '@angular/core';

@Component({
  selector: 'app-lifecycle-demo',
  template: `
    <div class="lifecycle-demo">
      <h2>Lifecycle Demo</h2>
      <p>Message: {{ message }}</p>
      <p>Timestamp: {{ timestamp }}</p>
      <p>Component Status: {{ status }}</p>
      <button (click)="triggerUpdate()">Manual Update</button>
    </div>
  `,
  styles: [`
    .lifecycle-demo {
      padding: 20px;
      border: 1px solid #ddd;
      border-radius: 8px;
      margin: 10px 0;
    }
  `]
})
export class LifecycleDemoComponent implements OnInit, OnDestroy, AfterViewInit, OnChanges {
  message = 'Component initialized';
  timestamp = new Date();
  status = 'active';
  private updateInterval: any;

  ngOnInit() {
    console.log('Component initialized');
    this.startAutoUpdate();
  }

  ngAfterViewInit() {
    console.log('View initialized');
    this.status = 'view ready';
  }

  ngOnChanges(changes: SimpleChanges) {
    console.log('Properties changed:', changes);
  }

  ngOnDestroy() {
    console.log('Component destroyed');
    this.stopAutoUpdate();
  }

  triggerUpdate() {
    this.message = 'Manual update triggered';
    this.timestamp = new Date();
  }

  private startAutoUpdate() {
    this.updateInterval = setInterval(() => {
      this.timestamp = new Date();
    }, 5000);
  }

  private stopAutoUpdate() {
    if (this.updateInterval) {
      clearInterval(this.updateInterval);
    }
  }
}

// 6. Advanced Service with Caching
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable, timer } from 'rxjs';

export interface CacheItem<T> {
  data: T;
  timestamp: number;
  ttl: number;
}

@Injectable({
  providedIn: 'root'
})
export class CacheService {
  private cache = new Map<string, CacheItem<any>>();
  private defaultTTL = 300000; // 5 minutes

  set<T>(key: string, data: T, ttl: number = this.defaultTTL): void {
    this.cache.set(key, {
      data,
      timestamp: Date.now(),
      ttl
    });
  }

  get<T>(key: string): Observable<T | null> {
    const item = this.cache.get(key);

    if (!item) {
      return of(null);
    }

    const now = Date.now();
    if (now - item.timestamp > item.ttl) {
      this.cache.delete(key);
      return of(null);
    }

    return of(item.data);
  }

  clear(): void {
    this.cache.clear();
  }

  clearExpired(): void {
    const now = Date.now();
    for (const [key, item] of this.cache.entries()) {
      if (now - item.timestamp > item.ttl) {
        this.cache.delete(key);
      }
    }
  }
}

// 7. Advanced Directive
import { Directive, Input, HostListener } from '@angular/core';
import { ElementRef } from '@angular/core';

@Directive({
  selector: '[appTooltip]',
  host: {
    '(mouseenter)': 'onMouseEnter($event)',
    '(mouseleave)': 'onMouseLeave($event)'
  }
})
export class TooltipDirective {
  @Input('appTooltip') tooltipText: string;

  private tooltipElement: HTMLElement;
  private isVisible = false;

  @HostListener('mouseenter', ['$event'])
  onMouseEnter(event: MouseEvent) {
    this.showTooltip(event.target as Element);
  }

  @HostListener('mouseleave', ['$event'])
  onMouseLeave(event: MouseEvent) {
    this.hideTooltip();
  }

  private showTooltip(element: Element) {
    if (this.isVisible || !this.tooltipText) return;

    this.createTooltipElement();
    this.tooltipElement.textContent = this.tooltipText;

    const rect = element.getBoundingClientRect();
    this.tooltipElement.style.left = rect.left + 'px';
    this.tooltipElement.style.top = (rect.bottom + 5) + 'px';
    this.tooltipElement.style.visibility = 'visible';
    this.isVisible = true;
  }

  private hideTooltip() {
    if (this.tooltipElement) {
      this.tooltipElement.style.visibility = 'hidden';
    }
    this.isVisible = false;
  }

  private createTooltipElement() {
    if (this.tooltipElement) return;

    this.tooltipElement = document.createElement('div');
    this.tooltipElement.className = 'tooltip';
    this.tooltipElement.style.cssText = `
      position: fixed;
      background: #333;
      color: white;
      padding: 8px 12px;
      border-radius: 4px;
      font-size: 14px;
      z-index: 1000;
      pointer-events: none;
      transition: opacity 0.3s;
      opacity: 0;
    `;
    document.body.appendChild(this.tooltipElement);
  }
}

// Usage in template:
// <div appTooltip="This is a tooltip">Hover over me</div>

// 8. Advanced Pipe
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'filterBy',
  pure: false
})
export class FilterByPipe implements PipeTransform {
  transform(items: any[], field: string, value: string): any[] {
    if (!items || !field || !value) {
      return items;
    }

    return items.filter(item =>
      item[field] &&
      item[field].toString().toLowerCase().includes(value.toLowerCase())
    );
  }
}

// Usage in template:
// *ngFor="let user of users | filterBy:'name':'searchTerm"
// <div *ngFor="let product of products | filterBy:'category':'electronics'">

// 9. Testing Setup
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';

import { UserService } from './user.service';

describe('UserService', () => {
  let service: UserService;
  let httpMock: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [UserService]
    });

    service = TestBed.inject(UserService);
    httpMock = TestBed.inject(HttpTestingController);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  it('should retrieve users', () => {
    const mockUsers = [
      { id: 1, name: 'Test User 1', email: '[email protected]', age: 25 },
      { id: 2, name: 'Test User 2', email: '[email protected]', age: 30 }
    ];

    httpMock.expectOne('https://jsonplaceholder.typicode.com/users')
      .flush(mockUsers);

    service.getUsers().subscribe(users => {
      expect(users).toEqual(mockUsers);
    });
  });

  it('should handle errors', () => {
    httpMock.expectOne('https://jsonplaceholder.typicode.com/users')
      .flushError('Server error', { status: 500, statusText: 'Server Error' });

    service.getUsers().subscribe(
      () => fail('Expected error but got success'),
      error => {
        expect(error).toBeTruthy();
      }
    );
  });
});

// 10. Main App Module
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { RouterModule } from '@angular/router';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { AuthInterceptor } from './auth.interceptor';
import { ErrorHandlingService } from './error-handling.service';

import { HelloWorldComponent } from './hello-world/hello-world.component';
import { GreetingComponent } from './greeting/greeting.component';
import { ItemListComponent } from './item-list/item-list.component';
import { UserFormComponent } from './user-form/user-form.component';
import { UserListComponent } from './user-list/user-list.component';

@NgModule({
  declarations: [
    AppComponent,
    HelloWorldComponent,
    GreetingComponent,
    ItemListComponent,
    UserFormComponent,
    UserListComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    RouterModule.forRoot(AppRoutingModule),
    ReactiveFormsModule
  ],
  providers: [
    AuthInterceptor,
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true
    },
    ErrorHandlingService
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}