我有一个复杂的结构化json数据,需要在Angular 6 App中应用高级过滤。
is_primaryTcin
我想在HTML页面中有3个下拉列表以进行过滤:
当我选择as_primaryTcin
和[{
"StudentId": 1,
"StudentName": "Student1",
"Sex":"M",
"Programs": [
{
"StudentId": 1,
"ProgramName": "Java",
"ProgramCategory": "Engineering",
"ProgramStatus": "Full Time"
},
{
"StudentId": 1,
"ProgramName": "HR Management 2",
"ProgramCategory": "HR",
"ProgramStatus": "Part Time"
},
{
"StudentId": 1,
"ProgramName": "Accounting 1",
"ProgramCategory": "Finance",
"ProgramStatus": "Full Time"
}
]
},
{
"StudentId": 2,
"StudentName": "Student2",
"Sex":"F",
"Programs": [
{
"StudentId": 2,
"ProgramName": "HR Management 1",
"ProgramCategory": "HR",
"ProgramStatus": "Part Time"
},
{
"StudentId": 2,
"ProgramName": "Accounting 3",
"ProgramCategory": "Finance",
"ProgramStatus": "Full Time"
}
]
},
{
"StudentId": 3,
"StudentName": "Student3",
"Sex":"F",
"Programs": [
{
"StudentId": 3,
"ProgramName": "Java 3",
"ProgramCategory": "Engineering",
"ProgramStatus": "Full Time"
}
]
},
{
"StudentId": 4,
"StudentName": "Student4",
"Sex":"M",
"Programs": [
{
"StudentId": 4,
"ProgramName": "Java 2",
"ProgramCategory": "Engineering",
"ProgramStatus": "Full Time"
},
{
"StudentId": 4,
"ProgramName": "Accounting 2",
"ProgramCategory": "Finance",
"ProgramStatus": "Part Time"
}
]
},
{
"StudentId": 5,
"StudentName": "Student5",
"Sex":"M",
"Programs": [
{
"StudentId": 5,
"ProgramName": "JavaScript",
"ProgramCategory": "Engineering",
"ProgramStatus": "Part Time"
},
{
"StudentId": 5,
"ProgramName": "HR Management 5",
"ProgramCategory": "HR",
"ProgramStatus": "Full Time"
}
]
}]
时,将仅返回2个学生(ProgramCategory = 'HR'
,ProgramStatus = 'Part Time'
)。我花了几天时间尝试获得想要的结果,但仍然无法解决。我将此article用作参考,并根据我的数据进行了一些改进,但返回的数据不正确,请参见下图:
因此,我只需要返回标记的行(student1
)。
上图中标记的行#:5被错误标记。
student2
row#:1,2
有人可以帮助我实现我的目标吗?
您可以更改我当前的ts代码或有新的解决方案,欢迎两者!
非常感谢!
答案 0 :(得分:3)
在这里,我使用了响应式表单和rxjs BehaviorSubjects为您提供了一个解决方案:
https://stackblitz.com/edit/how-to-filter-complex-json-data-new-chind-array-object-xtlbxy
该链接具有完整的解决方案,但这是我认为您遇到的过滤问题的核心:
private setFilters() {
this.filteredStudents$.next(this.students$.value);
combineLatest(
this.students$,
this.sexFilterControl.valueChanges,
this.programControls.valueChanges,
this.courseControls.valueChanges
)
.subscribe(([students, sexFilter, programFilters, courseFilters]) => {
let filteredStudents = [ ... students ];
if (sexFilter) {
filteredStudents = filteredStudents.filter(student => student.Sex === sexFilter);
}
// programs
filteredStudents = filteredStudents.filter(student => {
return student.Programs.reduce((programsPrev, program) => {
return programsPrev || Object.entries(programFilters).reduce((filterPrev, [filterName, filterValue]) => {
if (!filterValue) {
return filterPrev;
}
return filterPrev && program[filterName] === filterValue;
}, true);
}, false)
});
// courses
filteredStudents = filteredStudents.filter(student => {
return student.Courses.reduce((coursesPrev, course) => {
return coursesPrev || Object.entries(courseFilters).reduce((filterPrev, [filterName, filterValue]) => {
if (!filterValue) {
return filterPrev;
}
return filterPrev && course[filterName] === filterValue;
}, true);
}, false)
});
this.filteredStudents$.next(filteredStudents);
});
this.sexFilterControl.setValue('');
this.programCategoryFilterControl.setValue('');
this.programStatusFilterControl.setValue('');
this.courseCategoryFilterControl.setValue('');
this.courseStatusFilterControl.setValue('');
}
对ProgramCategory和ProgramStatus(都必须针对同一Program进行匹配)的过滤与根本上对这两个过滤都根本不同。
由于您对两个程序过滤器的要求基本上是“仅显示拥有至少1个与所有现有过滤器匹配的程序的学生”,因此您可以在堆栈闪电中看到将相关控件分组为{{1 }}并编写反映此预期行为的过滤器。
如果您愿意的话,建议您将表格调整为使用@angular/cdk/table
,实际上,我现在正在与Angular Firebase的家伙一起撰写有关该主题的文章(例如您所链接的发布)。我认为这是值得的,特别是如果您喜欢我在此解决方案中使用的以rxjs为中心的方法。
答案 1 :(得分:1)
设置过滤器,然后使用适当的值调用以下方法。
const people = [{
"StudentId": 1,
"StudentName": "Student1",
"Sex": "M",
"Programs": [
{
"StudentId": 1,
"ProgramName": "Java",
"ProgramCategory": "Engineering",
"ProgramStatus": "Full Time"
},
{
"StudentId": 1,
"ProgramName": "HR Management 2",
"ProgramCategory": "HR",
"ProgramStatus": "Part Time"
},
{
"StudentId": 1,
"ProgramName": "Accounting 1",
"ProgramCategory": "Finance",
"ProgramStatus": "Full Time"
}
]
},
{
"StudentId": 2,
"StudentName": "Student2",
"Sex": "F",
"Programs": [
{
"StudentId": 2,
"ProgramName": "HR Management 1",
"ProgramCategory": "HR",
"ProgramStatus": "Part Time"
},
{
"StudentId": 2,
"ProgramName": "Accounting 3",
"ProgramCategory": "Finance",
"ProgramStatus": "Full Time"
}
]
},
{
"StudentId": 3,
"StudentName": "Student3",
"Sex": "F",
"Programs": [
{
"StudentId": 3,
"ProgramName": "Java 3",
"ProgramCategory": "Engineering",
"ProgramStatus": "Full Time"
}
]
},
{
"StudentId": 4,
"StudentName": "Student4",
"Sex": "M",
"Programs": [
{
"StudentId": 4,
"ProgramName": "Java 2",
"ProgramCategory": "Engineering",
"ProgramStatus": "Full Time"
},
{
"StudentId": 4,
"ProgramName": "Accounting 2",
"ProgramCategory": "Finance",
"ProgramStatus": "Part Time"
}
]
},
{
"StudentId": 5,
"StudentName": "Student5",
"Sex": "M",
"Programs": [
{
"StudentId": 5,
"ProgramName": "JavaScript",
"ProgramCategory": "Engineering",
"ProgramStatus": "Part Time"
},
{
"StudentId": 5,
"ProgramName": "HR Management 5",
"ProgramCategory": "HR",
"ProgramStatus": "Full Time"
}
]
}];
const findFilteredStudents = (students, sex, category, status) => {
const foundStudents = students.filter(student => {
// if sex is set as a filter, compare students to it
if (sex && student.sex !== sex) {
return false;
}
// if category is a filter, return false if a student
// does not have the category
if (category) {
const hasCategory = student.Programs.find(Program => Program.ProgramCategory === category);
if (!hasCategory) {
return false;
}
}
// if status is a filter, return false if a student
// does not have the status
if (status) {
const hasStatus = student.Programs.find(Program => Program.ProgramStatus === status);
if (!hasStatus) {
return false;
}
}
return true;
});
return foundStudents;
};
const students = findFilteredStudents(people, null, 'HR', 'Part Time');
students.forEach(student => {
console.log(student);
})
答案 2 :(得分:1)
因为this.filters [property]的键始终是Programs,所以您总是覆盖先前的选择。因此,它只会应用两个子过滤器中最新的一个。
相反,您应该检查是否已经为this.filters[property]
定义了过滤器。如果是这样,请确保也将其选中。
您可以像这样修改filterMatchSub
:
filterMatchSub(property: string, childProperty: string, value: any) {
let existing = (val) => true; // Define a function that always returns true
// If a filter is already defined, hold a reference to it in existing
if (this.filters[property]) {
existing = this.filters[property];
}
// Call the existing function as well
this.filters[property] = val => val.find( child => child[childProperty] === value) && existing(val);
this.setFilters();
}
答案 3 :(得分:0)
这是我应如何处理的全部内容。 Full working exmaple on stackblitz。
模块:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
@NgModule({
imports: [BrowserModule, FormsModule, ReactiveFormsModule],
declarations: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule { }
组件:
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { STUDENTS } from './students';
interface FilterFormValue {
sex: string;
category: string;
status: string;
}
interface Program {
studentId: number;
programName: string;
programCategory: string;
programStatus: string;
}
export interface Student {
studentId: number;
studentName: string;
sex: string;
programs: Array<Program>;
}
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
students: Array<Student> = [];
filteredStudents: Array<Student> = [];
sexOptions: Array<string> = [];
programCategoryOptions: Array<string> = [];
programStatusOptions: Array<string> = [];
filterForm: FormGroup;
constructor(private formBuilder: FormBuilder) { }
ngOnInit() {
this.getStudents();
}
private getStudents() {
// you would get students from an API in a real word scenario, now we just simply initialize it here
// I put the data in a different file for convinience
this.students = STUDENTS;
// also setting filtered students to all of the students to display all of them at the start
this.filteredStudents = this.students;
// again, normally you would get these options from the backend but here we simply reduce our array of students
this.getSexOptions();
this.getProgramCategoryOptions();
this.getProgramStatusOptions();
// when we get all our data initialize the filter form
this.initFilterForm();
}
private getSexOptions() {
// get all unique values from array of students
this.sexOptions = Array.from(new Set(this.students.map((student: Student) => student.sex)));
}
private getProgramCategoryOptions() {
// this is a little bit trickier and normally you get these from the backend
// but suffice it to say that at the end we get all unique values for program categories
const categoryGroups = this.students.map((student: Student) => {
return student.programs.map((program: Program) => program.programCategory);
});
this.programCategoryOptions = Array.from(new Set(categoryGroups.reduce((a, b) => a.concat(b))));
}
private getProgramStatusOptions() {
// same as categories, we get all unique values for program statuses
const statusGroups = this.students.map((student: Student) => {
return student.programs.map((program: Program) => program.programStatus);
});
this.programStatusOptions = Array.from(new Set(statusGroups.reduce((a, b) => a.concat(b))));
}
private initFilterForm() {
// initialize the form with empty strings, in html the 'All' option will be selected
this.filterForm = this.formBuilder.group({
sex: [''],
category: [''],
status: ['']
});
// init watch for any form changes
this.watchFormChanges();
}
private watchFormChanges() {
// this will fire on any filter changes and call the filtering method with the value of the form
this.filterForm.valueChanges.subscribe((value: FilterFormValue) => this.filterStudents(value));
}
private filterStudents(value: FilterFormValue) {
// again, this operation would be executed on the backend, but here you go
// initialize a new array of all the students
let filteredStudents: Array<Student> = this.students;
if (value.sex) {
// if filter for sex is set, simply filter for any student that has the same value for sex
filteredStudents = filteredStudents.filter((student: Student) => student.sex === value.sex);
}
if (value.category && !value.status) {
// when category is set but status is not, filter for any student that has the category in any of its programs
filteredStudents = filteredStudents.filter((student: Student) => {
return student.programs
.map((program: Program) => program.programCategory)
.includes(value.category);
});
}
if (!value.category && value.status) {
// when status is set but category is not, filter for any student that has the status in any of its programs
filteredStudents = filteredStudents.filter((student: Student) => {
return student.programs
.map((program: Program) => program.programStatus)
.includes(value.status);
});
}
if (value.category && value.status) {
// when category and status is both set, filter for any student that has the status AND category in any of its programs
filteredStudents = filteredStudents.filter((student: Student) => {
return student.programs
.filter((program: Program) => program.programCategory === value.category)
.map((program: Program) => program.programStatus)
.includes(value.status);
});
}
// set the filtered students to display
this.filteredStudents = filteredStudents;
}
}
HTML:
<div class="row">
<div class="col-sm-12">
<div class="panel panel-sm ">
<div class="panel-body">
<h5>Basic Info</h5>
<div class="hs-lead">
<form [formGroup]="filterForm">
<div class="row">
<div class="col-sm-4">
<div class="form-group">
<label for="exampleSelect1">Sex</label>
<div class="row">
<div class="col-sm-9">
<select class="form-control" formControlName="sex">
<option value="">All</option>
<option *ngFor="let option of sexOptions" [value]="option">{{ option }}</option>
</select>
</div>
<div class="col-sm-3">
<button class="btn btn-primary" *ngIf="filterForm && !!filterForm.get('sex').value" (click)="filterForm.get('sex').setValue('')">Clear</button>
</div>
</div>
</div>
</div>
<div class="col-sm-4">
<div class="form-group">
<label for="exampleSelect1">ProgramCategory</label>
<div class="row">
<div class="col-sm-9">
<select class="form-control" formControlName="category">
<option value="">All</option>
<option *ngFor="let option of programCategoryOptions" [value]="option">{{ option }}</option>
</select>
</div>
<div class="col-sm-3">
<button class="btn btn-primary" *ngIf="filterForm && !!filterForm.get('category').value" (click)="filterForm.get('category').setValue('')">Clear</button>
</div>
</div>
</div>
</div>
<div class="col-sm-4">
<div class="form-group">
<label for="exampleSelect1">ProgramStatus</label>
<div class="row">
<div class="col-sm-9">
<select class="form-control" formControlName="status">
<option value="">All</option>
<option *ngFor="let option of programStatusOptions" [value]="option">{{ option }}</option>
</select>
</div>
<div class="col-sm-3">
<button class="btn btn-primary" *ngIf="filterForm && !!filterForm.get('status').value" (click)="filterForm.get('status').setValue('')">Clear</button>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="panel panel-xl">
<div class="panel-body">
<h5>Result
<span class="badge badge-info badge-pill pull-right">{{ filteredStudents.length }}</span>
</h5>
<div class="hs-lead">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Sex</th>
<th>Programs</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let student of filteredStudents">
<td>{{ student.studentId }}</td>
<td>{{ student.studentName }}</td>
<td>{{ student.sex }}</td>
<td>
{{ student.programs.length }}
<ol *ngFor="let program of student.programs">
<li>{{ program.programCategory }} / {{ program.programStatus }}</li>
</ol>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>