Search code examples
arraysangulartypescriptngforngonchanges

Angular: handle object changes in ngFor, force print


I have my app.component with a list of objects

class Hero {
    alias: string;

    constructor(public firstName: string,
                public lastName: string) {
    }
}

class AppComponent {
...
heroes: Hero[] = [
    new Hero("foo", "bar")
];
...
onHeroChange($event: Hero, index: number): void {
    this.heroes[index] = $event;
}

<div *ngFor="let hero of heroes; let index=index">
  <hero [hero]="hero" (heroChange)="onHeroChange($event, index)"></hero>
</div>

The HeroComponent is

export class HeroComponent {
  @Input()
  set hero(newValue: Hero) {
    this._hero = newValue;
    this.showAlias = !!newValue.alias;
  }

  get hero(): Hero {
    return this._hero;
  }

  @Output() heroChange: EventEmitter<Hero> = new EventEmitter<Hero>();
  showAlias: boolean = !1;

  private _hero: Hero;

  //

  @HostListener('click')
  onHeroClick(): void {
    this.hero.alias = `alias_${+new Date()}`;
    console.info('HERO TO EMIT', this.hero);
    this.heroChange.emit(this.hero);
  }
}

My problem is that even by assigning the changed hero in app.component, the set hero inside hero.component is not called, so showAlias in the example is not updated and I don't see the alias in the hero.component.

Do I need to force the ngFor by assigning the entire array?

Maybe a workaround could be removing the object from the array and then inserting again? Sounds like useless computation though.

Note: this is just an example, it's not what I'm really working on, so something like

Update the showAlias prop in the onHeroClick method

or

Assign hero in the hero.component

unfortunately don't solve the issue. I need the changes to be on the outside because other stuff happens.

Could be another option changing the detection to onPush and marking for check manually?

Blitz ==> https://stackblitz.com/edit/angular-ivy-kpn3ds


Solution

  • You're not setting a new hero, you're just modifying a property on the existing one:

    this.hero.alias = `alias_${+new Date()}`;
    

    That doesn't fire the setter. Change the line like this:

    this.hero = {...this.hero, alias: `alias_${+new Date()}`};