0

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

Sampgun
  • 2,822
  • 1
  • 21
  • 38

1 Answers1

1

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()}`};
lbsn
  • 2,322
  • 1
  • 11
  • 19
  • Thanks, but I mentioned that this wouldn't solve. The change must be reflected from the array, this would fire the setter inside hero.component too early. – Sampgun Jul 02 '21 at 14:24
  • I might be mistaking, but it seems to me that you're not setting anything. Why would that fire a setter? Your modifying a `hero` object on `HeroComponent`, emitting that same object to `AppComponent` which is passing that same object again to `HeroComponent`. No reference has changed, no setter has been called. Why do you expect something different? – lbsn Jul 02 '21 at 14:34
  • If you look at the `app.component` you'll see that the new hero is assigned there. And I don't expect a setter to fire. I'm asking for a clean solution, to trigger the ngFor to print again. – Sampgun Jul 02 '21 at 14:36
  • It's not a new hero. It's a reference to the same old (just modified) hero, coming from `HeroComponent`. The `ngFor` IS printing again, but the hero that `HeroComponent` gets from this new `ngFor` cycle is the same one as before. Angular change detection uses reference by default. In order to change the reference in AppComponent you could do this: `this.heroes[index] = {...$event};` – lbsn Jul 02 '21 at 14:49
  • Yeah actually you're right about that! In the original code I create an entirely new object, but it doesn't work anyway, if I remember correctly. Anyway I'll try in my Blitz and see what happens! Thanks – Sampgun Jul 02 '21 at 15:11
  • Damn! It actually works! Now I have to figure what happened this morning... – Sampgun Jul 02 '21 at 15:14
  • 1
    Depending on how complex your real use case is, another solution would be to define a separate component for the alias. Since that would be bound to the `alias` property (not the whole object), change detection would work without the need of treating `hero` as immutable: [https://stackblitz.com/edit/angular-ivy-yhpv3j](https://stackblitz.com/edit/angular-ivy-yhpv3j) – lbsn Jul 02 '21 at 15:32
  • Yep. I see what you mean, nice idea! – Sampgun Jul 04 '21 at 14:49
  • I marked your answer as accepted, since you actually pointed me in the right direction! Thanks! – Sampgun Jul 04 '21 at 14:50