myHotTake

Tag: Angular global state tips

  • How to Manage Global State in Angular Apps Effectively

    If you find this helpful, feel free to like or share! Let’s dive into the story. 🌟


    I’m running a relatively big city with many neighborhoods, and each neighborhood has its own mayor for some reason. The mayors are great at managing local issues, like fixing potholes or organizing block parties. But what happens when there’s a citywide event, like a power outage or a new traffic rule? That’s when I need a City Hall—one central place to make decisions that affect everyone.

    In my Angular application, managing global state is like running this city. Each neighborhood is a component, perfectly capable of handling its own small state. But when I have something important that multiple neighborhoods need to know—like the weather forecast or city budget—I rely on City Hall. This City Hall is my state management solution, like NgRx or BehaviorSubject, acting as a centralized store.

    When a neighborhood (component) needs to know the weather, it doesn’t call up every other neighborhood; it checks with City Hall. And if there’s an update—like a sudden storm—City Hall announces it to all the neighborhoods at once. Everyone stays in sync without endless chatter between the neighborhoods.

    The trick is to keep City Hall efficient. I don’t want it bogged down with every tiny detail. So, I only store what’s essential: citywide events, rules, or shared data. Local stuff? That stays with the mayors, or the components.

    This balance between local mayors and City Hall keeps the city—er, the app—running smoothly, even as it grows bigger. And that’s how I manage global state in Angular!


    Example 1: Using a Service with BehaviorSubject

    The BehaviorSubject acts as City Hall, storing and broadcasting state updates.

    // city-hall.service.ts
    import { Injectable } from '@angular/core';
    import { BehaviorSubject } from 'rxjs';
    
    @Injectable({
      providedIn: 'root',
    })
    export class CityHallService {
      private weatherSource = new BehaviorSubject<string>('Sunny');
      public weather$ = this.weatherSource.asObservable();
    
      setWeather(newWeather: string): void {
        this.weatherSource.next(newWeather); // Announce new weather to all components
      }
    }

    Here, the weatherSource is the centralized state. Components subscribe to weather$ to stay informed.


    Example 2: Components as Neighborhoods

    Let’s see how neighborhoods (components) interact with City Hall.

    WeatherDisplayComponent:

    import { Component, OnInit } from '@angular/core';
    import { CityHallService } from './city-hall.service';
    
    @Component({
      selector: 'app-weather-display',
      template: `<p>The weather is: {{ weather }}</p>`,
    })
    export class WeatherDisplayComponent implements OnInit {
      weather: string = '';
    
      constructor(private cityHallService: CityHallService) {}
    
      ngOnInit(): void {
        this.cityHallService.weather$.subscribe((weather) => {
          this.weather = weather; // Stay updated with City Hall's announcements
        });
      }
    }

    WeatherControlComponent:

    import { Component } from '@angular/core';
    import { CityHallService } from './city-hall.service';
    
    @Component({
      selector: 'app-weather-control',
      template: `<button (click)="changeWeather()">Change Weather</button>`,
    })
    export class WeatherControlComponent {
      constructor(private cityHallService: CityHallService) {}
    
      changeWeather(): void {
        const newWeather = prompt('Enter new weather:');
        if (newWeather) {
          this.cityHallService.setWeather(newWeather); // Update City Hall
        }
      }
    }

    Example 3: NgRx for Large-Scale State

    For a bigger city (application), I might use NgRx for even more structured state management.

    Defining the State:

    // weather.state.ts
    export interface WeatherState {
      weather: string;
    }
    
    export const initialState: WeatherState = {
      weather: 'Sunny',
    };

    Action:

    // weather.actions.ts
    import { createAction, props } from '@ngrx/store';
    
    export const setWeather = createAction(
      '[Weather] Set Weather',
      props<{ weather: string }>()
    );

    Reducer:

    // weather.reducer.ts
    import { createReducer, on } from '@ngrx/store';
    import { setWeather } from './weather.actions';
    import { WeatherState, initialState } from './weather.state';
    
    export const weatherReducer = createReducer(
      initialState,
      on(setWeather, (state, { weather }) => ({ ...state, weather }))
    );

    Selectors:

    // weather.selectors.ts
    import { createSelector, createFeatureSelector } from '@ngrx/store';
    import { WeatherState } from './weather.state';
    
    export const selectWeatherState = createFeatureSelector<WeatherState>('weather');
    export const selectWeather = createSelector(
      selectWeatherState,
      (state: WeatherState) => state.weather
    );

    Component Using NgRx Store:

    // weather-display.component.ts
    import { Component, OnInit } from '@angular/core';
    import { Store } from '@ngrx/store';
    import { Observable } from 'rxjs';
    import { selectWeather } from './weather.selectors';
    
    @Component({
      selector: 'app-weather-display',
      template: `<p>The weather is: {{ weather$ | async }}</p>`,
    })
    export class WeatherDisplayComponent {
      weather$: Observable<string>;
    
      constructor(private store: Store) {
        this.weather$ = this.store.select(selectWeather);
      }
    }

    Key Takeaways

    1. Choose the Right Tool: For small apps, a service with BehaviorSubject is lightweight and effective. For larger apps, NgRx offers robust state management.
    2. Centralize Shared State: Use a service or store as a single source of truth (City Hall) for global data, while keeping local state within components.
    3. Keep it Clean: Don’t overload your centralized state with unnecessary data. Balance local and global state effectively.