What is State?
A rock has no state, it is (more or less) an unchanging clump of minerals.
A lightbulb can be on or off, it has state. An elevator can be on or
off, it can be moving or stationary, it can be on level 5 or level 8,
it has a lot of state.
To put it simply, state in your application arises when something can change. Let’s consider a simple example:
@Component({ selector: `app-home`, template: ` @if (showGreeting()){ <app-greeting /> } `})export class HomeComponent { showGreeting = signal(false);}This is all our application does. It just displays a greeting component based on
whether showGreeting is set to true or not. Right now it is hard coded to be
false. This application doesn’t really have any “state” because nothing in the
application can change. It just does exactly what it was coded to do.
Now let’s add a button to toggle the showGreeting value:
@Component({ selector: `app-home`, template: ` @if (showGreeting()){ <app-greeting /> }
<button (click)="toggleGreeting()">Toggle</button> `})export class HomeComponent { showGreeting = signal(false);
toggleGreeting(){ this.showGreeting.update((showGreeting) => !showGreeting) }}Now the application has state. The showGreeting value can be changed from
false to true or vice versa and it will hide or display the greeting as
a result. Technically speaking, it doesn’t matter that changing the
showGreeting value actually does something, we still have state regardless.
So… what’s the big deal? This might seem like a silly thing to make such a fuss about. Our application can change, obviously, it’s an app! We want it to do things!
Managing state is a task that can be quite straight-forward, but it can also become a nightmare if the application becomes complex enough and an appropriate strategy isn’t being used. One of the difficult things is that there are so many different ways that you can go about managing state. You will find many people have many different opinions on how to best manage state, and you will also find there are many different state management libraries you can use to help you manage state (again, with plenty of people with strong opinions about those libraries).
When does managing state become hard?
Let’s expand on our silly greeting example a little. Right now our application’s state management concerns are quite simple. What if this greeting is not just shown on this one component? What if it is displayed globally in our application?
Now we can’t just store this showGreeting value on our component, the value is
going to need to be stored somewhere globally accessible like in a shared
service.
import { Injectable, signal } from '@angular/core';
@Injectable({ providedIn: 'root',})export class PreferenceService { #showGreeting = signal(false); showGreeting = this.#showGreeting.asReadonly();
toggleGreeting() { this.#showGreeting.update((showGreeting) => !showGreeting); }}Using this service might look like this:
import { Component, inject } from '@angular/core';import { PreferenceService } from '../shared/data-access/state.service';
@Component({ selector: `app-home`, template: ` @if (preferenceService.showGreeting()){ <app-greeting /> } <button (click)="preferenceService.toggleGreeting()">Toggle</button> `,})export default class HomeComponent { preferenceService = inject(PreferenceService);}But what if we also want to save this value into storage so that if the user refreshes the application the same state still applies? Now when we launch our application we need to trigger loading in this value somehow and setting it appropriately in the service. Now our application state needs to be initialised. We would need some kind of load method in our service:
import { Injectable, signal } from '@angular/core';
@Injectable({ providedIn: 'root',})export class PreferenceService { #showGreeting = signal(false); showGreeting = this.#showGreeting.asReadonly();
load() { // load value from storage/database/api // update this.#showGreeting with the appropriate value }
toggleGreeting() { this.#showGreeting.update((showGreeting) => !showGreeting); }}…and we would need to call that load method from somewhere when the application starts (e.g. in the root component).
This is probably as complex as our simple example is going to get in terms of state management, but there are still some annoying issues even in this simple example (like having to manually trigger a load, and also having to somehow deal with checking to see if the data has finished loading yet or not). For more advanced use cases, the state management concerns can become harder still:
- What if two different parts of the application are asynchronously attempting to update some state value at the same time? What should happen?
- If the application loads data from an API should we cache any of that data locally and fetch it from there instead of launching a request to the API upon reload?
- What strategy should we use to make sure we are predictably updating state consistently throughout the application? This is especially relevant if we have multiple developers working on this project
The rest of this module will be dedicated to exploring a little more about what dealing with state in Angular looks like, some basic strategies for handling it in different scenarios, the reactive/declarative strategy we will primarily be using, and a brief look at some state management libraries.