Angular 框架示例

基本的Angular示例,用于企业级Web开发,包括组件、TypeScript和模块

💻 Angular Hello World typescript

🟢 simple ⭐⭐

基础Angular组件、TypeScript和基本概念

⏱️ 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();
    }
  }
}

💻 Angular 高级模式 typescript

🟡 intermediate ⭐⭐⭐⭐

高级Angular模式,包括RxJS、表单、路由和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, FormGroup, FormBuilder, Validators } from '@angular/core';
import { 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, catchError, tap } from 'rxjs/operators';
import { of } from 'rxjs';

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(`deleteUser id=${id}`, of(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 {}

💻 Angular 企业级模式 typescript

🔴 complex ⭐⭐⭐⭐⭐

企业级Angular模式,包括懒加载、守卫、拦截器和测试

⏱️ 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 {}