Every application starts out with what seems like a simple task: get some data, transform it, and then show it to your users.

You can use a service to fetch data from your server and a pipe to transform it.

However, ...

Angular doesn't provide pipes for filtering or sorting lists.

Angular doesn't provide them because they perform poorly and prevent aggressive minification.

The Angular team strongly recommends moving filtering and sorting logic into the component itself.

For example:

import { Component } from '@angular/core';
import { DatePipe } from '@angular/common';

...

export interface Event {
  id?: string;
  name?: string;
  description?: string;
  ...
  startDate?: firebase.firestore.Timestamp;
  endDate?: firebase.firestore.Timestamp;
}

export interface GroupedByEvent {
  key?: string;
  items?: Event[];
}

...

@IonicPage({
  name: 'EventsPage',
  segment: 'events',
  defaultHistory: ['TabsPage']
})
@Component({
  selector: 'page-events',
  templateUrl: './events.page.html'
})
export class EventsPage extends CollectionPage {

  private static readonly DATE_FORMAT = 'dd MMMM yyyy';
  private static readonly GROUP_BY_KEY = 'startDate';

  public items: Array<Event>;
  public groupedItems: GroupedByEvent;

  constructor(public navCtrl: NavController,
              public navParams: NavParams,
              private eventsService: EventsService,
              private datePipe: DatePipe) {

    super();
  }

  protected subscribe() {

    this.loading.present();

    this.subscription = this.eventsService.list().subscribe(data => {
      
      this.items = data;
      this.groupedItems = this.groupByDate(this.items, EventsPage.GROUP_BY_KEY);
      
      this.loading.dismiss();
    });
  }

  private groupByDate(input: any, groupByKey: string) {

    const events: any[] = [];
    const groupedElements: any = {};

    input.forEach((obj: any) => {

      const timestamp: firebase.firestore.Timestamp = obj[groupByKey];
      const date: Date = timestamp.toDate();
      const dateString = this.datePipe.transform(date, EventsPage.DATE_FORMAT);

      if (!(dateString in groupedElements)) {
        groupedElements[dateString] = [];
      }

      groupedElements[dateString].push(obj);
    });

    for (const property in groupedElements) {

      if (groupedElements.hasOwnProperty(property)) {
        events.push({
          key: property,
          items: groupedElements[property]
        });
      }
    }

    return events;
  }

}

events.page.module.ts:

import { NgModule } from '@angular/core';
import { DatePipe } from '@angular/common';

import { IonicPageModule } from 'ionic-angular';

import { TranslateModule } from '@ngx-translate/core';

import { EventsPage } from './events.page';

@NgModule({
  declarations: [ EventsPage ],
  imports: [
    IonicPageModule.forChild(EventsPage),
    TranslateModule.forChild()
  ],
  providers: [ DatePipe ]
})
export class EventsPageModule {}

events.page.html:

<ion-header>

  ...

</ion-header>

<ion-content>

  <ng-container *ngIf='!groupedItems; then skeleton'> </ng-container>

  <ng-container *ngIf='groupedItems;'>
  
    <!-- To divide groups of items, use <ion-item-group> instead of <ion-list> -->

    <ion-item-group *ngFor="let group of groupedItems">

      <ion-item-divider>
        {{ group.key }}
      </ion-item-divider>

      <button ion-item *ngFor="let item of group.items">

        ...

      </button>

    </ion-item-group>

  </ng-container>

  <!-- Skeleton template -->

  <ng-template #skeleton>

    <ion-list class='fakeItem'>

      ...

    </ion-list>

  </ng-template>

</ion-content>

While the data is being retrieved we can display a skeleton page and a loading indicator:

Events grouped by start date:

References: