Search code examples
angularangular-servicesinjectable

Angular Service - Why is the primitive value not updated?


I'm figuring out how Angular Services work, but I don't understand why the heroCounter (number) doesn't update correctly while the hero array does. For instance, as I add a new dummy hero I expect the heroCounter to increment.

Thank you in advance if you can help me.

hero.services.ts

import { Injectable } from '@angular/core';
import { Hero } from '../model/hero.model';
import { HEROES } from '../constants/hero.constant';
import { Observable, of } from 'rxjs';

@Injectable({
  providedIn: 'root',
})
export class HeroService {
  heroes!: Hero[];
  heroesCounter!:number;

  constructor() {
    this.heroes = HEROES;
    this.heroesCounter = this.heroes.length;
  }

  getHeroes(): Observable<Hero[]> {
    return of(this.heroes);
  }

  getHeroesCounter(): Observable<number> {
    return of(this.heroesCounter);
  }

  mockAddHero(name: string): void {
    setTimeout(() => {
      this.heroes.push(new Hero(name));
      this.heroesCounter = this.heroes.length;
      console.log(this.heroesCounter);
    }, 1500);
  }
}

app.component.ts

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrl: './app.component.css',
})
export class AppComponent implements OnInit {
  heroes!: Hero[];
  heroesCounter!: number;
  constructor(private heroService: HeroService) {}

  getHeroes(): void {
    this.heroService.getHeroes().subscribe((heroes) => (this.heroes = heroes));
  }

  getHeroesCounter(): void {
 
    this.heroService
      .getHeroesCounter()
      .subscribe((counter) => {this.heroesCounter = counter});
  }

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

  addHero(): void {
    this.heroService.mockedAddHero('new hero');
  }
}

app.component.html

<div>
  <button (click)="addHero()" >Add Dummy Hero</button>
  <ul> 
    <!-- This works -->
    @for(hero of heroes; track $index){
      <li>{{hero.name}}</li>
    }
  </ul>
  <!-- This does not work -->
  <p> Total of heroes: {{this.heroesCounter}}</p>
</div>

Solution

  • Because the getHeroesCounter method of the HeroService return of(this.heroesCounter), and of function will emit a variable amount of values in a sequence and then emit a complete notification.

    As a result, when you add a new hero to the list, the subscribe method will not be triggered, even though you trigger the change detection yourself with the ChangeDetectorRef.detectChanges() or setTimeout, ...

    The heroService.getHeroes() also returns of(heroes), but the heroes list is an array - a reference type.

    More information about of can be found at

    Here is a demo Stackblitz

    have a getHeroesCounter complete! message in the Console

    To resolve your issue, you should use the Subject in HeroService to store the hero list.