简体   繁体   中英

How to filter an array of items if item's property contains text

I have an array of AppItems. Each app item has a property that is an array of ProfileItems. I want to filter my AppItems array based on which AppItem's have a Profile who's name contains my search text. It should return True on the first profile that contains the search text.

The problem is I'm for-looping through the profile items within a foreach within the filter function, which I don't think it likes. I have no idea how to do this.

export interface AppState {
  appItems: AppItem[];
}

export interface AppItem {
  profiles: ProfileItem[];
  ...
}

export interface ProfileItem {
  name: string;
  ...
}

appItemsFiltered(state) {
    return state.appItems
    .filter((item: AppItem) => {
      if (!state.filters.searchQuery) return true;

      item.profiles.forEach(function (profile, index) {
        const name = profile.name.toLowerCase()
        const text = state.filters.searchQuery?.toLowerCase();
        const result = name.indexOf(text)
        if (result !== -1) return true;
      })
      return false
    };
  }

If the arrays is:

const array = [
  {profiles: [{name: 'name 10'}, {name: 'name 11'}]},
  {profiles: [{name: 'name 20'}, {name: 'name 21'}]},
  // ...
];

Do filter like:

const filterText = 'name 21';
const result = array.filter(x => x.profiles.some(x => x.name === filterText));

result will be an array of matches.

const hasFound = result.length > 0;

When you return in a forEach it is the same as using continue in a for loop; it just moves on to the next iteration in the loop.

If you are looking for "at least one result is true " then consider using Array.prototype.some , which returns a boolean based on whether a single result in the array resolves as true .

Here's my attempt at re-writing your code with that solution, though with the data provided I can't do any better than this:

appItemsFiltered(state) {
    return state.appItems
    .filter((item: AppItem) => {
      if (!state.filters.searchQuery) return true;

      return item.profiles.some(function (profile, index) {
        const name = profile.name.toLowerCase()
        const text = state.filters.searchQuery?.toLowerCase();
        const result = name.indexOf(text)
        if (result !== -1) return true;
      })
    };
  }

I wrote a snippet that introduces two approaches:

  • join() the strings & search in the resulting string
  • use some() with find()

 const AppState = { appItems: [{ profileItem: ['1.1', '1.2', '1.3'], }, { profileItem: ['2.1', '2.2', '2.3'], }, { profileItem: ['3.1', '3.2', '3.3'], } ] } // creating an HTML representation of the list const itemHtml = (item) => { return `<li>${ item }</li>` } const profileItemsHtml = (profileItems) => { return profileItems.map(item => itemHtml(item)).join('') } const createList = (appItems) => { return appItems.reduce((a, { profileItem }) => { a = [...a, ...profileItem] return a }, []) } // putting out the HTML const updateListContainer = (container, list) => { container.innerHTML = profileItemsHtml(list) } // the full list const fullList = createList(AppState.appItems) const fullListContainer = document.getElementById('full-list') updateListContainer(fullListContainer, fullList) // initiating the filtered list let filteredList1 = fullList const filteredListContainer1 = document.getElementById('filtered-list-1') updateListContainer(filteredListContainer1, filteredList1) let filteredList2 = fullList const filteredListContainer2 = document.getElementById('filtered-list-2') updateListContainer(filteredListContainer2, filteredList2) // setting up filtering on input field input event const filterInput = document.getElementById('filter-input') filterInput.addEventListener('input', function(e) { // FILTER 1: use join() // if the list is made up of only strings, then you // could join them & search the whole string at once // might yield errors in edge cases, but mostly it // should be correct, I think // edge case example: 22 (you can try it) const filtered1 = AppState.appItems.find(({ profileItem }) => profileItem.join('').includes(e.target.value)) if (filtered1 && e.target.value) { updateListContainer(filteredListContainer1, filtered1.profileItem) } else { updateListContainer(filteredListContainer1, fullList) } // FILTER 2: use SOME const filtered2 = AppState.appItems.find(({ profileItem }) => profileItem.some(item => item.includes(e.target.value))) if (filtered2 && e.target.value) { updateListContainer(filteredListContainer2, filtered2.profileItem) } else { updateListContainer(filteredListContainer2, fullList) } })
 .list-container { display: grid; grid-template-columns: repeat(3, 1fr); }
 <div> <label for="filter-input"> Filter: <input type="text" id="filter-input" /> </label> <hr> <div class="list-container"> <div> FULL LIST: <ul id="full-list"></ul> </div> <div> FILTERED WITH JOIN: <ul id="filtered-list-1"></ul> </div> <div> FILTERED WITH SOME: <ul id="filtered-list-2"></ul> </div> </div> </div>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM