Help getting tabs to subscribe to service

I am trying to implement a dark mode toggle, and when I toggle dark mode the tabs should change to a different design. When I trigger the event in my settings page, I don’t get anything emitted when I subscribe in the tabs.page.ts

darkmode.service.ts

@Injectable({})
export class DarkmodeService {
  darkMode: boolean;
  darkModeChange: BehaviorSubject<boolean> = new BehaviorSubject(
    window.matchMedia("(prefers-color-scheme: dark)").matches,
  );

  constructor() {
    this.darkModeChange.subscribe(value => {
      this.darkMode = value;
      document.body.classList.toggle("dark", value);
    });
  }

  toggleDarkMode() {
    this.darkModeChange.next(!this.darkMode);
  }
}

settings.page.ts

export class SettingsPage implements OnInit {
  darkMode: boolean;
  constructor(private darkModeService: DarkmodeService) {
    this.darkMode = this.darkModeService.darkMode;
  }

  ngOnInit() {}

  toggle() {
    this.darkModeService.toggleDarkMode();
  }
}

tabs.page.ts

export class TabsPage implements OnInit {
  @ViewChild("myTabs", { static: true }) tabs: IonTabs;
  darkMode: boolean;

  constructor(private darkModeService: DarkmodeService) {
    this.darkModeService.darkModeChange.subscribe(value => {
      console.log('clicked the dark mode toggle'); //Not being called when I click the toggle
      this.darkMode = value;
      this.onDarkModeChange();
    });
  }

 onDarkModeChange() {
   ...
  }
}

Can anyone see why I’m not seeing any value in the tabs.page.ts when I click on the toggle in settings.page.ts? If I subscribe to it on the settings page I can see it being called. It might be to do with my understanding of observables. Any tips are much appreciated :grin:

I don’t see the need to have setting and darkmode classes. Have one or the other.

If try and wrangle a solution (not tested) using the code you have, may be it would look like this…

darkmode.service.ts

@Injectable({})
export class DarkmodeService {
    
    private darkModeChange: BehaviorSubject<boolean>;
    public darkMode: Observable<boolean>;
    private darkModeActive = true; // or false depending on what prefer as the default

  constructor() {
    this.darkModeChange = new BehaviorSubject<boolean>(this.darkModeActive );
    this.darkMode = this.darkModeChange.asObservable();
  }

  toggleDarkMode() {
    console.log('Yay someone called me, am going to change the mode....');
    this.darkModeChange.next(!this.darkModeActive);
  }
}

tabs.page.ts

export class TabsPage implements OnInit {
    ....  
    darkMode: boolean = false; // or something in-case its accessed before the observable has emitted a value 

    constructor(private darkModeService: DarkmodeService) {
        this.darkModeService.darkMode.subscribe( value => {
            console.log(`Am in the constructor and darkmode is: ${value}`); 
            this.darkMode = value;

           // after here the mode has been initialized and its value can be accessed and used in a template to set the class of an element conditionally
    });
  }
    // To be called by the toggle mode clicker
    toggleMode(){
        this.darkModeService.toggleDarkMode();
}

Here you are initializing your data or variables and this happens as the page or component is loaded. You cant make a call to anything inside of here because this code is executed way ahead before the click toggle mode button or whatever is rendered in the page. Kindly check on ionic page lifecyle events for more on the execution flow.

My suggestion would be to have a component which handles the toggle event i.e. it has the code the has the mode options e.g dark, light, sky e.t.c and then has the toggle button or buttons which emit toggle events and other components that load this component will listen and adjust accordingly depending on what they listen for. You’ll not need to implement toggle functionality for each page, you only need to load the component, listen to the emitted event and act.

Here is a link to get you started with an event service and also Behaviorsubjects
https://www.joshmorony.com/using-behaviorsubject-to-handle-asynchronous-loading-in-ionic/

This can also help you out on understanding event service using observables…
Angular 10 - Communicating Between Components with Observable & Subject

Thanks for your reply. The reason I have 2 pages is that the settings page has the toggle to turn dark mode on/off. Inside my tabs.page.ts (which is the tab-icons at the bottom of the app), I have custom icons that I have created myself. When dark mode is triggered, I need to change these custom tab icons to use the dark versions (different color, outline), so all that was what the onDarkModeChange() method was going to do. The issue is as the ion-tabs are always displayed, (they are at the bottom of all my pages), I need a way to listen for a change in the service.

ion-tabs has a (ionTabsDidChange) listener that detects when you click on a tab, so I want to try and create something similar but to listen for a change to the darkMode service.

I guess I want a flow like this:

Click the dark mode toggle on the SettingsPage page -> calls DarkModeService to update darkMode to true -> The TabsPage sees the value has changed and automatically updates the icons to use the dark versions.

I thought subscribing to something in the TabsPage would mean any subscriptions would stay active while the tabs are in view (which isn’t the behaviour I am seeing)

Subscriptions are alive as long as the page they’ve been created in is in view, on exiting or navigating away to another page you must unsubscribe to avoid issues like memory leaks.

A Subscription is an object that represents a disposable resource, usually the execution of an Observable. A Subscription has one important method, unsubscribe , that takes no argument and just disposes the resource held by the subscription.

What you need to work with is Behaviorsubject.
Go through the previous links I shared above, they lay out the road map you need to achieve :point_down:

1 Like

A tiny point of clarification here that may be largely semantic, but subscriptions are alive until one of the following two things happens:

  • you call their unsubscribe method (or indirectly trigger a chain of events that results in this)
  • the Observable they’re subscribed to completes

There isn’t anything inherent about pages being “out of view” that automatically terminates subscriptions. Even if you use something like the UntilDestroy annotation to trigger unsubscription upon ngOnDestroy, a component that is hidden or offscreen or cached somewhere, as long as ngOnDestroy is yet to be called, will still have subscriptions active (which isn’t necessarily a bad thing).

1 Like

Thanks for confirming. Thats what I thought, I’ve definitely hooked up my observables correctly, I guess I just don’t get the behaviour of the TabsPage. It doesn’t go out of view, so any time the observable is called with .next, my subscription in the TabsPage should be called? I think I’ll have to potentially look into the inner workings of Ionic and the tabs component

I would recommend against that, if for no other reason than even if you figure something out, the best you can do is ending up depending on internals that may change without warning at any time.

I suspect the problem lies in how you’re interacting with the DOM. I get very nervous when I see people post code that directly accesses document. So if you can figure out a way to instead bind [class.dark]="darkMode" on some element instead of what you are doing, that would be the cleanest choice.

If that’s not possible, and you absolutely have no other option than doing direct DOM modification, try wrapping it like so:

import {DomController} from "@ionic/angular";
constructor(private domController: DomController) {
  this.darkModeChange.subscribe(value => {
    this.darkMode = value;
    this.domController.write(() => {
      // do nasty document.body junk inside here
    });
  });
}

Yeah I get what you’re coming from. You’re right, looking at the inner workings won’t get me anywhere. FWIW, I was trying to adapt on the ionic documentation for dark mode - https://ionicframework.com/docs/theming/dark-mode#manually-toggle-dark-mode. When it changes I need a method to be called inside Tabs because I need the icon which is stored inside the component to change based on the toggle :confused:

Events… Events… Events… using observables, thats where your answer is…