如何在Angular 6中过滤复杂的结构化Json数据

时间:2018-07-12 20:58:19

标签: json angular typescript

我有一个复杂的结构化json数据,需要在Angular 6 App中应用高级过滤。

JSON数据:

is_primaryTcin

过滤器选项:

我想在HTML页面中有3个下拉列表以进行过滤:

  1. 性别
  2. ProgramCategory
  3. ProgramStatus

HTML视图:

UI视图如下所示: enter image description here

想要的结果:

当我选择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用作参考,并根据我的数据进行了一些改进,但返回的数据不正确,请参见下图:   enter image description here 因此,我只需要返回标记的行(student1)。

上图中标记的行#:5被错误标记

我的ts代码:

student2

我的HTML代码:

row#:1,2

帮助:

有人可以帮助我实现我的目标吗?

您可以更改我当前的ts代码或有新的解决方案,欢迎两者!

非常感谢!

4 个答案:

答案 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();
  }

Here is a Stackblitz demo

答案 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>