Angular 2+ - check if Pipe returns an empty subset of original list
Asked Answered
G

7

22

I have a list of strings that I want to iterate through, but I want to be able to filter them using a search term. Like this:

<div *ngFor="#item in list | search: searchTerm">{{ item }}</div>

My question is: how can I check if the pipe returns an empty subset of the list?

In other words if none of the strings matches the search term, I want to display a message saying: "No matches".

Gangrene answered 6/5, 2016 at 7:52 Comment(0)
H
72
<div *ngIf="(list | search: searchTerm).length === 0">
  "No matches"
</div>
<div *ngFor="#item in list | search: searchTerm">{{ item }}</div>

Alternatively you could modify your pipe to return a specific token that indicates that the list is empty

@Pipe({
  name: 'search'
})
export class SearchPipe {

  transform(value, searchTerm) {
    let result = ...
    if(result.length === 0) {
      return [-1];
    }
    return result;
  }
}
<ng-container *ngFor="let item of list | search: searchTerm">
  <div *ngIf="item === -1">"No matches"</div>
  <div *ngIf="item !== -1">{{ item }}</div>
</ng-container>
Herodotus answered 6/5, 2016 at 7:54 Comment(10)
Great! I think the second solution is best, because in the first one you will have to iterate through the list two times, right?Gangrene
isn't there a better way?Olivares
@ValdemarEdvardSandalRolfsen So in the second solution, what happens if I have multiple fields on the {{ item }} object? Say, {{ item.name }}, {{ item.age }} etc?Hematuria
@Rexford I don't understand how this is related to the question or my answer or what the problem could be.Liebermann
@Rexford I agree with Günter, I don't see why that is relevant?Gangrene
Not sure if relevant. However, was just curious what happens in that situation. Still works?Hematuria
I don't understand yet what the situation is you're talking about. Have you tried your code?Liebermann
These comments are a little old, but I'm guessing @Rexford is talking about if the transform return is typed as something like { name: string, age: number }, in which case returning -1 would give a "not assignable" error.Ledford
Thanks! It saves my lot of time. :-)Lillith
I downvoted this answer because it is not typesafe. This solution works only in javascript worlds but would fail in TypeScript, where your would mix the response types with numbers, which might conflict with some other logic.Bingo
B
10

One more way to achieve this is to check the html element for children or in my case the table for rows.

<table #myTable>
  <tr *ngFor="let item of list | somePipe : searchText">
      <td>{{ item.name }}</td>
  </tr>
</table>

<p *ngIf="!myTable.rows.length">No results</p>
Bauxite answered 28/12, 2016 at 15:48 Comment(3)
use element.children.length if #element is not a table (e.g. div)Creuse
this is much better solution than duplicating the filter code or returning an arbitrary value as the accepted answer suggestsCreuse
Doesn't work anymore - gives a ExpressionChangedAfterItHasBeenCheckedError error - at least on Angular 4.4.5Nominate
S
6

This is my code which modified a bit from @Günter Zöchbauer

<div  *ngFor="let filter_list of list | FilterItem" >
    <div *ngIf=" filter_list == -1 " class="alert alert-info">No item found</div>    
    <div *ngIf="filter_list !== -1" *ngFor="let item of filter_list  ; let i = index;" >
        {{ item }}
    </div>
</div>

Pipe code

@Pipe({
  name: 'FilterItem'
})
export class FilterItem {

  transform(list, args?) {
       let result = ...; 
       if ( result && result.length > 0 ){
          return [ result ];
       }else{
          return [ -1 ];
       }
  }
}
Strander answered 3/6, 2016 at 2:56 Comment(0)
P
5

It's possible to leverage dependency injection into pipes. You could inject the component:

Then you can set a property on it to notify this:

@Pipe({
  name: 'search'
})
export class SearchPipe {
  constructor(@Inject(forwardRef(() => SomeComponent)) private comp:SomeComponent) {

  }

  transform(value) {
    var filtered = value.map((v) => v-1);
    this.comp.isEmpty = (filtered.length === 0);
    return filtered;
  }
}

The main drawback is that you link the pipe within the component. The advantage is that filtering is executed once.

Pieria answered 6/5, 2016 at 7:55 Comment(0)
R
2

If your purpose is just render an element than you could do with CSS query it self, i just fallowing Günter Zöchbauer code.

<ng-container *ngFor="let item of list | search: searchTerm">
  <div *ngIf="item !== -1">{{ item }}</div>
  <div class="empty">"No matches"</div>
</ng-container>

CSS

div.empty {
  display:none;
}
div.empty:first-child {
  display:block;
}

.list div.empty {
  display: none;
}

.list div.empty:first-child {
  display: block;
}
<h4>If you hava record to display than</h4>
<div class="list">
  <div>The first record.</div>
  <div>The second record.</div>
  <div>The third record.</div>
  <div class="empty">"No matches"</div>
</div>
<br>
<h4>If no record to show</h4>
<div class="list">
  <div class="empty">"No matches"</div>
</div>
Rodman answered 23/11, 2017 at 9:48 Comment(0)
N
2

With angular now supporting ngIf with variables you could assign the pipe result to a new variable and then do the looping inside the if block

<ng-container *ngIf="search: searchTerm as results; else noItems">
 <!-- else is for cases where search:Search term is undefined or null  -->

    <div *ngFor="let item of results">{{item}}</div>

    <!-- the case where the pipe returns an empty array  -->
    <div *ngIf="!result.length">no items match the search</div>
</ng-container>
<ng-template #noItems>searching...</ng-template>
Nebula answered 3/11, 2018 at 16:2 Comment(1)
The cleanest solution IMHOIvaivah
T
1

This is the cleanest solution I've been able to produce.

@Pipe({
  name: 'search'
})
export class SearchPipe {

  transform(value, searchTerm) {
    let result = ...
    if(result.length === 0) {
      return [undefined];
    }
    return result;
  }
}

By returning [undefined], checks in the DOM are much cleaner and easier to read.

<ng-container *ngFor="let item of list | search: searchTerm">
  <div *ngIf="!item">"No matches"</div>
  <div *ngIf="item">{{ item }}</div>
</ng-container>
Tunable answered 20/4, 2018 at 10:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.