Search code examples
angularionic-frameworkcapacitorngondestroy

Angular ngOnDestroy


I've been trying to set up a network plugin as a service on an app to tell whether there's internet or not.

In my example I've set up two components:

  1. HOME component with the network listener implemented directly in it and
  2. TEST component that subscribes to an BehaviourSubject observable in networkService

My problem is when trying to destroy the components as I navigate from one to the other. The component where the app is loaded never gets destroyed. Even if I implement something like @HostListener('window:beforeunload') on top of ngOnDestroy().

I have implemented ngOnDestroy on both components to either remove the listener or unsubscribe to the networkServices's observable respectively.

I noticed that if I refresh the app on say Home:

  1. I navigate to TEST and HOME.ngOnDestroy() does NOT get called.
  2. When I navigate back from TEST to HOME, TEST.ngOnDestroy() gets called.

Console output on scenario above

If I refresh the app on TEST the same behaviour happens but reversed

  1. I navigate to HOME and TEST.ngOnDestroy() does NOT get called.
  2. When I navigate back from HOME to TEST, TEST.ngOnDestroy() gets called.

I've uploaded the project to a public git just in case: https://github.com/jjgl/ionicNetworkCapacitorTests/

but here are the main bits:

HOME

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Plugins, NetworkStatus, PluginListenerHandle } from '@capacitor/core';

const { Network } = Plugins;

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage implements OnInit, OnDestroy {
  networkStatus: NetworkStatus;
  networkListenerHome: PluginListenerHandle;

  async ngOnInit() {
    this.networkListenerHome = Network.addListener('networkStatusChange', (status) => {
      console.log("Home Page Network status changed", status);
      this.networkStatus = status;
    });

    this.networkStatus = await Network.getStatus();
  }

  ngOnDestroy() {
    console.log('home destroyed')
    this.networkListenerHome.remove();
  }
}

TEST

import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import { ConnectionStatus, NetworkService } from 'src/app/services/network.service';

@Component({
  selector: 'app-test-page',
  templateUrl: './test-page.page.html',
  styleUrls: ['./test-page.page.scss'],
})

export class TestPagePage implements OnInit, OnDestroy {
  subscription : Subscription
  private statusOnline : boolean;

  constructor(private networkService: NetworkService) {}

  async ngOnInit() {
    console.log('hola')
    this.subscription = this.networkService.onNetworkChange().subscribe((status: ConnectionStatus) => {
      this.statusOnline = status == ConnectionStatus.Online ? true : false;
      console.log('network change, status:', status);
    })
  }

  ngOnDestroy() { 
    console.log('test destroyed')
    this.subscription.unsubscribe()
  }

}

Network Service

import { Injectable, OnDestroy } from '@angular/core';
import { Plugins, NetworkStatus, PluginListenerHandle } from '@capacitor/core';
import { BehaviorSubject, Observable } from 'rxjs';

const { Network } = Plugins;

export enum ConnectionStatus {
  Online,
  Offline
}

@Injectable({
  providedIn: 'root'
})

export class NetworkService implements OnDestroy {

  private netStatus: BehaviorSubject<ConnectionStatus> = new BehaviorSubject(ConnectionStatus.Offline);
  networkStatus: NetworkStatus;
  networkListener: PluginListenerHandle;

  constructor() { 
    this.networkListener = Network.addListener('networkStatusChange', (status) => {
      console.log("Service Network status changed", status);
      this.networkStatus = status;
      let auxStatus = status && status.connected ? ConnectionStatus.Online : ConnectionStatus.Offline;
      this.netStatus.next(auxStatus)
    });
    this.initialState()
  }
  

  private async initialState(){
    this.networkStatus = await Network.getStatus();
    let auxStatus = this.networkStatus && this.networkStatus.connected ? ConnectionStatus.Online : ConnectionStatus.Offline;
    this.netStatus.next(auxStatus)
  }
  public onNetworkChange(): Observable<ConnectionStatus> {
    return this.netStatus.asObservable();
  }
 
  public getCurrentNetworkStatus(): ConnectionStatus {
    return this.netStatus.getValue();
  }

  ngOnDestroy(): void {
    console.log('services destroyed')
    this.networkListener.remove();
  }


}

Needless to say I'm new to Angular so feel free to point out other things too, all comments are welcome.

PS: I've implemented ngOnDestroy on the networkService, but I'm not calling it from TEST as the service doesn't get reinstantiated when navigating back to TEST after TEST.ngOnDestroy gets called the first time.


Solution

  • You cannot use Angular lifecycle in services. Use it inside components only.

    Please call Network.addListener into app.component.ts.

    ...
    export class AppComponent implements OnInit {
      subscription : Subscription
      private statusOnline : boolean;
    
      constructor(private networkService: NetworkService) {}
    
      async ngOnInit() {
        this.networkListener = Network.addListener('networkStatusChange', (status) => {
          console.log("Service Network status changed", status);
          this.networkStatus = status;
          let auxStatus = status && status.connected ? ConnectionStatus.Online : ConnectionStatus.Offline;
          this.networkService.netStatus.next(auxStatus) // Push the new status to NetworkService behaviorSubject
        });
      }
    }
    

    app.component.ts is a root singleton component, so it's neither destroyed nor refreshed until you refresh the browser tab. Now you can use the netstatus in NetworkService inside any component like you are already doing.

    ...
    export class NetworkService {
    
      private netStatus: BehaviorSubject<ConnectionStatus> = new BehaviorSubject(ConnectionStatus.Offline);
    
      public onNetworkChange(): Observable<ConnectionStatus> {
        return this.netStatus.asObservable();
      }
     
      public getCurrentNetworkStatus(): ConnectionStatus {
        return this.netStatus.getValue();
      }
    }