nzrytmn
nzrytmn

Reputation: 6941

Angular 9 filter results of async pipe

I am kind of new at angular and I am doing an application to learn better. I have a problem about binding and filtering results of a http call. At below code I try to bind a list of some data.

At service I have a call like this

 getTestFields(): Observable<Engagement[]> {
   return this.httpClient.get<Engagement[]>(this.url + '/testFields');
 }

It basically returns a list of some test fields; and at component.ts I am binding the results of call to an observable variable.

dataSet$: Observable<Engagement[]>;
ngOnInit(): void {
  this.dataSet$ = this.service.getTestFields();
}

And at the html template I bind the data like below:

<table class="table table-hover" *ngIf="dataSet$ | async as resultSet; else loading">
        <tr *ngFor="let item of resultSet" >
            <td>{{item.testName}}</td>
            <td>{{item.planned}}</td>
            <td>{{item.workingOn}}</td>
            <td>{{item.reviewed}}</td>
        </tr>   </table>

Until here I have no problem; I get the data successfully and show in table. The problem is filtering; I couldn't figure out how I can filter the data that I already got from server. I don't want to make server calls for filtering data, I want to filter the current list that I already got.

I tried something like below but it didn't work.

filter() {
    this.dataSet$ = this.dataSet$.pipe(
    switchMap(data => {
      return data.filter(p => p.planned)
    })
  );
 }

How can I filter existing observable list without sending a new call to server ?

Upvotes: 1

Views: 3108

Answers (3)

angellica.araujo
angellica.araujo

Reputation: 298

I came across with a mixed solution; separating responsibilities was the key to get my problem solved; I had an autocomplete implemented within a async pipe to it's own list however I was not able to handle filters in a proper, so here we go:

my-dear-autocomplete.component.ts


import {
  ChangeDetectionStrategy,
  Component,
  OnInit,
  Input,
  Output, EventEmitter
} from '@angular/core';
import { FormControl } from '@angular/forms';
import { MyInterface } from '~interfaces-whatsoever';
import { map, startWith } from 'rxjs/operators';
import { Observable } from 'rxjs';


@Component({...})
export class MyDearAutocompleteComponent
  implements OnInit
{
  @Input() controlName: string;
  @Input() control: FormControl;
  @Input() options: MyInterface[];
  @Output() selectOptionEvent = new EventEmitter<MyInterface>();
  filteredOptions: Observable<MyInterface[]>;

  ngOnInit() {
    this.filteredOptions = this.control.valueChanges.pipe(startWith(''), map(name => this.isOptionNameMatch(name)));
  }

  isOptionNameMatch(value: string): MyInterface[] {
    const clause = (option: MyInterface) => { do your stuff test here };
    return (typeof value === 'string') ? this.options.filter(clause) : this.options;
  }

  selectOption(value: MyInterface) {
    this.selectOptionEvent.emit(value);
  }
}


Where @Input() options: MyInterface[]; is your full list given by component parents using something like:

options$ | async as options; else loading
...
<app-my-dear-autocomplete ... [options]="options" ... ></app-my-dear-autocomplete>

my-dear-autocomplete.component.html

<mat-form-field appearance="fill" fxFlex="100">
  <mat-label>My dear option list</mat-label>
  <input type="text" required matInput [matAutocomplete]="optionAutocomplete" [formControl]="control"/>
  <mat-autocomplete #optionAutocomplete="matAutocomplete" (optionSelected)="selectOption($event.option.value)">
    <mat-option *ngFor="let option of filteredOptions | async" [value]="option">
      {{ option.whatsoever }}
    </mat-option>
  </mat-autocomplete>
</mat-form-field>

Doing it and using the autocomplete like a component subscribing to it output helped me handling the selection events along with other components.

Hope it helps and I am open to suggestions of course

Upvotes: 0

AliF50
AliF50

Reputation: 18839

Try the rxjs map operator coupled with array filter.

Map transforms the observable, so in this case we are using it to transform the array to only include the items where planned is true.

import { map } from 'rxjs/operators';
.....
ngOnInit(): void {
  this.dataSet$ = this.service.getTestFields().pipe(
    map(data => data.filter(p => p.planned)),
  );
}

You can also filter emissions from the Observable using the filter operator of rxjs but I don't think you need it in this case.

================ Use of RxJS Filter ===================

import { filter } from 'rxjs/operators';
...
ngOnInit(): void {
  this.dataSet$ = this.service.getTestFields().pipe(
    // make sure data is truthy and has a length before emitting to 
    // the consumer (in this case it is the HTML).
    filter(data => !!data && !!data.length),
  );
}

Upvotes: 2

Naveen Motwani - AIS
Naveen Motwani - AIS

Reputation: 619

I agree with AliF50, just little tweak as I am assuming you have a filter button or somehow you want to filter out once data is loaded.

dataSet$: Observable<Engagement[]>;
ngOnInit(): void {
 filter();
}

filter() {
    this.dataSet$ = this.service.getTestFields().pipe(
    map(data => data.filter(p => p.planned)),
  );
  );
 }

There is one more scenario that you never want to go to the server once you load the data then you probably need to subscribe at the component level.

like

dataSet: Engagement[];
ngOnInit(): void {
  this.dataSet = this.service.getTestFields().subscribe(response => this.dataSet = 
   response);
}

filter() {
    this.dataSet = this.dataSet.filter(data => data.planned === true)
  }

Upvotes: 1

Related Questions