Angular Dialog changes are overwriting always the UI

Viewed 437

I am passing data in an Angular dialog, but in meantime when I edit something in the Dialog it overwrites the data even I didn't save them. For example I have an Object and I click ot edit it. It opens an Angular Dialog with all data of this Object.

Is it possible only to change data when I click save button. I heard about Object.assign it can be useful but didn't know how to use it. I tried with Object.assign but it didn't work. Why is not working I think that this.data holds another data as the this.data.education. See the code below This is my code of the dialog.

constructor(@Inject(MAT_DIALOG_DATA) public data: EditEducation,
              private dialogRef: MatDialogRef<EducationDialogComponent>,
              public dataService: ModelDataService) { }

  ngOnInit(): void {
    if (!this.data.edit) {
      this.data.education = {} as SubCategory;
    } else {
      ({education: this.data.education} = Object.assign({}, this.data));
    }
  }

And this is how I open the dialog.

  <ul>
    <li class="fa fa-pencil addIconTop" (click)="editEducation({edit: true, education: subCategory, model: model})"></li>
    <button (click)="addEducation({edit: false, model: model})" class="btn"><i class="fa fa-plus addIconBottom"></i></button>
    <button [disabled]="education.subCategories.length === 1" (click)="deleteSubCategory(i)" class="btn"><i class="fa fa-trash deleteIconRight"></i></button>
    <li class="fa fa-arrow-down moveIconDown"></li>
    <li class="fa fa-arrow-up moveIconTop"></li>
  </ul>

public editData(data: EditEducation) {
   this.dialog.open(DataDialogComponent, {
        data,
        });
  }
public addData(data: EditEducation) {
    this.dialog.open(DataDialogComponent, {
      data,
    });
  }

This is one interface to see if is one new Object or is need to be edited.

export interface EditEducation {
  education?: SubCategory;
  edit?: boolean;
  model?: Model;
}

This is the interface of Education

export interface Education {
  subCategories: EducationSubCategory[];
}


 export interface EducationSubCategory {
  name: string;
  startDate: number;
  endDate: number;
  graduation: string;
  title?: string;
  description: string;
}
2 Answers

The Problem

You are experincing a case of variables being passed by reference

Javascript has 5 primitive data types that are passed by value: Boolean, null, undefined, String, and Number
Variables that are assigned a non-primitive value are given a reference to that value. That reference points to the object’s location in memory. The variables don’t actually contain the value
When a reference type value, an object, is copied to another variable using =, the address of that value is what’s actually copied over as if it were a primitive. Objects are copied by reference instead of by value.

Solution

The easiest way to solve the problem would be to use the spread operator (...) while assigning your variables. Below is a refactor of your code that should solve the problem

constructor(@Inject(MAT_DIALOG_DATA) public data: EditEducation,
              private dialogRef: MatDialogRef<EducationDialogComponent>,
              public dataService: ModelDataService) { }

  ngOnInit(): void {
    if (!this.data.edit) {
      this.data.education = {} as SubCategory;
    } else {
      ({education: this.data.education} = {...this.data };
    }
  }
public editData(data: EditEducation) {
   this.dialog.open(DataDialogComponent, { ...data});
  }
public addData(data: EditEducation) {
    this.dialog.open(DataDialogComponent, {...data});
  }

It is best to copy the object before sending it into the dialog. This way your original data stays unedited:

   const dialogRef = this.dialog.open(DataDialogComponent, {
     data: _.cloneDeep(data),
   });

For the deep clone I suggest you use the lodash library. It has a function called "cloneDeep" which copies the object inclusive all nested objects.

In your DataDialogComponent.ts you can send the updated data back to the parent component through the DialogRef when you save the edits:

  save(): void {
    this.dialogRef.close(this.data)
  }

In the parent component you can subscribe to the DialogRef and update the original data:

   dialogRef.afterClosed().subscribe((updatedData) => {
     if (updatedData) {
       this.data = updatedData;
     }
   });

Complete Code

ParentComponent.ts

import { MatDialog } from '@angular/material/dialog';
import { DialogComponent } from '../dialog/dialog.component';
import * as _ from 'lodash';

export class SomeComponent implements OnInit {
  data: Education;

  constructor(public dialog: MatDialog) {}

  public editData(data: EditEducation) {
    const dialogRef = this.dialog.open(DataDialogComponent, {
      data: _.cloneDeep(data),
    });

    dialogRef.afterClosed().subscribe((updatedData) => {
      if (updatedData) {
        this.data = updatedData;
      }
    });
  }
}

DataDialogComponent.ts

import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';

export class DataDialogComponent implements OnInit {

  constructor(
    @Inject(MAT_DIALOG_DATA) public data: EditEducation,
    private dialogRef: MatDialogRef<DataDialogComponent>
  ) {}

  // Implement your methods

  save(): void {
    this.dialogRef.close(this.data)
  }

  discard(): void {
    this.dialogRef.close()
  }
}
Related