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 !
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! :-)