Search code examples
angulartypescriptrxjsrxjs6rxjs-observables

Angular/RXJS, How do I sort an Observable Array of objects based on a SPECIFIC String value?


I have an interesting problem (I think) and would love some help.

I am returning an Observable[DisplayCard[]] that is filtered based on a value. That all works great. After I filter that array based on a value, I NOW want to sort the returned array by another one of it's values called category. category can only be one of three String values: ONGOING, UPCOMING, and PAST.

I want to return an array that will sort like this: ONGOING --> UPCOMING --> PAST. So, all the display cards that have "ONGOING" will be in front, after there is no more of those, I want all the "UPCOMING" cards and then after those, I want all the "PAST" cards to show.

Here is my code:

displayCommunityCards$ = this.cardService.displayCards$
    .pipe(
      map(communityEventsCards => communityEventsCards.filter(community => community.type === 'COMMUNITY EVENT')
        .sort((a, b) => {
          return ???
        }),
      tap(data => console.log('Community Cards: ', JSON.stringify(data))),
    );

Solution

  • You can achieve this by creating an enum to represent all possible values for CardCategory and defining the order they should be sorted in using Record<CardCategory, number>:

    export enum CardCategory {
      OnGoing  = 'ONGOING',
      Upcoming = 'UPCOMING',
      Past     = 'PAST',
    }
    
    export const CategorySortOrder: Record<CardCategory, number> = {
      'ONGOING'  : 0,
      'UPCOMING' : 1,
      'PAST'     : 2
    }
    

    Then create a sort function that uses that to sort:

    function sortByCardCategory(a: DisplayCard, b: DisplayCard) {  
      if(a.category === b.category) return 0;
      return CategorySortOrder[a.category] > CategorySortOrder[b.category] ? 1 : -1;
    }
    

    You can now easily sort like this:

    displayCommunityCards$ = this.cardService.displayCards$.pipe(
      map(cards => cards
        .filter(c => c.type === CardType.CommunityEvent)
        .sort(sortByCardCategory)
      )
    );
    

    Here's a working StackBlitz demo.


    Using the Record type isn't completely necessary, but it does guarantee that you define a sort order for all values of CardCategory. For example, if you added a new CardCategory, typescript will throw an error about CategorySortOrder not defining all values of CardCategory.

    screenshot of error

    Property 'MYNEWCAT' is missing in type
    '{ 
      UPCOMING: number; 
      ONGOING: number; 
      PAST: number; 
    }' 
    but required in type 'Record<CardCategory, number>'. (2741)