[Fixed] Angular Datatable updated data disappear upon export/sort/search

Issue

I’m still a rookie when it comes to angular datatables. My page displays breeds in a table and can toggle the read-only inputs to editable fields and delete entries. I also want these data to be exported as excel or pdf. The problem I’m facing is that after I edit or delete (after the API call that changes the values in my database) the data in the table on the screen changes BUT it’s not updated when exporting (excel or pdf) meaning that only the initial data when the page was first loaded remains when exporting…
The way it works: an icon enables the user to edit -> user edits then confirms -> API call is made through the service of this module -> data changes -> Excel/Pdf export still has same data as before
HTML

<table datatable [dtOptions]="dtExportButtonOptions" class="table table-bordered table-striped mb-0">
            <thead>
                <tr>
                    <th class="text-center">Breed Name</th>
                    <th class="text-center">Options</th>
                </tr>
            </thead>
            <tbody>
                <tr *ngFor="let breed of BREEDS">
                    <td class="text-center">
                        <input class="form-control" type="text" placeholder="Insert Breed Name"
                            [(ngModel)]="breed.BREED_NAME" [readonly]="!breed.Edit">
                        <span style="display: none;" >{{breed.BREED_NAME}}</span>
                    </td>

                    <td class="text-center">
                        <div class="row align-items-center m-l-0">
                            <div class="col-sm-6 text-right">
                                <button *ngIf="!breed.Edit" type="button" class="btn btn-icon btn-outline-info mr-2"
                                    (click)="ToggleEdit(breed)"><i class="feather icon-edit-2"></i></button>
                                <button *ngIf="breed.Edit" type="button" class="btn btn-icon btn-outline-success mr-2"
                                    (click)="EditBreed(breed)"><i class="feather icon-check-circle"></i></button>
                            </div>
                            <div class="col-sm-6 text-left">
                                <button *ngIf="!breed.Edit" type="button" class="btn  btn-icon btn-outline-danger"
                                    (click)="OpenDeleteModal(breed)"><i class="feather icon-trash-2"></i></button>
                                <button *ngIf="breed.Edit" type="button" class="btn  btn-icon btn-outline-danger"
                                    (click)="ToggleEdit(breed)"><i class="feather icon-slash"></i></button>
                            </div>
                        </div>
                    </td>
                </tr>
            </tbody>
        </table>

TYPESCRIPT

addCusForm: FormGroup;
  dtExportButtonOptions: any = {};
  DeletedBreed: Breed;
  _params_EditBreed: Breed = new Breed();
  _params_DeleteBreed: Params_Delete_Breed = new Params_Delete_Breed();
constructor(
    private proxy: Proxy,
    private fb: FormBuilder,
    private CmSvc: CommonService,
    private GSSVC: GeneralsetService
  ) { }

  ngOnInit(): void {
    this.addCusForm = this.fb.group({
      BREED_ID: [-1],
      OWNER_ID: [1],
      BREED_NAME: [, [Validators.required]],
    });

    var buttonCommon = {
      exportOptions: {
        // format: {
        //   body: function (data, row, column, node) {
        //     return node.firstChild.tagName === "INPUT" ?
        //       node.firstElementChild.value :
        //       data;
        //   }
        // },
        columns: [0],
      }
    };

    this.dtExportButtonOptions = {
      dom: 'Bfrtip',
      buttons: [
        $.extend(true, {}, buttonCommon, {
          extend: 'copyHtml5'
        }),
        $.extend(true, {}, buttonCommon, {
          extend: 'excelHtml5',
          titleAttr: 'Export to Excel',
        }),
        $.extend(true, {}, buttonCommon, {
          extend: 'print',
          titleAttr: 'Print',
        }),
      ]
    };
  }
ToggleEdit(breed) {
    breed.Edit = !breed.Edit;
  }

  EditBreed(breed: Breed) {
    if (breed.BREED_NAME.length > 0) {
      this._params_EditBreed = breed;
      console.log(this._params_EditBreed);

      this.proxy.Edit_Breed(this._params_EditBreed).subscribe((result) => {
        if (result) {
          this.CmSvc.ShowMessage('Breed ' + this._params_EditBreed.BREED_NAME + ' has been successfully Updated.',
            3500);
          this.addCusForm.get('BREED_NAME').reset();
          this.GSSVC.resolve();
        }
      });
      this.ToggleEdit(breed);
    }
  }

  SubmitBreed() {
    if (this.addCusForm.valid) {
      this._params_EditBreed = this.addCusForm.getRawValue() as Breed;
      console.log(this._params_EditBreed);

      this.proxy.Edit_Breed(this._params_EditBreed).subscribe((result) => {
        if (result) {
          this.CmSvc.ShowMessage('Breed ' + this._params_EditBreed.BREED_NAME + ' has been successfully submitted.',
            3500);
          this.addCusForm.get('BREED_NAME').reset();
          this.GSSVC.resolve();
        }
      });
    }
  }

DeleteBreed() {
    this._params_DeleteBreed.BREED_ID = this.DeletedBreed.BREED_ID;
    this.proxy.Delete_Breed(this._params_DeleteBreed).subscribe((result) => {
      if (!result) {
        this.CmSvc.ShowMessage(
          'Breed ' + this.DeletedBreed.BREED_NAME + ' has been successfully removed.',
          3500
        );
        this.modalDelete.hide();
        this.DeletedBreed = null;
        //this.ToggleEdit(breed);
        this.GSSVC.resolve();
      }
    })
  }

!!You might notice that I have a hidden span in the table HTML. I did this because input fields weren’t displayed in the table so I read this is a turnaround solution. I tried another solution which you will see commented in the TypeScript file, but this didn’t enable me to sort out the columns or search.
Here’s a "non-working" stackblitz link for a better idea of the whole architecture : https://stackblitz.com/edit/angular-bppwap?embed=1&file=src/app/hello.component.html
I hope I provided enough enough and thanks for the help in advance.

Solution

I did use the reRender function provided by Maria but that alone was not enough.
First off I moved the API function that gets data from the parent to the child component because I need to activate the reRender function upon getting data. (I could’ve activated it as well from the parent component using @ViewChild but this is just to keep things simple).

First thing to add

ngAfterViewInit(): void {
    this.dtTrigger.next();
    this.isfirst = false;
  }

the isfirst flag is to differentiate if it’s the first time the page loads or not, since reRendering onInit throws errors.

The following is the reRender function

reRender(): void {
    this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => {
      // Destroy the table first
      dtInstance.destroy();
      // Call the dtTrigger to rerender again
      this.dtTrigger.next();
    });
  }

And this is what happens upon retrieving data

this.GSSVC.onDog_breedChange
      .pipe(takeUntil(this._unsubscribAll)).subscribe(breed => {
        if (breed) {
          this.BREEDS = breed;
          if (this.BREEDS.length === 0) {
            this.EmptyData = true;
          }
          else {
            this.EmptyData = false;
          }
          if (!this.isfirst)
            this.reRender();
        }
      });

It is also important to note that I had a ngIf flag on the table tag in case the incoming data was empty. This will throw some errors, it was shown when I deleted everything from the table then tried to add an entry, thus either remove ngIf or replace with [hidden].

Leave a Reply

(*) Required, Your email will not be published