Angular Service - Why is the primitive value not updated?

81 Views Asked by At

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>
2

There are 2 best solutions below

0
On BEST ANSWER

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.

2
On

From what I can gather from your code, this would be because angular change detection works with memory references (among other things) : updating a memory reference triggers the change detection.

This is what happens with your array : your set a new value into its memory reference.

But it does not work like this for primitive values (strings, numbers, booleans).

If you wish to make it work, you should trigger the change detection yourself with the ChangeDetectorRef.detectChanges().

But more importantly, I would suggest you first learn more about the observables and how they work, particularly with the async pipe : your code is very prone to issues like this, and using the observables correctly would prevent most of them from happening.

In other terms : this is a edge case you created by mistake, don't loose too much time over it and keep learning !