TypeScript enables us to extend existing classes (in order to create new ones) and Angular includes support for component inheritance.

In order to minimise code duplication we can move common code into a base class.

For example:

import { Injector, OnInit, OnDestroy, Type } from '@angular/core';

import { Loading, LoadingController } from 'ionic-angular';

import { Subscription } from 'rxjs/Subscription';

import { LoggerService, StaticInjectorService } from '@app/core';

export abstract class CollectionPage implements OnInit, OnDestroy {

  protected loading = (() => {

    let loadMessage: Loading;

    return {

      present: () => {

        if (!loadMessage) {
          loadMessage = this.loadingCtrl.create({
            spinner: 'bubbles'
          });
        }

        loadMessage.present();

      },

      dismiss: () => {

        if (loadMessage) {
          loadMessage.dismiss().then(() => {
            loadMessage = null;
          });
        }

      }

    };

  })();
  
  protected loadingCtrl: LoadingController;
  protected logger: LoggerService;
  protected subscription: Subscription;
  
  constructor() {

    const injector: Injector = StaticInjectorService.getInjector();
    this.loadingCtrl = injector.get<LoadingController>(LoadingController as Type<LoadingController>);
    this.logger = injector.get<LoggerService>(LoggerService as Type<LoggerService>);
  }

  public ngOnInit() {
    this.subscribe();
  }

  protected subscribe() {
  }

  protected unsubscribe() {
    
    if (this.subscription) {
      this.subscription.unsubscribe();
    }
  }

  public ngOnDestroy() {
    this.unsubscribe();
  }

}

Abstract classes are base classes from which other classes may be derived. They may not be instantiated directly. Unlike an interface, an abstract class may contain implementation details for its members.

A derived class:

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

import { IonicPage, NavController, NavParams } from 'ionic-angular';

import { EventsService } from '@app/core';
import { Event } from '@core/models';

import { CollectionPage } from '@pages/abstract/collection.page';

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

  public items: Array<Event>;
  
  constructor(public navCtrl: NavController,
              public navParams: NavParams,
              private eventsService: EventsService) {

    super();
  }
  
  protected subscribe() {

    this.loading.present();

    this.subscription = this.eventsService.list().subscribe(data => {
      
      this.items = data;
      this.loading.dismiss();
    });

  }  
  
}  

Notice the super() call in the derived classes constructor, because the base class uses static injection we don't need to pass any constructor arguments.

The Static Injector service:

import { Injector } from '@angular/core';

export class StaticInjectorService {

  private static injector: Injector;

  static setInjector(injector: Injector) {
    StaticInjectorService.injector = injector;
  }

  static getInjector(): Injector {
    return StaticInjectorService.injector;
  }

}

main.ts:

...

platformBrowserDynamic().bootstrapModule(AppModule).then((moduleRef) => {

  StaticInjectorService.setInjector(moduleRef.injector);

  ...

}).catch(error => console.log(error));
References: