A simple service for responsive Angular applications

3 min read

Building responsive user interfaces can sometimes feel like a puzzle. Angular's BreakpointObserver from the CDK is a fantastic tool for reacting to screen size changes, and when combined with the power of signals, it becomes even more elegant. Let's look at a simple service that centralizes your responsive logic, making your components cleaner and more declarative.

What this service does

This ResponsiveManager service simplifies how you react to different screen sizes across your Angular application:

  1. Observes Breakpoints: It uses the Angular CDK's BreakpointObserver to detect when predefined screen widths are met. In our example, it checks for screens (max-width: 600px).
  2. Signals for Reactivity: The magic happens when we use toSignal to convert the BreakpointObserver's observable stream into a reactive signal (screenWidth).
  3. Derived Screen State: From screenWidth, we create computed signals like smallWidth. This smallWidth signal will automatically update to true or false based on whether the current screen falls within our "small" breakpoint.
  4. Application-Specific Logic: You can then build further computed signals, such as sideNavMode, which dynamically changes the mode of a Material side navigation (e.g., 'side' for larger screens, 'over' for small screens).

Here's the code:

@Injectable({
  providedIn: "root",
})
export class ResponsiveManager {
  // Add other widths as needed
  private readonly small = "(max-width: 600px)";

  // Observe widths and convert to a signal
  private readonly screenWidth = toSignal(
    inject(BreakpointObserver).observe([this.small]),
  );

  // Create computeds for easy component binding
  public readonly smallWidth = computed(
    () => this.screenWidth()?.breakpoints[this.small],
  );

  // Example: More derived signals for components
  private readonly sideNavOpened = signal(true); // Manages open state
  public readonly sideNavMode = computed(() =>
    !this.smallWidth() ? "side" : "over",
  ); // Changes mode based on width

  toggleSideNav() {
    this.sideNavOpened.set(!this.sideNavOpened());
  }
}

Why it's useful

This approach offers several benefits for your Angular development:

  • Clean Components: Components don't need to directly subscribe to BreakpointObserver or manage complex RxJS streams. They simply consume signals.
  • Declarative UI: You can bind directly to computed signals in your templates, making your UI logic more readable and declarative. For example, [mode]="responsiveManager.sideNavMode()" is very straightforward.
  • Centralized Logic: All your breakpoint definitions and core responsive logic reside in one service, making it easier to maintain and extend.
  • Performance: Signals provide fine-grained reactivity, potentially leading to more efficient change detection updates only for parts of your UI that actually depend on the changing signal values.

How to use it

Using the ResponsiveManager in your components is straightforward:

  1. Inject the service:

    import { Component, inject } from "@angular/core";
    import { ResponsiveManager } from "./responsive-manager.service"; // Adjust path
    
    @Component({
      // ...
    })
    export class MyResponsiveComponent {
      readonly responsiveManager = inject(ResponsiveManager);
    
      // Access signals directly in template or in component logic
      // e.g., this.responsiveManager.smallWidth()
    }
    
  2. Bind in your template: You can then bind the computed signals directly to your HTML elements or component inputs. For instance, if you're using Angular Material's mat-sidenav:

    <mat-sidenav-container>
      <mat-sidenav
        [mode]="responsiveManager.sideNavMode()"
        [opened]="responsiveManager.sideNavOpened()"
      >
        <!-- Sidenav content -->
      </mat-sidenav>
      <mat-sidenav-content>
        <!-- Main content -->
      </mat-sidenav-content>
    </mat-sidenav-container>
    
  3. Extend it: Easily add more breakpoints (medium, large, etc.) and corresponding computed signals in the ResponsiveManager service to cover all your responsive needs.

By using this pattern, you can build responsive Angular applications with a clean, signal-driven approach that is both powerful and easy to understand.


This responsive manager was part of my Modern Angular Ecommerce App. You can find the full code and a list of all features here: Code Link

You may also like...