Home > database >  Angular - How to debounce PipeTransform?
Angular - How to debounce PipeTransform?

Time:09-30

I am using *ngFor to display a collection, I also have one input which will be used as a filter criteria (this input is a formControl). Now I have added a PipeTransform to filter my collection based on that input. Everything works as expected but I can't figure out how to debounce. I want my filterPipe to be called 0.5s after last keystroke inside input but at the moment it is called instantly after any change inside my input.

HTML:


    <div class="test-ctr">
      <div class="form-ctr">
        <form class="example-form" [formGroup]="filterForm">>
          <input formControlName="nick" placeholder="ex. hdw"> 
        </form>
      </div>
      <div class="app-cards-ctr">
        <div class="app-card-ctr" *ngFor="let nick of nicks | filterPipe: filterForm.get('nick')?.value">
          <app-card [nick]="nick"></app-card>
        </div>
      </div>
    </div>

Component.ts:


    import { Component, OnInit } from '@angular/core';
    import { FormBuilder } from '@angular/forms';
    
    @Component({
      selector: 'app-test',
      templateUrl: './test.component.html',
      styleUrls: ['./test.component.scss'],
    })
    export class TestComponent implements OnInit {
      nicks: string[] = ['Siema', 'Elo', 'Tu', 'Hdw3DCtV', 'and', 'Gównow', 'Zdzisiu11', 'Zdzisiu1','Zdzisiu2','Zdzisiu3','Zdzisiu4','Zdzisiu5'];
    
      constructor(private formBuilder: FormBuilder) {}
    
      filterForm = this.formBuilder.group({
        nick: ['']
      })
    
      ngOnInit(): void {}
    }

Pipe.ts:


    import { Pipe, PipeTransform } from '@angular/core';
    
    @Pipe({ name: 'filterPipe', pure: true })
    export class FilterPipe implements PipeTransform {
      transform(value: string[], arg: string): string[] {
        console.log(arg);
        return value.filter(
          (e) => e.toLowerCase().indexOf(arg.toLowerCase()) !== -1
        );
      }
    }

CodePudding user response:

Working example in Stackblitz

HTML:

<h3>Filter:</h3>
<form [formGroup]="filterForm">
  <input type="text" formControlName="nick" />
</form>

<h3>Nicks:</h3>
<div *ngFor="let nick of nicks | filterPipe: (debouncedControl$ | async)">
  {{ nick }}
</div>

Component:

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  nicks: string[] = [
    'Siema',
    'Elo',
    'Tu',
    'Hdw3DCtV',
    'and',
    'Gównow',
    'Zdzisiu11',
    'Zdzisiu1',
    'Zdzisiu2',
    'Zdzisiu3',
    'Zdzisiu4',
    'Zdzisiu5',
  ];

  constructor(private formBuilder: FormBuilder) {}

  filterForm = this.formBuilder.group({
    nick: [''],
  });

  debouncedControl$ = this.filterForm.controls.nick.valueChanges.pipe(
    startWith(''),
    debounceTime(500)
  );
}

Pipe:

@Pipe({
  name: 'filterPipe',
})
export class FilterPipe implements PipeTransform {
  transform(values: string[], inputValue: string): string[] {
    return values.filter((v) => {
      return (
        v.toLowerCase().indexOf((inputValue || '').toLocaleLowerCase()) > -1
      );
    });
  }
}

All that said, this is probably not a scenario where I would reach for an angular pipe.

Alternate component solution:

@Component({
  selector: 'alternate',
  template: `
    <form [formGroup]="filterForm">
        <input type="text" formControlName="nick" />
    </form>

    <ul>
      <li *ngFor="let nick of filteredNicks$ | async"> {{nick}} </li>
    </ul>
  `,
})
export class AlternateSolutionComponent {
  constructor(private formBuilder: FormBuilder) {}

  nicks = ['Siema', 'Elo', 'Tu'];

  lowercaseIncludes(a: string, b: string): boolean {
    return a.toLocaleLowerCase().indexOf(b.toLocaleLowerCase()) > -1;
  }

  filterForm = this.formBuilder.group({ nick: [''] });

  filteredNicks$ = this.filterForm.controls.nick.valueChanges.pipe(
    startWith(''),
    debounceTime(500),
    map((inputValue) => {
      return this.nicks.filter((nick) =>
        this.lowercaseIncludes(nick, inputValue)
      );
    })
  );
}

CodePudding user response:

For me, best approach is to use the pipe inside the component and not in the template.

constructor(
  private filterPipe: FilterPipe
) {}

ngOnInit() {
 this.filterForm.controls.nick.valueChanges.pipe(
    startWith(''),
    debounceTime(500),
    map(value => {
      this.nicks.forEach(nick => {
          nick = this.filterPipe.transform(value, nick); // or what you want to do
      });
    })
  );
}
  • Related