6

I have a complex structured json data that needs to be apply an advanced filtering in my Angular 6 App.

JSON Data:

[{
    "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"
        }
    ]
 }]

Filter Options:

I would like to have 3 drop-down list in HTML page to filter by:

  1. Sex
  2. ProgramCategory
  3. ProgramStatus

HTML View:

The UI View will look like the below: enter image description here

Wanted Result:

When I select ProgramCategory = 'HR' and ProgramStatus = 'Part Time', there will be only 2 students (student1,student2) returned. I spend days try to get the result that I want, but still not solve. I use this article as ref and made some improvement based on my data, but the returned data is incorrect, see the image below: enter image description here So, I only need the marked rows (row#:1,2) to be returned.

The the marked row#:5 is marked by mistake in the image above.

My ts code:

import { Component, OnInit } from '@angular/core';
import * as _ from 'lodash';

@Component({
  selector: 'app-hfo',
  templateUrl: './hfo.component.html',
  styleUrls: ['./hfo.component.css']
})
export class HfoComponent implements OnInit {

  students: any;
  filteredStudents: any;

  // basic info
  Sex: string;
  // child info
  ProgramCategory: string;
  ProgramStatus: string;

  // filter by value
  filters = { };

  constructor() { }

  ngOnInit() {
    /// get all students
    this.students = this.getStudents();
    this.setFilters();
  }

  private setFilters() {
    this.filteredStudents = _.filter(this.students, _.conforms(this.filters) );
  }

  filterMatch(property: string, value: any) {
    this.filters[property] = i => i === value;
    this.setFilters();
  }

  filterMatchSub(property: string, childProperty: string, value: any) {
    this.filters[property] = val => val.find( child => child[childProperty]  === value);
    this.setFilters();
  }

  /// removes filter
  removeFilter(property: string) {
    delete this.filters[property];
    this[property] = null;
    this.ProgramCategory = null;
    this.ProgramStatus = null;
    this.setFilters();
  }

  private getStudents() {
    return JSON.parse(`
    [
      {
          "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"
              }
          ]
       }
  ]
    `);
  }

}

My HTML Code:

<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">
                    <div class="row">
                        <div class="col-sm-3">
                            <div class="form-group">
                                <label for="exampleSelect1">Sex</label>
                                <div class="row">
                                    <div class="col-sm-9">
                                        <select class="form-control" [(ngModel)]="Sex" (change)="filterMatch('Sex', Sex)">
                                            <option value="M">M</option>
                                            <option value="F">F</option>
                                        </select>
                                    </div>
                                    <div class="col-sm-3">
                                        <button class="btn btn-primary" *ngIf="Sex" (click)="removeFilter('Sex')">
                                            Clear
                                        </button>
                                    </div>
                                </div>
                            </div>
                        </div>
                        <div class="col-sm-3">
                            <div class="form-group">
                                <label for="exampleSelect1">ProgramCategory</label>
                                <div class="row">
                                    <div class="col-sm-9">
                                        <select class="form-control" [(ngModel)]="ProgramCategory" (change)="filterMatchSub('Programs', 'ProgramCategory', ProgramCategory)">
                                            <option value="Engineering">Engineering</option>
                                            <option value="HR">HR</option>
                                            <option value="Finance">Finance</option>
                                        </select>
                                    </div>
                                    <div class="col-sm-3">
                                        <button class="btn btn-primary" *ngIf="ProgramCategory" (click)="removeFilter('Programs')">
                                                Clear
                                        </button>
                                    </div>
                                </div>
                            </div>
                        </div>

                        <div class="col-sm-3">
                            <div class="form-group">
                                <label for="exampleSelect1">ProgramStatus</label>
                                <div class="row">
                                    <div class="col-sm-9">
                                        <select class="form-control" [(ngModel)]="ProgramStatus" (change)="filterMatchSub('Programs', 'ProgramStatus', ProgramStatus)">
                                            <option value="Full Time">Full Time</option>
                                            <option value="Part Time">Part Time</option>
                                        </select>
                                    </div>
                                    <div class="col-sm-3">
                                        <button class="btn btn-primary" *ngIf="ProgramStatus" (click)="removeFilter('Programs')">
                                                Clear
                                        </button>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>

                </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 item of filteredStudents ">
                                    <td>{{item.StudentId }}</td>
                                    <td>{{item.StudentName }}</td>
                                    <td>{{item.Sex}}</td>
                                    <td>
                                        {{item.Programs.length}}

                                        <ol *ngFor="let obj of item.Programs">
                                            <li>{{obj.ProgramCategory}} / {{obj.ProgramStatus}}</li>
                                        </ol>
                                    </td>
                                </tr>
                            </tbody>
                        </table>
                    </div>

                </div>
            </div>
        </div>
    </div>
</div>

Help:

Could anyone help me to achieve my goal?

You can change my current ts code or have a new solution, both are welcomed!

Thanks a lot!

Lester
  • 1,112
  • 2
  • 15
  • 22
  • Because the key of `this.filters[property]` is always `Programs`, you're always overwriting the previous selection. For that reason it will only ever apply the latest of the 2 sub-filters – user184994 Jul 12 '18 at 21:21

4 Answers4

3

I've got a solution for you here using reactive forms and rxjs BehaviorSubjects:

https://stackblitz.com/edit/how-to-filter-complex-json-data-new-chind-array-object-xtlbxy

That link has your full solution, but here's the core of the filtering issue I think you had:

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

Filtering for both ProgramCategory and ProgramStatus (where both have to match for the same Program) is a fundamentally different filter than filtering for either separately.

Since you what you want with your two program filters is essentially "only show students who have at least 1 Program that matches all existing filters", you can see in my stack blitz that I group the relevant controls into a FormGroup and write filters that reflect this intended behavior.

If you're up for it, I'd recommend adjusting your table to using @angular/cdk/table, I'm actually working on an article on that topic now with the guy from Angular Firebase (like in the link you posted). I think that'd be well-worth the effort, especially if you like this more rxjs-centric approach I used in this solution.

ZackDeRose
  • 2,146
  • 1
  • 16
  • 28
  • Hi, thank you for your detailed answer, you solution works great when data structure is simple & few filter options. However, as I said, my data structure is complex(sorry, i didn't provide the full complex data due to privacy), and I need as many as filter options to filter the data, if I use your solution, then I have to write many code, it will not be the flexible way in my situation! Thanks! – Lester Jul 18 '18 at 18:02
  • Updated the link to use FormGroups, should allow you to write a single filter per FormGroup; where a FormGroup would cover all FormControls in a given section. (https://stackblitz.com/edit/how-to-filter-complex-json-data?file=src%2Fapp%2Fhfo%2Fhfo.component.ts) if you had multiple arrays within your `students` level data; so multiple `Programs` it shouldn't be that difficult to make an array/dictionary of FormGroups so you could iterate through each one without writing custom filters per each form group. Let me know if that gets you closer! – ZackDeRose Jul 18 '18 at 19:01
  • You should be able to gather up your ungrouped FormControl as well (like `Sex` in this example) and iterate through each. – ZackDeRose Jul 18 '18 at 19:05
  • It looks good, I will do a test in my project, and is this will work on checkbox as well (like instead of the drop-down list, change it to check box for multi-selection)? – Lester Jul 19 '18 at 17:06
  • yep, formControls work for checkboxes, text inputs, radio buttons, etc and the `ts` is always the same. The markup in your template is going to change though. For the most part, where you used the banana-in-a-box `[(ngModel)]="nameOfVariable"` instead you use `[formControl]="nameOfFormControl"`. – ZackDeRose Jul 19 '18 at 17:41
  • it seems the combinLatest does not allow more than one FormGroup Control? – Lester Jul 23 '18 at 17:55
  • please see this: https://stackblitz.com/edit/how-to-filter-complex-json-data-new-chind-array-object – Lester Jul 23 '18 at 18:13
  • Think this is working: https://stackblitz.com/edit/how-to-filter-complex-json-data-new-chind-array-object-xtlbxy?file=src%2Fapp%2Fhfo%2Fhfo.component.ts I added comments where I saw issues – ZackDeRose Jul 23 '18 at 19:22
  • I can see the mistake, thank you! could you please explain what is line of code means? ```return student.Programs.reduce((programsPrev, program) => { return programsPrev || Object.entries(programFilters).reduce((filterPrev, [filterName, filterValue]) => {``` – Lester Jul 23 '18 at 19:32
  • sure! I'm nesting [`Array.reduce()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) here to get only students that have at least 1 program (the outer `reduce()`) that matches all existing filters for programs (the inner `reduce`). `reduce()` combines all items in an array into a single value, in this case I'm just filtering to a boolean. I'd recommend breaking both the inner and outer filter into their own named function for readability's sake. (I took some shortcuts for time :\\) – ZackDeRose Jul 23 '18 at 20:46
  • Also my [Tables CDK article](https://medium.com/@zackderose/angular-cdk-tables-1537774d7c99) (and [video](https://www.youtube.com/watch?v=lLwhEKLIXa8&t=8s)) came out last week - I'd recommend giving it a look, since I think CDK tables could really help you here! – ZackDeRose Jul 23 '18 at 20:51
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/176652/discussion-between-lester-and-zackderose). – Lester Jul 24 '18 at 13:38
1

Set your filters and then call the following method with the appropriate values.

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);
})
VtoCorleone
  • 16,813
  • 5
  • 37
  • 51
1

Because the key of this.filters[property] is always Programs, you're always overwriting the previous selection. For that reason it will only ever apply the latest of the 2 sub-filters.

Instead, you should check if a filter is already defined for this.filters[property]. If it is, make sure it is also checked.

You can modify your filterMatchSub like so:

 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

user184994
  • 17,791
  • 1
  • 46
  • 52
  • Hi, thank you for your quick response! I tried your solution and it worked. However, when I remove the disable property from the select drop-down html, and select different option, the result is also incorrect. (you can try copy my new html code to your code editor to see the problem). So, it means it only works at every first time, but when you change the drop-down option, it doesn't work! Could you please help? thank you very much! – Lester Jul 13 '18 at 15:55
1

Here is my full take on how this should be handled. Full working exmaple on stackblitz.

Module:

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 { }

Component:

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>
bodorgergely
  • 558
  • 1
  • 3
  • 12