Как правильно реализовать фильтрацию по конкретному столбцу?
Пишу обучающий проект на 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 шт):
В Вашем варианте, если в фильтр приходят несколько значений, то всегда применяется последнее значение и это не правильно. Напр. в фильтре есть 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 {