Explicación: Servicios http

10. Búsqueda + filtrado

10.1. Lógica del post-list

import { Component } from '@angular/core';
import { RouterLink } from '@angular/router';
import { Post } from '../../models/post';
import { PostsService } from '../../services/posts';
import { AsyncPipe } from '@angular/common';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { Observable, combineLatest, map, startWith } from 'rxjs';

@Component({
  selector: 'app-post-list',
  imports: [AsyncPipe,RouterLink,ReactiveFormsModule],
  templateUrl: './post-list.html',
  styleUrl: './post-list.css',
})
export class PostListComponent {
  posts$!: Observable<Post[]>;

  searchControl = new FormControl('', { nonNullable: true });
  userIdControl = new FormControl<number | null>(null);
  sortControl = new FormControl<'id-asc' | 'id-desc' | 'title-asc' | 'title-desc'>(
    'id-asc',
    { nonNullable: true }
  );

  filteredPosts$!: Observable<Post[]>;

 constructor(private postsService: PostsService) {
    //  Inicialización segura
    this.posts$ = this.postsService.getAll();

    this.filteredPosts$ = combineLatest([
      this.posts$,
      this.searchControl.valueChanges.pipe(startWith(this.searchControl.value)),
      this.userIdControl.valueChanges.pipe(startWith(this.userIdControl.value)),
      this.sortControl.valueChanges.pipe(startWith(this.sortControl.value)),
    ]).pipe(
      map(([posts, term, userId, sort]) => {
        const q = term.trim().toLowerCase();

        const filtered = posts.filter(p => {
          const matchesText =
            !q || (p.title + ' ' + p.body).toLowerCase().includes(q);

          const matchesUser =
            userId === null || p.userId === userId;

          return matchesText && matchesUser;
        });

        const sorted = [...filtered];
        sorted.sort((a, b) => {
          switch (sort) {
            case 'title-asc':
              return a.title.localeCompare(b.title);
            case 'title-desc':
              return b.title.localeCompare(a.title);
            case 'id-desc':
              return b.id - a.id;
            default:
              return a.id - b.id;
          }
        });

        return sorted;
      })
    );
}
  clear() {
    this.searchControl.setValue('');
    this.userIdControl.setValue(null);
    this.sortControl.setValue('id-asc');
  }
}