1
\$\begingroup\$

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?

\$\endgroup\$
1
  • 2
    \$\begingroup\$ Have you considered localeCompare for sortFunc implementation? \$\endgroup\$
    – sschmeck
    Oct 13, 2021 at 9:29

1 Answer 1

5
\$\begingroup\$

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())
```
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.