Managing State in Angular 2+

The hardest thing in a growing single-page application is managing state. Click To Tweet

This problem has always been strongly discussed in the Angular community, but not many standards have emerged. Most tutorials are too far from real-world applications to capture the essence of the problem and only simplistic “parent-child” interactions are discussed. As an application grows, components become more deeply nested and we need solutions to handle interactions between components that are more distant from each other than “parent-child”.

The best approaches to me seem to be storing some state in services or using a state management library. The most prominent exponent for such a library would be ngrx/store (https://github.com/ngrx/store). If you’re having trouble with managing states you are not alone, other people have already had this problem and dealt with it. Unfortunately those people just don’t seem the to be the ones from the Angular Team, which still promote their “hello-world” data-binding mechanisms.

I’m not alone with this opinion, similarly thinks Kyle Cords in this excellent summary on managing states in Angular2+. Exactly as in my experience, he describes how the application gets messier as the codebase grows with the classical data-binding mechanisms you find in all Angular tutorials. The way I see it, the tutorials mostly mention those techniques for two reasons:

  • They are a tiny bit simpler to demonstrate in a small demo app than other methods.
  • They are a “Unique Selling Point” for Angular. If they would suggest using something like ngrx/store they’d essentially say “why don’t you just switch to React & Redux, here’s a link to get you started”.

Now whether it’s best to use ngrx/store or using mechanisms provided by angular (services) is subject to debate. Here’s a list of pros and cons for each: State Management: ngrx/store vs Angular services

In case you want to take the manual path with services, here are some of my thoughts on the subject.

Update resources with an id

Typically, behind every application, we have a database. And typically, this database has different tables (or collections or however you want to call them) and all of the entries in a table have an id. For example behind the Angular2 Heroes tutorial, we’d have a database with a heroes table where each hero is stored with its own id. Almost every application is structured this way. What’s very often the case in a real-world application, is that we want to change the data in the database as well as in our entire application. There could be, for example, an input field to change a hero’s name and when clicking the button “Change”, we’d send the change-request to the server (database). When the server responds with “200 ok” we want to change the hero’s name application wide. How would we go about that? The answer is: observables.

UpdateAll (1)

Since “ResourceName + ResourceId” are always unique for each unique object in our application, we can easily build a frontend-datastore based on this. The basics of the ResourceStore service, or here more specifically a HeroStore (leaned on the Angular hero tutorial-series) are:

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

@Injectable()
export class HeroService {
  private heroStore = {
  }

  insertHero(hero: Hero): void {
    this.heroStore[hero.id] = new BehaviorSubject<Hero>(hero);
    this.heroStore[hero.id].next(hero);
  }

  updateHero(hero: Hero): void {
    this.heroStore[hero.id].next(hero);
  }
  
  getHero(heroId: string): Hero {
    return this.heroStore[heroId];
  }
  
}

Like this, everywhere in the application you can listen to changes on those observables. There is one catch though: You shouldn’t forget to unsubscribe when the component is destroyed or otherwise you’ll gather loads of dead listeners on your observables over time. Here’s how it’s done:

import { Component, OnDestroy, OnInit } from '@angular/core';
import 'rxjs/add/operator/takeUntil';
import { Subject } from 'rxjs/Subject';

import { MyThingService } from '../my-thing.service';

@Component({
    selector: 'my-thing',
    templateUrl: './my-thing.component.html'
})
export class MyThingComponent implements OnDestroy, OnInit {
    private ngUnsubscribe: Subject<void> = new Subject<void>();

    constructor(
        private myThingService: MyThingService,
    ) { }

    ngOnInit() {
        this.myThingService.getThings()
            .takeUntil(this.ngUnsubscribe)
            .subscribe(things => console.log(things));

        this.myThingService.getOtherThings()
            .takeUntil(this.ngUnsubscribe)
            .subscribe(things => console.log(things));

    }

    ngOnDestroy() {
        this.ngUnsubscribe.next();
        this.ngUnsubscribe.complete();
    }
}

(courtesy to stackoverflow)

Here is the Hero-Tutorial adapted to use observables instead of ngModel:

Angular Heroes

The essential code bits behind this example are the hero.service:

import { Injectable } from '@angular/core';
import { Hero } from './hero';

import {BehaviorSubject} from 'rxjs/BehaviorSubject';

@Injectable()
export class HeroService {
  private heroStore = {
  }

  insertHero(hero: Hero): void {
    this.heroStore[hero.id] = new BehaviorSubject(hero);
    this.heroStore[hero.id].next(hero);
  }

  updateHero(hero: Hero): void {
    this.heroStore[hero.id].next(hero);
  }
  
  getHero(heroId: string): Hero {
    return this.heroStore[heroId];
  }
  
}

as well as the subscribing entities (here: app.component and hero-detail.component)

//app.component.ts
...
ngOnInit() {
    HEROES.forEach(hero => {
      this.heroes.push(hero);
      this.heroService.insertHero(hero);
      this.heroService.getHero(hero.id).subscribe(updatedHero => {
        for (let i = 0; i < this.heroes.length; i++) {
          if (this.heroes[i].id === updatedHero.id) {
            this.heroes[i] = updatedHero;
          }
        }
      });
    })
  }
...

//hero-detail.component.ts
...
ngOnInit() {
    const heroObs = this.heroService.getHero(this.heroId);
    this.hero = heroObs.getValue();
    heroObs.subscribe(hero => {
      this.hero = hero;
    });
  }
...

 

Here, the heroes are fetched from a constant HEROES which is turned into observables on storage. The library used for observables is RxJS, the standard for an observable-library nowadays.

RxJS Logo

Admittedly, at first it may look a bit more complicated using observables than using ngModel, especially when you are used to ngModel from all the tutorials and Angular 1 two-way binding. But as your application grows, this scales nicely as it guarantees consistent data across your application.

Talk to all components of the same name

For example if our component structure would be:

hero-list > hero > delete-button

Then how could the delete-button tell the hero list to delete a hero? Does it really have to tell its parent (hero) “please delete this hero”, but the parent is also not allowed to do so, so the parent needs to ask the hero-list in return “please delete hero”? As you see this becomes more annoying as components become more nested. Is there really no way where components can communicate directly with one another? Can’t the child tell the grandparent directly please delete the hero?

Namespaced Broadcast Service

The solution to this problem are, again, observables. Let me elaborate. Basically you can create a service like the following

import { Injectable, EventEmitter } from '@angular/core';

@Injectable()
export class BroadcastService {

  public heroList = {
    deleteHero: new EventEmitter()
  };
  constructor() { }

}

The structure of this service encapsulates the magic behind this approach. It avoids name-spacing problems, by naming the methods of the BroadcastService after the components that should listen to the events. Each method then has the available EventEmitters as properties.

In the child component, you can simply inject the BroadcastService and emit a new event:

import {Component, Input, OnInit} from '@angular/core';
import {Hero} from "../hero";
import {HeroService} from "../hero.service";
import {BroadcastService} from "../broadcast.service";

@Component({
  selector: 'app-delete-hero',
  templateUrl: './delete-hero.component.html',
  styleUrls: ['./delete-hero.component.css']
})
export class DeleteHeroComponent implements OnInit {

  constructor(
    private heroService: HeroService,
    private broadcast: BroadcastService
  ) { }

  ngOnInit() {
  }

  @Input()
  hero: Hero;

  public deleteHero() {
    this.heroService.deleteHero(this.hero.uid).then(resp => {
      this.broadcast.heroList.deleteHero.emit({
        heroId: this.hero.uid
      });
    });
  }


}

and the great-grandparent (or whoever!) can subscribe to those events

...
ngOnInit() {

  //set up listeners
  this.broadcast.heroList.deleteHero.subscribe(evt => {
    this.heroes = this.heroes.filter(hero => hero.uid !== evt.heroId);
  });

  ...

}

Where is the downside of this approach? The child will not tell something directly to it’s grandparent. Other components with the same name are also listening. So for example if we were to feature the hero-list twice on the same page, and both lists would contain the hero with the uid xyz, then emitting a deleteHero Event would delete the hero in both lists. If you need the child to tell something to its grandparent and only to its grandparent, then you’ll really need to pass the information through the parent (see event-binding below). However, this is often not a problem since, like in this example, most of the time we would even want the second list to also be updated, since that hero was actually deleted from the database and this should be reflected everywhere in our application. We’ve also limited the problem with our name-spacing, such that only the right components will actually listen to those events. This is already much much better than just broadcasting events out into the wild like broadcast.deleteHero.

When no id is present

When no id is present on the data you want to update, the classical Angular data-binding mechanisms come into play which you find in most of the tutorials.

In any case, when no ids are available, those mechanisms prove to be quite useful.

“Downwards” Data-Flow

Upwards vs Downwards

Here “downwards” designates a flow from parent to a child to grand-child and so on. Angular pretty much takes care of this one for us. It’s similar as in Angular 1, just the notation changed a bit. Here’s how it would look like to propagate data and data changes from parent to child to grandchild.

The parent:

import { Component } from '@angular/core';

@Component({
  selector: 'app-parent',
  template: `<div> {{item + 1}} <app-child [item]="item"></app-child></div>`,
})
export class ParentComponent {

  item;
  constructor() {
    this.item = 'My Item ';
  }

}

The child:

import {Component, Input} from '@angular/core';

@Component({
  selector: 'app-child',
  template: `<div> {{item + 2}} <app-grandchild [item]="item"></app-grandchild></div>`
})
export class ChildComponent {
  @Input()
  public item;
}

The grandchild:

import {Component, Input} from '@angular/core';

@Component({
  selector: 'app-grandchild',
  template: '<div>{{item + 3}}</div>'
})
export class GrandchildComponent {
  @Input()
  public item;
}

Which would yield the output:

When item is changed in the parent, the changes are also propagated downwards. Here, I’m probably not telling you anything new and it’s the method of choice in most Angular tutorials. This method has proven clear, convenient and maintainable to our team over the years. The most common source of errors with this method in Angular 2 is to forget the square brackets (i.e. to write <app-child item=”item”> instead of <app-child [item]=”item”>, thus marked red above). Apart from this, you can’t really go wrong.

Upwards Flow

Classically in Angular1, you would just modify data on the child and the changes would be propagated upwards through two-way data-binding. However, in Angular2+,  they diluted this concept for performance & “clarity of the application” reasons. Now only changes via ngModel  in properties of objects are propagated upwards (example: tour of heroes). What exactly does this mean? For one, it means that if you don’t have an object but instead a primitive (string, number etc.), changes made will not be reflected in the parent. As an example, here is the “tour of heroes” from the Angular tutorial series adapted to demonstrate this:

Second, this means that you can’t just update this property in your controller or you may run into nasty errors like the following:

Nasty Angular2 Error

This happens with the following piece of code:

import {Component, Input, OnInit} from '@angular/core';

@Component({
  selector: 'app-grandchild',
  template: `<div>{{item.name + 3}}...</div>`
})
export class GrandchildComponent implements OnInit {
  @Input()
  public item;

  ngOnInit() {
    this.item.name = 'Jacky';
  }

}

All of this makes it hard for novices in Angular 2 to understand where they may and where they may not change data. That’s why I prefer the method with the observables whenever possible. In cases where you have to use ngModel: just make sure it’s actually an object and not a primitive you’re trying to change.

In case you want to propagate an event (as opposed to data-changes) upwards, then you can use the Angular event-binding mechanisms. Let’s consider the following component structure

hero-list > hero > change-color-button

In event-binding, the hero component invokes the change-color-button like so:

<change-color-button (colorChanged)="doOnColorChange($event)">
</change-color-button>

and in the change-color-button an event can be emitted like that

@Output() colorChanged = new EventEmitter();
...
colorChanged.emit({newColor: "green"});

Then the hero component can act upon a color change by implementing the doOnColorChange method. This method becomes quite annoying if we have components that are nested by several levels, as the each component must create its own EventEmitter, but sometimes we have no other choice.

Conclusion

There are many options for handling state. In my opinion it’s a close race between using a state-management library such and combining some of Angular’s core features. In case you want to handle management yourself rather than including yet another library, here’s a possible decision-making flowchart:

Exchange Information Between Components (6)

 

Thanks for reading, don’t forget to subscribe to get updates timely & share if you found this post useful!