FILTER SERVICE - observable for basic data filtering
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { Filter } from '../../models/filter.model';
import { Convert } from '../../utils/converter';
@Injectable({
providedIn: 'root'
})
export class FilterService {
private currentFilter$ = new BehaviorSubject<Filter>({
page: 0,
limit: 20
});
constructor() { }
private init(name: string) {
let filterStr = localStorage.getItem(name);
if (filterStr) {
this.currentFilter$.next(Convert.fromJson<Filter>(filterStr))
} else {
localStorage.setItem(name, Convert.ToJson<Filter>(this.currentFilter$.value));
}
}
private saveFilter(name: string, filter: Filter) {
this.currentFilter$.next(filter);
localStorage.setItem(name, Convert.ToJson<Filter>(filter));
}
public getFilter(name: string) : Observable<Filter> {
this.init(name);
return this.currentFilter$;
}
public nextPage(name: string) {
let filter = this.currentFilter$.value;
filter.page = filter.page 1;
this.saveFilter(name, filter);
}
public pageLimit(name: string, limit: number) {
let filter = this.currentFilter$.value;
filter.limit = limit;
this.saveFilter(name, filter);
}
public scroll() {
let filter = this.currentFilter$.value;
filter.limit = 20;
this.currentFilter$.next(filter);
}
public resetPage(name: string) {
let filter = this.currentFilter$.value;
filter.page = 0;
filter.limit = 20;
this.saveFilter(name, filter);
}
public search(name: string, search: string) {
let filter = this.currentFilter$.value;
filter.search = search;
filter.page = 0;
filter.limit = 20;
this.saveFilter(name, filter);
}
public sort(name: string, column: string, direction: string) {
let filter = this.currentFilter$.value;
filter.limit = 20;
filter.page = 0;
if (direction != '') {
filter.orderBy = column ' ' direction;
} else {
filter.orderBy = undefined;
}
this.saveFilter(name, filter);
}
}
DOCUMENT ROUTE - this component has dynamic parameter type
: 302 = invoice, 306 = delivery note etc.
{ path: 'documents/:type', component: DocumentsComponent },
DOCUMENT COMPONENT
@Component({
selector: 'app-documents',
templateUrl: './documents.component.html',
styleUrls: ['./documents.component.scss']
})
export class DocumentsComponent extends Unsubscriber implements OnInit {
displayedColumns: string[] = ['number', 'customerName', 'date', 'paymentDate', 'settledStatus', 'net', 'vat', 'gross'];
documentsDataSource: Document[] = [];
sortColumn = '';
sortDirection: SortDirection = '';
searchText = '';
title = '';
filters?: FilterItem[] = [];
type = 0;
constructor(
private documentsService: DocumentsService,
private filterService: FilterService,
private route: ActivatedRoute,
private router: Router
) {
super();
this.route.params.subscribe((params) => {
this.type = params["type"];
this.title = this.getTitle(params["type"]);
this.filterService.getFilter(`documents:${this.type}`).subscribe((filter) => {
if (filter.orderBy) {
this.sortColumn = filter.orderBy.split(" ")[0];
this.sortDirection = filter.orderBy.split(" ")[1] === "asc" ? "asc" : "desc";
}
if (filter.search) {
this.searchText = filter.search;
}
this.subscription.add(this.documentsService.getDocuments(filter, params["type"]).subscribe(result => {
this.documentsDataSource = result;
}));
});
});
}
UNSUBSCRIBER
import { Injectable, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
@Injectable()
export abstract class Unsubscriber implements OnDestroy {
subscription = new Subscription();
constructor() { }
ngOnDestroy(): void {
console.log("unsubscribe");
if (this.subscription) {
this.subscription.unsubscribe();
}
}
}
RESULT - With each change of the route (parameter) in the document context I am getting 1 subscription to filters and documents, because the Unsubscriber
is never called. If I change route between different components, like Products
or Customers
and than go back to Documents
everything is fine.
How should I fix this?
CodePudding user response:
I am seeing some anti-patterns here which might be a part of your issue.
Your are creating Subscriptions inside other .subscribe()
callbacks. This should always be avoided by chaining other RxJS operators like switchMap
together.
this.subscription.add(this.route.params.pipe(
switchMap((params) => {
this.type = params["type"];
this.title = this.getTitle(params["type"])
return combineLatest([this.filterService.getFilter(`documents:${this.type}`), of(params])
}),
switchMap(([filter, params]) => {
if (filter.orderBy) {
this.sortColumn = filter.orderBy.split(" ")[0];
this.sortDirection = filter.orderBy.split(" ")[1] === "asc" ? "asc" : "desc";
}
if (filter.search) {
this.searchText = filter.search;
}
return this.documentsService.getDocuments(filter, params["type"])
})
).subscribe(result => {
this.documentsDataSource = result
}))
Now you only have 1 Subscription which can be added to this.subscription
.