I'm trying to sort a Sector
array and a Province
array based on the language.
sector.ts
:
export interface Sector {
id: string;
Label_FR: string;
Label_NL: string;
}
province.ts
:
export interface Province {
id: string;
frenchName: string;
dutchName: string;
}
constructor(
private readonly _translateService: TranslateService,
private statisticsManager: StatisticsManager) {
let currentLang = _translateService.currentLang; // 'fr' / 'nl'
statisticsManager.sectors$.pipe(takeUntil(this.onDestroy$))
.subscribe((sector: Sector[]) => (this.sectorOptions = sector));
statisticsManager.provinces$.pipe(takeUntil(this.onDestroy$))
.subscribe((province: Province[]) => (this.provinceOptions = province));
this._translateService.onLangChange.subscribe(() => {
this.sortFilters();
})
}
sortFilters() {
let currentLang = this._translateService.currentLang;
this.sectorOptions = this.sectorOptions.sort((a, b) => {
if (currentLang == 'fr') {
return this.sortFunc(a.Label_FR, b.Label_FR);
} else {
return this.sortFunc(a.Label_NL, b.Label_NL);
}
});
this.provinceOptions = this.provinceOptions.sort((a, b) => {
if (currentLang == 'fr') {
return this.sortFunc(a.frenchName, b.frenchName);
} else {
return this.sortFunc(a.dutchName, b.dutchName);
}
});
}
sortFunc(obj1: any, obj2: any) {
if (obj1 > obj2) {
return 1;
} else if (obj1 < obj2) {
return -1;
} else {
return 0;
}
}
The code does the job but is really 'long' for that kind of task, is there any short / optimisation to achieve the same result?
1 Answer 1
Constructor
The constructor should mostly just be used for injection:
currentLang: string // Declare members above constructor
constructor(
private readonly _translateService: TranslateService,
private statisticsManager: StatisticsManager
) { }
OnInit
Initialization should go in ngOnInit
:
ngOnInit(): void {
this.currentLang = this._translateService.currentLang;
}
Don't forget to implement the interface...
Observables
I also prefer to declare any Observables above the constructor:
sector$ : Observable<Sector[]> = this.statisticsManager.sectors$
provinces$ : Observable<Province[]> = this.statisticsManager.provinces$
And in the template we can then use an async pipe on them, e.g.:
*ngFor="let sector of sort((sector$ | async)!)"
The Sorting Function
And for the sort function:
sortSectors(sectors: Sector[]) {
let currentLangProperty = getCurentLangProvinceProperty()
return sectors?.sort(
(a, b) => (a as any)[currentLangProperty].toLocaleLowerCase().localeCompare((b as any)[currentLangProperty].toLocaleLowerCase())
)
}
sortProvinces(provinces: Province[]) {
let currentLangProperty = getCurentLangProvinceProperty()
return provinces?.sort(
(a, b) => (a as any)[currentLangProperty].toLocaleLowerCase().localeCompare((b as any)[currentLangProperty].toLocaleLowerCase())
)
}
getCurrentLangSectorProperty(){
return 'Label_' + this._translateService.currentLang.toUpperCase()
}
getCurrentLangProvinceProperty(){
return this.currentLang === 'fr' ? 'frenchName' : 'dutchName'
}
You might be able to further generify this by using toBeSorted: Sector[] | Province[] as parameter.
This way, you can use the OnPush ChangeDetectionStrategy:
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
// ...
})
You can be sure that subscriptions are handled automatically, and don't need an onDestroy
.
Pipes
Although the functions work perfectly, you might want to consider generating and implementing an angular pipe.
Binding functions to the template might cause them to be triggered way more often than expected when change in the template are detected. Pipes by default are pure, which means angular trusts them to return the same result when the input stays the same. Thus, angular will not run execute the pipe again as long as the input doesn't change.
It could look something like this:
@Pipe({
name: 'sort'
})
export class SortPipe implements PipeTransform {
transform(sectors: Sector[], ...args: string[]): Sector[] {
let currentLangProperty = args[0]
return sectors?.sort(
(a, b) => (a as any)[currentLangProperty].toLocaleLowerCase().localeCompare((b as any)[currentLangProperty].toLocaleLowerCase())
)
}
}
Don't forget to add the pipe to your declarations.
Using it in the template:
<p *ngFor="sectors | sort : getCurentLangProvinceProperty()"></p>
It is a little less useful, but they are also usable in script:
constructor(private sortPipe : SortPipe)
/* ... */
sortPipe.transform(this.sectors, this.getCurentLangProvinceProperty())
```
localeCompare
forsortFunc
implementation? \$\endgroup\$