Angular modal data not refreshing after data change

Viewed 3203

I have data that is displayed in a modal. However, when my data is updated and parent detects this, my modal data is never updated. Am I missing something?

PARENT:

  @Input() players: Players[] = [];

  showPlayers() {
     const modalRef = this.modalService.open(PlayersModalComponent, {
      data: { players: this.players }
     },
    });
  }

 ngOnChanges(changes: SimpleChanges) {
    console.log('changes', changes);
    // This shows that players has changed and gone from say 2 players to 3 players after a few actions.  But modal doesn't update.
  }

Modal

this.players = this.modalRef.data.players;

Modal HTML

Here I just list the players and initially they are perfect. But they do not update when a new player is added and ngChanges in parent ALSO shows there is a new row.

<modal class="modal" *ngIf="players">
  <div>
    <div *ngFor="let player of players">
         {{player.name}}
    </div>
  </div>
</modal>

However, if new result is added --- since I am pulsing on API from parent. The change is detected but the list of players in modals is not updated.

6 Answers

Normally, everything should work straight away if you were always modifying the same instance of the players array (stackblitz demo). So the players @Input passed to your parent component is probably a new instance of an array each time.

Since the players property in your player modal component is only initialised once ( this.players = this.modalRef.data.players;); once the players for the parent variable changes, the players variable from the modal component is still pointing to the previous array.

To solve this, you can either pass an observable to the modal itself like explained in Andrei's answer, or you can explicitely modify the modal's players variable when the parent's players input changes.

You can do that either using ngOnChanges (stackblitz demo)

parent.component.ts

ngOnChanges(changes: SimpleChanges) {
   if(this.modalRef && changes.players.currentValue)
    {
      this.modalRef.componentInstance.players = changes.players.currentValue;
    }
}

Or using a setter (stackblitz demo)

parent.component.ts

@Input() set players(players: Players[])
{
 if(this.modalRef)
 {
  this.modalRef.componentInstance.players = players;
 }
}

These examples rely on the fact that your modalRef contains an instance of the component used in the dialog

You can use ngFor trackBy function to detect changes

<modal class="modal" *ngIf="players">
    <div>
        <div *ngFor="let player of players, trackBy: trackItem">
             {{player.name}}
        </div>
    </div>
</modal>

and add trackItem function

trackItem(item: any): any {
    return item.id;
}

item should have unique id

it is expected that the modal doesn't recieve an update as you pass only the first array that you have when you call showPlayers. to be able to update the value I propose to use observables and subsribtion to get those updates
PARENT

players$ = new BehaviorSubject<Players[]>();
@Input() set players(players: Players[]) {
  this.players$.next(players);
};
showPlayers() {
     const modalRef = this.modalService.open(PlayersModalComponent, {
      data: { players$: this.players$ }
     },
    });
  }

MODAL

constructor(@Inject(MAT_DIALOG_DATA) public data: any, cdRef: ChangeDetectorRef) {
   data.players$.subscribe((players) => {
    this.players = players;
    cdRef.markForCheck(); // this line probably won't be needed. added just to be sure change detection detects this change
   });

}

simple solution: use a ViewChild.

parent:

import { ChildModalComponent } from ...

@ViewChild(ChildModalComponent) childModalComponent: ChildModalComponent;

ngOnChanges(changes: SimpleChanges) {
  this.childModalComponent.updatePlayers(this.players);
}

modal:

updatePlayers(players:any) {
  this.players = players;
}

done.

in parent .ts =

Players any=[];

OnChangePlayers(){
Players  = [1,2,3];
}

in parent .html =

<modalcomponentSelector [Players]="Players"></modalcomponentSelector >

in modal .ts :

  @Input() players: Players[] = [];

Let me show u how ur code is running actually

1-showPlayers()

2-model constructor

3-OnChanges

4-OnChanges

.

.

model constructor has been called before so now whenever OnChanges will be called there will be no effect on model bcs u have send players to model constructor

how we can send new player to model ? instead of sending player[] send Observable of it so u can get next players

Use can use player observable Directly in ur model template u don't have to deal with subscription.

// parent
players$ = new BehaviorSubject<Players[]>();
@Input() set players(players: Players[]) {
  this.players$.next(players);
};
showPlayers() {
     const modalRef = this.modalService.open(PlayersModalComponent, {
      data: { players$: this.players$ }
     },
    });
  }

Model

players$ :Observable<Players[]>
constructor(@Inject(MAT_DIALOG_DATA) public data: any) {
   this.players$ =  data.players$
   });

}

Model HTML

<div>
    <div *ngFor="let player of (players$ | async)">
         {{player.name}}
    </div>
  </div>
Related