Filter array of objects by property in nested array of objects Filter array of objects by property in nested array of objects angular angular

Filter array of objects by property in nested array of objects


If you just need filtered arrays and counts, you could iterate over the data array once with a forEach and check for each condition, pushing to the appropriate array if any are met. Then you have three filtered arrays and can call the .length method on any of them to get the count.

// app.component.tslet players: any[] = [];let dummies: any[] = [];let unsigned: any[] = [];let signed: any[] = [];this.adminService.GetUnsignedWaivers().subscribe((data: any[]) => {  data.forEach(x => {    if (x.player.email === 'dummypartner@pottstown.com') {      dummies.push(x);    }    x.waivers.forEach(waiver => {      if (waiver.has_signed[0] === true) {        signed.push(waiver);      } else {        unsigned.push(waiver);      }      if (waiver.signatureUrl.length > 0) {        waiver.url = waiver.signatureUrl;        waiver.message = "View Waiver";      }    }); // waiver forEach  }); // data forEach  this.players = data;}); // subscribe

If you would rather use the filter method:

// app.component.tslet players: any[];let dummies: any[];let unsigned: any[] = [];let signed: any[] = [];this.adminService.GetUnsignedWaivers().subscribe((data: any[]) => {  let updatedData = data.map(x => {    x.waivers.forEach(waiver) {      if (waiver.signatureUrl.length > 0) {        waiver.url = waiver.signatureUrl;        waiver.message = "View Waiver";      }    });    return x;  }); // map  this.dummies = updatedData.filter(x => {    x.player.email === 'dummypartner@pottstown.com';  }); // filter  updatedData.forEach(x => {    unsigned = unsigned.concat(x.waivers.filter(w => !w.has_signed[0]));    signed = signed.concat(x.waivers.filter(w => w.has_signed[0]));  }); // forEach  this.players = updatedData;}); // subscribe

Note that since you are using Material Table, you can add a filter with little code:

<!-- app.component.html --><mat-form-field>  <input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter"></mat-form-field>
// app.component.tsapplyFilter(filterValue: string) {  this.dataSource.filter = filterValue.trim().toLowerCase();}


Model

[{event: string,day: string,player: {    first: string,    last: string,    email: string},waivers: [{    url: string,    signatureUrl: string,    email: string,    message: string}],_id: {  id: objectID,  email: string},...]

Expectations

  • this.dummies will filter the results to show any records with a player.email of dummypartner@pottstown.com
  • unsigned will filter the results by the waivers array's has_signed property. It should be set to false
  • signed should filter the results by the nested array waivers' has_signed property being equal to true

Code

this.dummies = data  .filter(item => item.player.email === 'dummypartner@pottstown.com');this.unsigned = data  .filter(item => item.waivers.some(waiver => !waiver.has_signed));this.signed = data  .filter(item => item.waivers.some(waiver => !!waiver.has_signed));

This should be waaaay enough for your needs. I don't get why you have made so much complicated code !

Array.prototype.some documentation


In you component code there is mismatch between what you filter for and the data structure itself. For example:

this.dummies = this.data.filter((x: any) => {  // `x` does not have a `player` property based on `this.data` structure  // The same holds true for all other filter predicates in the file  return x.player.email === "dummypartner@pottstown.com";});

In your second try the problem lies in how you filter your data...

this.unsigned = data.filter(x => {  x.waivers.filter(w => {    return w.has_signed === false;});

The outer filter function does not return, hence the return value is undefined, which is false-y by JS standards. Your unsigned array is always empty because the filter predicate returns a false-y statement no matter what.

Also, your data structure does not contain definition for has_signed. Are you certain it is always present? If has_signed is not present in the data object, w.has_signed is undefined and w.has_signed === false returns false. You can either opt not to use type-safe comparison (== instead of ===), or use JS implicit type conversion capabilities by returning !w.has_signed.

So, I would re-do your filtering like this (apply the same logic to the signed filtering):

// If you want all objects from `data` where at least one object from `wavers` has not signedthis.unsigned = data.filter(x => x.waivers.some(w => !w.has_signed));// ^^ With no curly braces the result of the single statement is also the return value// If you want all the objects from `waivers` with false-y `has_signed`this.unsigned = data.reduce((x, acc) => [...acc, ...x.waivers.filter(w => !w.has_signed)], []);

As for the last part, it is not recommendable to mutate data that you already stored somewhere else. I would suggest making a shallow copy of the objects, like so:

this.players = data.map(p => ({  ...p,  waivers: p.waivers.map(w =>     w.signatureUrl.length > 0      ? { ...w, url: w.signatureUrl, message: "View Waiver" }      : w  )}));

Hope this helps a little! :-)