Reputation: 337
I have two arrays which have mutual ID, so I am able to connect them together but I need help with filtering.
So I have a list of first array and check box in front of it. If I select one check box, I am performing filtering and I am displaying another item from second array inside the table (by checking up the mutual ID).
I need help because if I perform multiple selection, I expect to have multiple results, and not just one. This is the image how it looks like and the code I am using for filtering.
So if I select check box for Item 1 and Item 2 I expect to have 3 results (ids 10, 20(x2)) from apps array in the lower table. Of course if I deselect check box, they should be removed from the lower table. Because I am using only .filter method, I am able to get only one result (from last selected checkbox).
let items = [
{id:1, name:'Item 1', appId:10},
{id:2, name:'Item 2', appId:20},
{id:3, name:'Item 3', appId:20},
{id:4, name:'Item 4', appId:30}
]
let apps = [
{id:10, address:'Some street 1', city:'City 1'},
{id:20, address:'Some street 2', city:'City 2'},
{id:20, address:'Some street 2', city:'City 2'},
{id:30, address:'Some street 3', city:'City 3'}
]
this.dataSource = this.items.filter(x => x.appId == apps.id)
Thanks
UPDATE
I have managed to do it by Rohit's answer, but I have a problem now because when I select check box in first array, all previously selected check boxes in second array are being removed, so the question is how to keep them?
I have added new images, before and after, so after selecting check box Item 1, I expect check boxes for Some street 9 and Some street 10 to remain selected.
My html.
<table mat-table [dataSource]="dataSourceObjects" class="mat-elevation-z8">
<ng-container matColumnDef="objectChBox">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let element">
<mat-checkbox
class=""
[checked]="element.chbxActive"
(change)="checkSingleObject($event, element)"
>
</mat-checkbox>
</td>
</ng-container>
<ng-container matColumnDef="address">
<th mat-header-cell *matHeaderCellDef>Address</th>
<td mat-cell *matCellDef="let element">{{element.address}}</td>
</ng-container>
<ng-container matColumnDef="city">
<th mat-header-cell *matHeaderCellDef>City</th>
<td mat-cell *matCellDef="let element">{{element.city}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumnsObjects"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumnsObjects;"></tr>
</table>
Upvotes: 1
Views: 567
Reputation: 27202
Observation as per your current implementation, As apps
is an array. It's items should be access via index
, we can not directly access using dot(.) notation.
As user can select multiple items, we can just check for the id
in the apps array based on the appId
selected from the items
array using checkbox.
Just for a demo purpose, I am directly assigning the appId
10 and 20 in an array and then will fetch the mapped results from apps
array based on Array#filter
method along with Array#includes
method.
// Apps array
let apps = [
{id:10, address:'Some street 1', city:'City 1'},
{id:20, address:'Some street 2', city:'City 2'},
{id:20, address:'Some street 2', city:'City 2'},
{id:30, address:'Some street 3', city:'City 3'}
];
// Selected items array.
const selctedItems = [10, 20];
const result = apps.filter(({ id }) => selctedItems.includes(id));
// result
console.log(result);
Update : Full working demo as per the author request (Descriptive comments have been added in the below code snippet itself).
// Input Array 1
let items = [
{id:1, name:'Item 1', appId:10},
{id:2, name:'Item 2', appId:20},
{id:3, name:'Item 3', appId:20},
{id:4, name:'Item 4', appId:30}
];
// Input Array 2
const apps = [
{id:10, address:'Some street 1', city:'City 1'},
{id:20, address:'Some street 2', city:'City 2'},
{id:30, address:'Some street 3', city:'City 3'}
];
// Element on which checkboxes will get bind.
var myDiv = document.getElementById("checkBoxes");
// Loop through each object of "items" array to create checkbox dynamically and bind in the "myDiv" variable which referes to "checkBoxes" element.
for (var i = 0; i < items.length; i++) {
var checkBox = document.createElement("input");
var label = document.createElement("label");
checkBox.type = "checkbox";
checkBox.value = items[i].appId;
myDiv.appendChild(checkBox);
myDiv.appendChild(label);
label.appendChild(document.createTextNode(items[i].name));
}
// Getting all the checkboxes elements for iteration.
var cbs = document.querySelectorAll('input[type="checkbox"]');
// This variable will hold the selected checkboxes values.
let selectedItems = [];
// Iterate over each checkbox element and triggering an "onchange" event.
cbs.forEach(function(item) {
item.onchange = getCheckBoxValue;
});
// Method which is responsible to get all the selected checkbox values into an array.
function getCheckBoxValue() {
selectedItems = [];
cbs.forEach(function(item) {
if (item.checked) {
if (!selectedItems.includes(item.value)) {
selectedItems.push(item.value)
}
}
});
getFilteredValuesFromAnotherArr();
}
// Final method which is basically used to fetch the selected values from a second array based on the first array selections.
function getFilteredValuesFromAnotherArr() {
console.log(apps.filter(({ id }) => selectedItems.includes(id.toString())));
}
<div id="checkBoxes"></div>
Upvotes: 3
Reputation: 119
My advice would be to merge the two arrays. This will remove a lot of complexity and make your code super easy to read. There are many ways to solve this problem, but the following is how I would do it.
Firstly I create a dictionary for the app
array using the reduce
function. This gives me the benefit of having indexes which correlate with the ids in the items array, allowing me to directly access each app item without looping the entire array.
Once I have the dictionary, I can easily perform the merge by mapping the items array and creating a new object for each item containing the original item + the app item from the appDict
.
Now you have a single array with everything you need to solve the problem, and you remain within the O(n) time complexity.
const items = [
{id:1, name:'Item 1', appId:10},
{id:2, name:'Item 2', appId:20},
{id:3, name:'Item 3', appId:20},
{id:4, name:'Item 4', appId:30}
]
const apps = [
{id:10, address:'Some street 1', city:'City 1'},
{id:20, address:'Some street 2', city:'City 2'},
{id:30, address:'Some street 3', city:'City 3'}
];
const appDict = apps.reduce((acc, {id, ...rest})=> ({...acc, [id]: rest}), {})
const combinedArr = items.map(item=>({...item, ...appDict[item.appId]}))
Upvotes: 1
Reputation: 135227
The answer using .filter(...includes...).map(...)
combines nested linear operations which results in quadratic time complexity. The answer using .filter(...findIndex...)
has the same problem.
Here's a minimal reproducible example. This solution uses -
Set
for fast O(1) lookupsuseMemo
to avoid wasted computationflatMap
to combine filter->map
in a single passfunction App({ items, apps }) {
const [selected, setSelected] = React.useState(_ => new Set())
const selectedApps = React.useMemo(
() => new Set(Array.from(selected, s => s.appId)),
[selected]
)
const select = item => event => {
const s = new Set(selected)
if (s.has(item)) s.delete(item)
else s.add(item)
setSelected(s)
}
return <div>
{items.map((item, key) =>
<div>
<input
key={key}
type="checkbox"
checked={selected.has(item)}
onClick={select(item)}
/>
{item.name}
</div>
)}
{apps.flatMap((app, key) =>
selectedApps.has(app.id)
? [<pre key={key}>{JSON.stringify(app)}</pre>]
: []
)}
</div>
}
const items = [{id:1, name:'Item 1', appId:10},{id:2, name:'Item 2', appId:20},{id:3, name:'Item 3', appId:20},{id:4, name:'Item 4', appId:30}]
const apps = [{id:10, address:'Some street 1', city:'City 1'},{id:20, address:'Some street 2', city:'City 2'},{id:20, address:'Some street 2', city:'City 2'},{id:30, address:'Some street 3', city:'City 3'}]
ReactDOM.render(<App items={items} apps={apps} />, document.body)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
Upvotes: 0
Reputation: 73
[UPDATED]
This code should suit your needs exactly:
let items = [
{id: 1, name: 'Item 1', appId: 10},
{id: 2, name: 'Item 2', appId: 20},
{id: 3, name: 'Item 3', appId: 20},
{id: 4, name: 'Item 4', appId: 30}
]
let apps = [
{id: 10, address: 'Some street 1', city: 'City 1'},
{id: 20, address: 'Some street 2', city: 'City 2'},
{id: 20, address: 'Some street 2', city: 'City 2'},
{id: 30, address: 'Some street 3', city: 'City 3'}
]
this.dataSource = items.filter(x => apps.findIndex(y => y.id === x.appId) >= 0);
Explanation
We run filter
on the items
array, which goes through every element of that array, and checks if the function you pass in returns true
or not for that element. It adds elements that pass this test to a new array, and returns that array.
The function we pass in for that check, x => apps.findIndex(y => y.id === x.appId) >= 0
uses another utility function in Arrays, findIndex
, which works a lot like filter. The exception is that it only returns one item, and the place it is in the array. We pass in the function y => y.id === x.appId
, which sees if the item we're checking in the apps
array is the one we're looking for. If it doesn't find the item, it returns -1
, so we just check against it.
I hope this helps~!!
Upvotes: 0
Reputation: 2613
If you want to keep track of multiple items, consider this simple example. Pretend App ID 10 and 30 are selected. You need to map the app array to contain the original information as well as the selected-item results like this:
let items = [
{ id: 1, name: "Item 1", appId: 10 },
{ id: 2, name: "Item 2", appId: 20 },
{ id: 3, name: "Item 3", appId: 20 },
{ id: 4, name: "Item 4", appId: 30 },
];
let apps = [
{ id: 10, address: "Some street 1", city: "City 1" },
{ id: 20, address: "Some street 2", city: "City 2" },
{ id: 20, address: "Some street 2", city: "City 2" },
{ id: 30, address: "Some street 3", city: "City 3" },
];
const selectedAppIDs = [10, 30];
const result = apps
.filter((app) => selectedAppIDs.includes(app.id))
.map((app) => ({
...app,
selectedItems: items.filter((x) => x.appId === app.id),
}));
console.log(JSON.stringify(result));
Results:
[{"id":10,"address":"Some street 1","city":"City 1","selectedItems":[{"id":1,"name":"Item 1","appId":10}]},{"id":30,"address":"Some street 3","city":"City 3","selectedItems":[{"id":4,"name":"Item 4","appId":30}]}]
Upvotes: 0