DEV Community

Cover image for BrowserStateService: A Single Point of Truth for Dark Mode State in Angular
netsi1964 🙏🏻
netsi1964 🙏🏻

Posted on

BrowserStateService: A Single Point of Truth for Dark Mode State in Angular

As more and more users prefer dark mode on their devices, web developers have to keep up with this trend by providing dark mode support on their web applications. This can be achieved in several ways, but maintaining the state of dark mode across the application can be a daunting task, especially as it grows larger. A good solution is to use a single point of truth in a service that maintains the state of dark mode across all components. This is where the BrowserStateService comes in.

TLDR

  • The BrowserStateService is an Angular service that maintains the state of dark mode across an Angular application.
  • It listens for changes to the dark class on the body element, and provides an observable that components can subscribe to in order to observe changes to the state of dark mode.
  • The service provides methods for toggling the mode and setting it to a specific value.
  • It listens for changes to the user's preferred color scheme at the operating system level, and sets the initial mode accordingly.
  • Using this service can help maintain a single point of truth for the state of dark mode across an Angular application.

The code

browserstate.service.ts looks like this:

import { BehaviorSubject } from 'rxjs';
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class BrowserStateService {
  private isDarkModeSubject = new BehaviorSubject<boolean>(false);
  public isDarkMode$ = this.isDarkModeSubject.asObservable();

  constructor() {
    // Check if 'dark' class is present on body element on initialization
    this.isDarkModeSubject.next(document.body.classList.contains('dark'));

    // Watch for changes to 'dark' class on body element
    new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        if (mutation.attributeName === 'class') {
          const isDark = document.body.classList.contains('dark');
          this.isDarkModeSubject.next(isDark);
        }
      });
    }).observe(document.body, { attributes: true });

    // Listen for user's preferred color scheme
    const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
    mediaQuery.addEventListener('change', (event) => {
      const isDark = event.matches;
      this.setDarkMode(isDark);
    });

    // Set initial mode based on user's preferred color scheme
    if (mediaQuery.matches) {
      this.setDarkMode(true);
    }
  }

  toggleMode() {
    const isDark = !this.isDarkModeSubject.value;
    this.setDarkMode(isDark);
  }

  setDarkMode(isDark: boolean) {
    if (isDark) {
      document.body.classList.add('dark');
    } else {
      document.body.classList.remove('dark');
    }

    this.isDarkModeSubject.next(isDark);
  }
}
Enter fullscreen mode Exit fullscreen mode

How to use

To use the BrowserStateService in your Angular application, follow these steps:

1) Create a new file named browser-state.service.ts in your Angular project's src/app directory.

2) Copy the code from the example in the previous section and paste it into the browser-state.service.ts file.

3) Import the service in any component where you want to observe or change the state of dark mode:

import { BrowserStateService } from './browser-state.service';
Enter fullscreen mode Exit fullscreen mode

4) Inject the service in the component's constructor:

constructor(private browserStateService: BrowserStateService) {}
Enter fullscreen mode Exit fullscreen mode

You can also import it as public if you for instance want to use it in you template like this: {{ browserStateService.isDarkKode$ || async }}.

5) Use the isDarkMode$ observable to observe changes to the state of dark mode, and the setDarkMode() and toggleMode() methods to change the state of dark mode:

// Subscribe to changes to the state of dark mode
this.browserStateService.isDarkMode$.subscribe(isDarkMode => {
  // Do something based on the value of isDarkMode
});

// Set the state of dark mode
this.browserStateService.setDarkMode(true);

// Toggle the state of dark mode
this.browserStateService.toggleMode();
Enter fullscreen mode Exit fullscreen mode

Example of Use

Here's an example of how you can use the BrowserStateService in an Angular component:

import { Component, OnInit } from "@angular/core";
import { BrowserStateService } from "./browser-state.service";

@Component({
  selector: "app-my-component",
  template: `
    <div [ngClass]="{ 'dark-mode': isDarkMode }">
      <p>Some text in the component.</p>
      <button (click)="toggleMode()">Toggle Mode</button>
    </div>
  `,
})
export class MyComponent implements OnInit {
  isDarkMode: boolean;

  constructor(private browserStateService: BrowserStateService) {}

  ngOnInit() {
    // Subscribe to changes to the
    // state of dark mode
    this.browserStateService.isDarkMode$.subscribe((isDarkMode) => {
      this.isDarkMode = isDarkMode;
    });
  }

  toggleMode() {
    this.browserStateService.toggleMode();
  }
}

Enter fullscreen mode Exit fullscreen mode

In this example, we're using the ngClass directive to conditionally apply the dark-mode class to the div element based on the value of the isDarkMode property. We're also using a button to call the toggleMode() method of the BrowserStateService to toggle the state of dark mode.

Conclusion

The BrowserStateService is a simple and effective way to maintain the state of dark mode across an Angular application. By using a single point of truth in a service, you can avoid the complexity of managing the state of dark mode across multiple components, and provide a more consistent user experience. The BrowserStateService listens for changes to the dark class on the body element, and provides methods for toggling the mode and setting it to a specific value. Additionally, it listens for changes to the user's preferred color scheme at the operating system level, and sets the initial mode accordingly.

Thank you to ChatGPT for providing the code for this useful Angular service.

Top comments (0)