I have a page with three ponents: 1. Products list ponent which gets some products as input and display them. 2. Filters ponent which displays some filters list i.e. (size, colour,...) and also display the added filters. 3. Main ponent which is the root ponent
Let say a user adds 1 filter which fires a http request to get new filtered products and while the request is pending he removes the added filter which fires another http request to fetch all the products How to cancel the first request so we don't display the filtered products? Here is my code:
class FiltersService {
private _filters: any[];
get filters() {
return this._filters;
}
addFilter(filter) {
this._filters.push(filter);
}
removeFilter(filter) {
// Remove filter logic ...
}
}
class DataService_ {
constructor(private http: HttpClient) {
}
getProducts(filters) {
return this.http.post<any[]>('api/get-products', filters)
}
}
@Component({
selector: 'app-main',
template: `
<div>
<app-filters [filtersChanged]="onFiltersChange()"></app-filters>
<app-products-list [products]="products"> </app-products-list>
</div>
`
})
class MainComponent {
products: any[];
constructor(private dataService: DataService_, private filtersService: FiltersService) {
}
ngOnInit() {
this.setProducts()
}
setProducts() {
let filters = this.filtersService.filters;
this.dataService.getProducts(filters)
.subscribe(products => this.products = products)
}
onFiltersChange() {
this.setProducts();
}
}
@Component({
selector: 'app-filters',
template: `
<div>
Filters :
<ul>
<li *ngFor="let filter of filters" (click)="addFilter(filter)"> {{ filter.name }}</li>
</ul>
<hr>
Added Filters:
<ul>
<li *ngFor="let filter of filtersService.filters"> {{ filter.name }} <button (click)="removeFilter(filter)"> Remove</button></li>
</ul>
</div>
`
})
class FiltersComponent {
filters = [{ name: 'L', tag: 'size' }, { name: 'M', tag: 'size' }, { name: 'White', tag: 'colour' }, { name: 'Black', tag: 'colour' }]
@Output() filtersChanged = new EventEmitter()
constructor(public filtersService: FiltersService) {
}
addFilter(filter) {
const isAdded = this.filtersService.filters.find(x => x.name === filter.name);
if (isAdded) return;
this.filtersService.addFilter(filter);
this.filtersChanged.emit()
}
removeFilter(filter) {
this.filtersService.remove(filter);
this.filtersChanged.emit()
}
}
@Component({
selector: 'app-products-list',
template: `
<div>
<h1>Products</h1>
<ul *ngIf="products.length">
<li *ngFor="let product of products">
{{product.name }}
</li>
</ul>
</div>
`
})
class ProductsListComponent {
@Input() products
constructor() {
}
}
I have a page with three ponents: 1. Products list ponent which gets some products as input and display them. 2. Filters ponent which displays some filters list i.e. (size, colour,...) and also display the added filters. 3. Main ponent which is the root ponent
Let say a user adds 1 filter which fires a http request to get new filtered products and while the request is pending he removes the added filter which fires another http request to fetch all the products How to cancel the first request so we don't display the filtered products? Here is my code:
class FiltersService {
private _filters: any[];
get filters() {
return this._filters;
}
addFilter(filter) {
this._filters.push(filter);
}
removeFilter(filter) {
// Remove filter logic ...
}
}
class DataService_ {
constructor(private http: HttpClient) {
}
getProducts(filters) {
return this.http.post<any[]>('api/get-products', filters)
}
}
@Component({
selector: 'app-main',
template: `
<div>
<app-filters [filtersChanged]="onFiltersChange()"></app-filters>
<app-products-list [products]="products"> </app-products-list>
</div>
`
})
class MainComponent {
products: any[];
constructor(private dataService: DataService_, private filtersService: FiltersService) {
}
ngOnInit() {
this.setProducts()
}
setProducts() {
let filters = this.filtersService.filters;
this.dataService.getProducts(filters)
.subscribe(products => this.products = products)
}
onFiltersChange() {
this.setProducts();
}
}
@Component({
selector: 'app-filters',
template: `
<div>
Filters :
<ul>
<li *ngFor="let filter of filters" (click)="addFilter(filter)"> {{ filter.name }}</li>
</ul>
<hr>
Added Filters:
<ul>
<li *ngFor="let filter of filtersService.filters"> {{ filter.name }} <button (click)="removeFilter(filter)"> Remove</button></li>
</ul>
</div>
`
})
class FiltersComponent {
filters = [{ name: 'L', tag: 'size' }, { name: 'M', tag: 'size' }, { name: 'White', tag: 'colour' }, { name: 'Black', tag: 'colour' }]
@Output() filtersChanged = new EventEmitter()
constructor(public filtersService: FiltersService) {
}
addFilter(filter) {
const isAdded = this.filtersService.filters.find(x => x.name === filter.name);
if (isAdded) return;
this.filtersService.addFilter(filter);
this.filtersChanged.emit()
}
removeFilter(filter) {
this.filtersService.remove(filter);
this.filtersChanged.emit()
}
}
@Component({
selector: 'app-products-list',
template: `
<div>
<h1>Products</h1>
<ul *ngIf="products.length">
<li *ngFor="let product of products">
{{product.name }}
</li>
</ul>
</div>
`
})
class ProductsListComponent {
@Input() products
constructor() {
}
}
Share
Improve this question
edited Aug 30, 2018 at 11:41
Ishara Dayarathna
3,6015 gold badges27 silver badges43 bronze badges
asked Aug 30, 2018 at 11:38
SMHSMH
1,0993 gold badges15 silver badges32 bronze badges
2 Answers
Reset to default 10Long story short:
Easiest way to handle such situations is by using the switchMap operator. What this does is cancel the internal subscription as soon as a new event es along.
One implementation would be:
class MainComponent {
products: any[];
private _filters$ = new Subject();
constructor(private dataService: DataService_, private filtersService: FiltersService) {
}
ngOnInit() {
this.setProducts()
}
setProducts() {
this._filters$
.switchMap((filters)=> this.dataService.getProducts(filters)) // or .let(switchMap...) if you are using rxjs >5.5
.subscribe(products => this.products = products);
}
onFiltersChange() {
this._filters$.next(this.filtersService.filters);
}
}
Long story:
What happens here is: When you change filter the onFilterChange is triggered. You then emit the latest filters (inside this.filtersService.filters) through the _filters$ Subject (a subject is almost identical to an EventEmitter).
Back in time during ponent initialization the ngOnInit method has called setProducts, which has subscribed to the _filters$ subject for future events (none has happened at this point). When an event arrives on _filters$ then we trigger the getProducts method of dataservice, passing it the filters that where contained in the event. We will be waiting on this line until the http call has pleted. As soon as it pletes the result of the http call will be assigned to the products of the ponent.
If while we are waiting for the http response to get back, onFiltersChange is fired again, then a new event will arive at the switchMap and it will cancel the previous http request so that it can handle the new event.
This is a very powerful approach as changing a single operator, you can easily change the behavior of your app. For instance, changing switchMap to concatMap will make the request wait for the previous one to plete (will happen serially). Changing it to flatMap will have the same behaviour as the original code you posted (http requests will happen as soon as filters change, without affecting previous ones, order of responses will not predictable) and so on.
Note : to cancel the request just use unsubscribe.
For exmple
const course$ = this.service$.getCourses(`/api/courses`).subscribe(courses => { console.log(courses) }
setTimeout(() => course$.unsubscribe(),1000) // cancel the request