35

Injecting data to a material dialog is working well, but how can be a way to automatically update these data if they change in the parent / opener component (coming from a (WAMP) subscription for example)?

[Update]
I crated a minimal version to try out how the behavior is: https://stackblitz.com/edit/angular-njh44v?embed=1&file=src/app/app.component.ts
Open the console on the bottom right side and click on the grey box with the numbers. You see that the dialog gets the current number and does no more update after that.
[/Update]

[Update 2]
Based on the second idea of Jusmpty, this seams to work at first sight (even if the dialog shows / updates the first time after data arrive): https://stackblitz.com/edit/angular-thd34f?embed=1&file=src/app/app.component.ts
[/Update]

The concrete case looks like this:

  • There is an area component that subscribes to a WAMP service and gets all the data for the including parts. This draws all the parts with an *ngFor.
    File area.component.ts
  • Each part has an own component showing some data. If the data in the subscriptions change, the view is correctly and automatically updated, for each / all parts.
    Files part.component.ts and part.component.html
  • On each part a click opens a dialog where more data are showing.
    Files part-details-dialog.component.ts and part-details-dialog.component.html

And on this last action / step occurs the issue where the current data are showing (injecting) but no more updated if something in the subscribed data changes.

area.component.ts

@Component({
  selector: 'app-area',
  template: '<app-part *ngFor="let part of plan" [partData]="part"></app-part>'
})
export class AreaComponent {

  plan = [];

  planasync$ = this.wampService
    .topic('sendplan')
    .subscribe(
      res => {
        let tm = new TableMap();
        this.plan = tm.tableToMap(res.argskw).filter((m) => m.area === 1);
      },
      err => console.log("res err", err),
      () => console.log("res complete"));

  constructor(private wampService: WampService) { }
}

part.component.ts

import { PartDetailsDialogComponent } from './part-details-dialog/part-details-dialog.component';

@Component({
  selector: 'app-part-details',
  templateUrl: './part.component.html'
})
export class PartComponent {
  @Input() partData: any;

  constructor(public dialog: MatDialog) {}

  openDetailsDialog(): void {
    let dialogRef = this.dialog.open(PartDetailsDialogComponent, {
      width: '650px',
      height: '400px',
      data: {
        partData: this.partData
      }
    });
  }
}

part.component.html

<mat-card (click)="openDetailsDialog()">
  <mat-card-title>Nr: {{ partData.partNr }}</mat-card-title>
  <mat-card-content>
      <div>Current speed: {{ partData.speed }}</div>
  </mat-card-content>
</mat-card>

part-details-dialog.component.ts

@Component({
  selector: 'app-part-details-dialog',
  templateUrl: './part-details-dialog.component.html'
})
export class PartDetailsDialogComponent {

  constructor(public dialogRef: MatDialogRef<PartDetailsDialogComponent>, @Inject(MAT_DIALOG_DATA) public data: any) { }

  onNoClick(): void {
    this.dialogRef.close();
  }
}

part-details-dialog.component.html

<h1 mat-dialog-title>Part Details for Nr. {{ data.partData.partNr }}</h1>
<div mat-dialog-content>
  <div>Current speed details: {{ data.partData.speed }}</div>
</div>
<div mat-dialog-actions>
  <button mat-button (click)="onNoClick()">Cancel</button>
  <button mat-button [mat-dialog-close]="data.animal" cdkFocusInitial>Ok</button>
</div>
Billy G
  • 658
  • 1
  • 8
  • 20
  • You can make a common service and make a change in the service so the change from any side will be available for both components – Rohit.007 May 19 '18 at 09:09

3 Answers3

69

You can just change data of the component instance, like this:

this.dialogRef.componentInstance.data = {numbers: value};

Example here: https://stackblitz.com/edit/angular-dialog-update

peng37
  • 4,480
  • 1
  • 11
  • 13
  • 4
    I feel like this should be the selected answer, *much* much more simple than the current selected answer. – ErraticFox Nov 11 '18 at 17:40
  • This certainly helped me along. I had to update the data in a `FormGroup` in the MatDialog however, which I couldn't get to happen automatically when `data` updated (Reactive Forms don't support two-way binding). So I ended up calling `this.dialogRef.componentInstance.updateMyFormGroup(newFormData)` instead of only updating the `data` object. Silly that it took me so long to figure out that `componentInstance` can do anything you want on that Component. – Sygmoral Aug 12 '20 at 22:20
  • 3
    it wont work when dialog has onPush change Strategy – Vugar Abdullayev Jul 05 '21 at 09:30
  • 1
    You could manually trigger the change detection in a `setter` when using `OnPush`, see https://stackblitz.com/edit/angular-dialog-update-assakr?file=src%2Fapp%2Fpart-details-dialog.component.ts – Philippe Jul 26 '23 at 08:49
3

There are 2 ways I can think of at the moment, I don't really like either, but hey...

