What is one of the harder things, if not the hardest, you have done in your Angular applications?

STATE

STATE

Local state management

start small, start local (it is kind of a "must")

Chau Tran

...and more (who doesn't like to own a State Management library)

When should we use state management?

– Any Angular developer ever

Do I need to use a Global Store ABorC?

– Any Angular developer ever

FeatureA

FeatureB

FeatureC

StateA

StateB

StateC

App

FeatureA

FeatureB

FeatureC

StateA

StateB

StateC

App

FeatureA

FeatureB

FeatureC

StateA

StateB

StateC

App

FeatureA

FeatureB

FeatureC

StateA

StateB

StateC

App

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

@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: [ './dashboard.component.css' ]
})
export class DashboardComponent implements OnInit {
  heroes: Hero[] = [];

  constructor(private heroService: HeroService) { }

  ngOnInit(): void {
    this.getHeroes();
  }

  getHeroes(): void {
    this.heroService.getHeroes()
      .subscribe(heroes => this.heroes = heroes.slice(1, 5));
  }
}
<div>
  <label for="new-hero">Hero name: </label>
  <input id="new-hero" #heroName />

  <!-- (click) passes input value to add() and then clears the input -->
  <button type="button" class="add-button" (click)="add(heroName.value); heroName.value=''">
    Add hero
  </button>
</div>


<h2>Top Heroes</h2>

<div class="heroes-menu">
  <a *ngFor="let hero of heroes"
      routerLink="/detail/{{hero.id}}">
      {{hero.name}}
  </a>
</div>

<app-hero-search></app-hero-search>
export class DashboardComponent implements OnInit {
  heroes: Hero[] = [];

  constructor(private heroService: HeroService) { }

  ngOnInit(): void {
    this.getHeroes();
  }

  getHeroes(): void {
    this.heroService.getHeroes()
      .subscribe(heroes => this.heroes = heroes.slice(1, 5));
  }
  
  add(name: string) {
    name = name.trim();
    if (!name) { return; }
    this.heroService.addHero({ name } as Hero)
      .subscribe(hero => {
        this.heroes.push(hero);
      });
  }
}
export class DashboardComponent implements OnInit {
  heroes: Hero[] = [];

  constructor(private heroService: HeroService) { }

  ngOnInit(): void {
    this.getHeroes();
  }

  getHeroes(): void {
    this.heroService.getHeroes()
      .subscribe(heroes => this.heroes = heroes.slice(1, 5));
  }
  
  add(name: string) {
    name = name.trim();
    if (!name) { return; }
    this.heroService.addHero({ name } as Hero)
      .subscribe(hero => {
        this.heroes.push(hero);
      });
  }
}
export class DashboardComponent implements OnInit {
  heroes: Hero[] = [];

  constructor(private heroService: HeroService) { }

  ngOnInit(): void {
    this.getHeroes();
  }

  getHeroes(): void {
    this.heroService.getHeroes()
      .subscribe(heroes => this.heroes = heroes.slice(1, 5));
  }
  
  add(name: string) {
    name = name.trim();
    if (!name) { return; }
    this.heroService.addHero({ name } as Hero)
      .subscribe(hero => {
        this.heroes.push(hero);
      });
  }
}
export class DashboardComponent implements OnInit {
  heroes: Hero[] = [];

  constructor(private heroService: HeroService) { }

  ngOnInit(): void {
    this.getHeroes();
  }

  getHeroes(): void {
    this.heroService.getHeroes()
      .subscribe(heroes => this.heroes = heroes.slice(1, 5));
  }
  
  add(name: string) {
    name = name.trim();
    if (!name) { return; }
    this.heroService.addHero({ name } as Hero)
      .subscribe(hero => {
        this.heroes.push(hero);
      });
  }
}

Pull-based state management

PUSH-based state management

export class SomeStateService {
  private readonly $data = new BehaviorSubject<SomeData[]>([]);
  readonly data$ = this.$data.asObservable();
  
  private readonly $loading = new BehaviorSubject<boolean>(false);
  readonly loading$ = this.$loading.asObservable();
  
  constructor(private readonly someService: SomeService) {}
  
  getData() {
    this.$loading.next(true);
    this.someService.getData().subscribe({
      next: data => {
        this.$data.next(data);
        this.$loading.next(false);
      },
      error: error => {
        console.log(error);
        this.$loading.next(false);
      },
    })
  }
  
  addData(item) {
    this.$loading.next(true);
    this.someService.addData(item).subscribe({
      next: addedItem => {
        this.$data.next([...this.$data.value, addedItem]);
        this.$loading.next(false);
      },
      error: error => {
        console.log(error);
        this.$loading.next(false);
      },
    })
  }
  
  filter(query) {
    this.$data.next(this.$data.value.filter(...));
  }
}

ngrx/component-store

ngrx/component-store

Read

Write

Side-effect

select

updater

effect

@ngrx/store

createSelector

createReducer

createReducer

createEffect

ngrx/component-store

Demo

ngrx/component-store

Demo

ngrx/component-store

no subscriptions

ngrx/component-store

race-conditions built-in

ngrx/component-store

easy to graduate to a global store if needed

ngrx/component-store

easier to test too

Q/A

Local State

By Chau Tran

Local State

  • 322