Reputation: 6941
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
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
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
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