Both ways involve sending an observable to the dialog through the data.

  1. You could pass on the observable of the part to the part component, and then pass the observable in the data to the dialog. The dialog could then subscribe to the observable and get the updates that way.

AreaComponent

@Component({
  selector: 'app-area',
  template: '<app-part *ngFor="let part of planAsync$ | async; i as index" [partData]="part" [part$]="part$(index)"></app-part>'
})
export class AreaComponent {

  plan = [];

  constructor(private wampService: WampService) {
  }

  part$(index) {
    return this.planAsync$
      .map(plan => plan[index]);
  }

  get planAsync$() {
    return this.wampService
      .topic('sendplan')
      .map(res => {
        let tm = new TableMap();
        return tm.tableToMap(res.argskw).filter((m) => m.area === 1);
      });
  }
}

Part Component

@Component({
  selector: 'app-part-details',
  templateUrl: './part.component.html'
})
export class PartComponent {
  @Input() partData: any;
  @Input() part$

  constructor(public dialog: MatDialog) {}

  openDetailsDialog(): void {
    let dialogRef = this.dialog.open(PartDetailsDialogComponent, {
      width: '650px',
      height: '400px',
      data: {
        partData: this.partData,
        part$: this.part$
      }
    });
  }
}

Of course the question then is how useful it is to even pass partData along, if you could just access the data directly anyway.

  1. The other way requires you to just modify the part component, but you'll have to create a subject that you feed the changes to part that the component receives. I don't really like making subjects, especially if the data is already originally coming from an observable, but whatever:

Part Component

@Component({
  selector: 'app-part-details',
  templateUrl: './part.component.html'
})
export class PartComponent, OnChanges {
  @Input() partData: any;

  private Subject part$ = new Subject();

  constructor(public dialog: MatDialog) {}

  ngOnChanges(changes){
    this.part$.next(changes['partData']);    
  }

  openDetailsDialog(): void {
    let dialogRef = this.dialog.open(PartDetailsDialogComponent, {
      width: '650px',
      height: '400px',
      data: {
        partData: this.partData,
        part$: this.part$
      }
    });
  }
}

finally, to access the data, you could change the html to

Dialog HTML

<div *ngIf="(data.part$ | async) as part">
  <h1 mat-dialog-title>Part Details for Nr. {{ part.partNr }}</h1>
  <div mat-dialog-content>
    <div>Current speed details: {{ part.speed }}</div>
  </div>
  <div mat-dialog-actions>
    <button mat-button (click)="onNoClick()">Cancel</button>
    <button mat-button [mat-dialog-close]="data.animal" cdkFocusInitial>Ok</button>
  </div>
</div>

Or you could subscribe the to observable in the dialog component, and provide the data from there

Jusmpty
  • 171
  • 1
  • 8
  • Thank you for your solutions! Solution 2. would be my preferred one but seams not to work as expected; in fact it does not work at all... :-/ Using Subject gives no data to the template (so nothing is showing at all) and using BehaviorSubject with some initial data shows the current (first) speed but does no more update then. Really strange! If you can not find / imagine something that could be changed and make it work, I will try solution 1. – Billy G May 18 '18 at 15:00
  • Hm, option 2 is indeed cleaner, technically speaking it should work. Changes registers on any change (including at component creation), and then the observable is just passed on. Have you checked that the dialog actually receives an observable? I saw I had a typo in the second option where I pass the data along (part instead of part$) – Jusmpty May 19 '18 at 07:21
  • I just added a minimal example to try out. I will try to implement your second solution into it now. – Billy G May 22 '18 at 12:25
  • https://stackblitz.com/edit/angular-jsinny?file=src%2Fapp%2Fapp.component.ts The dialog template part is not implemented here. The only strange thing is, that the OnNgChanges seams not to fire in this testing environment... – Billy G May 22 '18 at 12:54
  • I think I got somethink... :-) https://stackblitz.com/edit/angular-thd34f?file=src/app/part-details-dialog.component.html – Billy G May 22 '18 at 15:13
2

One component is hosting a dialog. If the component data is changed then the dialog data also should change. For this, first answer by Penghui is working.

In case if that dialog has to do some action based on the data change, there is no (event) trigger telling that dialog data has been changed.

for this the solution is (It will work for this question also):

Step 1: declare class variable dialogRef: MatDialogRef

step2: in Dialog component crate a method xMethod()

step3: keep the dialog reference assigned to this.dialogRef

step4: whenever hosting component wants to change dialog-data use below lines in the component:

  if (this.dialogRef  && this.dialogRef.componentInstance) {
       this.dialogRef.componentInstance.xMethod( data);
  }

step5: check xMethod() in dialog is called and write anything you want to do on the data change event from the component.

pls check this working code:

https://stackblitz.com/edit/angular-arvuho?embed=1&file=src/app/app.component.ts