Как правильно реализовать фильтрацию по конкретному столбцу?

Пишу обучающий проект на Angular. Приложение отображает посты из фейк-апи https://jsonplaceholder.typicode.com/. Требуется реализовать фильтрацию по колонкам. Фильтрация реализована, но есть один момент: если ввести id, например, 1, а потом заголовок su, то покажутся все записи с "su" в заголовке. Как это можно исправить?

Посмотреть проект можно тут: проект

posts.component.ts:

import { Component, OnInit, Output, EventEmitter } from "@angular/core";
import { FormGroup, FormControl } from "@angular/forms";
import { Post, PostService } from "../post.service";
import { User, UserService } from "../user.service";

@Component({
  selector: "app-posts",
  templateUrl: "./posts.component.html",
  styleUrls: ["./posts.component.css"]
})
export class PostsComponent implements OnInit {
  page: any = 1;

  posts: Post[] = [];
  users: User[] = [];

  columns: any = [
    { title: "User id", filterKey: "userIdType", value: "usd" },
    { title: "User name", filterKey: "user", value: "us" },
    { title: "Title", filterKey: "titleArticle", value: "tart" }
  ];

  @Output() filterChange = new EventEmitter();
  form!: FormGroup;

  public totalItems: number = 100;
  public itemsPerPage: number = 100;

  error = "";

  constructor(
    private postService: PostService,
    private userService: UserService
  ) {}

  ngOnInit() {
    this.fetchPosts();
    this.fetchUsers();

    this.form = new FormGroup({});

    for (let column of this.columns) {
      this.form.addControl(column.filterKey, new FormControl(""));
    }

    this.form.valueChanges.subscribe((values) => {
      const params: any = {};

      const str = Object.values(values).reduce((a: string, i: string) => a + i);

      if (str) {
        for (let key in values) {
          if (values[key]) {
            params[key] = values[key];
            console.log("params", params);

            this.posts.map((post: any) => {
              if (params["titleArticle"]) {
                console.log("title");
                post.show = post.title
                  .toUpperCase()
                  .includes(params["titleArticle"].toUpperCase());
              } else if (params["userIdType"]) {
                console.log("id");
                post.show = post.userId === +params["userIdType"];
              } else if (params["user"]) {
                console.log("name");
                post.show = post.user.username
                  .toUpperCase()
                  .includes(params["user"].toUpperCase());
              }

              return post;
            });
          }
        }
      } else {
        this.posts.map((post: any) => {
          post.user = this.users.find((user) => user.id === post.userId) ?? {
            username: ""
          };
          post.show = true;
          return post;
        });
      }
    });
  }

  onChangePage(event: any) {
    this.page = event;
    this.fetchPosts();
  }

  fetchPosts() {
    this.postService.fetchPosts(this.page, this.itemsPerPage).subscribe(
      (posts) => {
        this.posts = posts;

        this.posts.map((post: any) => {
          post.user = this.users.find((user) => user.id === post.userId) ?? {
            username: ""
          };
          post.show = true;
          return post;
        });
      },
      (error) => {
        this.error = error.message;
      }
    );
  }

  fetchUsers() {
    this.userService.fetchUsers().subscribe((users) => {
      this.users = users;
    });
  }
}

posts.component.html:

<table>
  <colgroup>
    <col style="width: 10%;" />
    <col style="width: 15%;" />
    <col style="width: 75%;" />
  </colgroup>
  <thead>
    <tr>
      <th *ngFor="let column of columns">{{ column.title }}</th>
    </tr>
    <tr [formGroup]="form">
      <th *ngFor="let column of columns">
        <ng-container *ngIf="column.filterKey">
          <input type="text" [formControlName]="column.filterKey" />
        </ng-container>
      </th>
    </tr>
  </thead>
  <tbody>
    <ng-container *ngFor="let item of posts">
      <tr *ngIf="item.show">
        <td>{{ item.userId }}</td>
        <td>
          <div *ngFor="let user of users">
            {{ item.userId === user.id ? user.username : '' }}
          </div>
        </td>
        <td>
          <a [routerLink]="['/posts', item.id]">{{ item.title | titlecase }}</a>
        </td>
      </tr>
    </ng-container>
  </tbody>
</table>

Ответы (1 шт):

Автор решения: MrFylypenko

В Вашем варианте, если в фильтр приходят несколько значений, то всегда применяется последнее значение и это не правильно. Напр. в фильтре есть 2 значения values: userIdType и titleArticle. При проходе по циклу по userIdType у всех post в post.show у всех значений будет задано некое значение true/false. При следующем проходе по циклу по значению titleArticle все значения post.show будут перезаписаны и проигнорируется первый проход. Проблема тут:

    if (str) {
    for (let key in values) {
      if (values[key]) {
        params[key] = values[key];
        console.log("params", params);

        this.posts.map((post: any) => {
          if (params["titleArticle"]) {
            console.log("title");
            post.show = post.title
              .toUpperCase()
              .includes(params["titleArticle"].toUpperCase());
          } else if (params["userIdType"]) {
            console.log("id");
            post.show = post.userId === +params["userIdType"];
          } else if (params["user"]) {
            console.log("name");
            post.show = post.user.username
              .toUpperCase()
              .includes(params["user"].toUpperCase());
          }

          return post;
        });
      }
    }
  } else {

Проще всего проверять количество значений переданных в фильтр, и количество совпавших значений. Если они совпадают, то значение post.show нужно задать true, иначе false. Исправленный код:

if (str) {
  this.posts.map((post: any) => {    
    //количество совпавших значений    
    const valuesMatchedCount = Object.keys(values).filter(function(key: any) {
      params[key] = values[key];

      if (key === "titleArticle") {
        console.log("title");
        return post.title
          .toUpperCase()
          .includes(params["titleArticle"].toUpperCase());
      }

      if (key === "userIdType") {
        console.log("id");
        return post.userId === +params["userIdType"];
      }

      if (key === "user") {
        console.log("name");
        return post.user.username
          .toUpperCase()
          .includes(params["user"].toUpperCase());
      }
      return false;
    });
    //количество всего значений
    const notEmptyValuesCount = Object.values(values).length;

    if (valuesMatchedCount.length === notEmptyValuesCount){
      post.show = true;
    } else {
      post.show = false;
    }
    
    return post;
  });
} else {
→ Ссылка