Strange behaviour after updating record

Dear, when I update a record in my grid, I get strange behaviour. The record is still in edit mode, while the update call is done when I try to click out, and the grid is not refreshed.

(I want to attach a video, but it says "file failed to upload")


import { Component, OnInit, ViewChild } from '@angular/core';
import { TranslationService } from '@arcelormittal-platform/core';
import {
  ProcessesGridService,
  WcmPillarsService,
  StrategicalAxisesService,
} from '@masterdata/services';
import {
  LoadingIndicator,
  PageSettingsModel,
  EditSettingsModel,
  GridComponent,
  RecordDoubleClickEventArgs,
} from '@syncfusion/ej2-angular-grids';
import { DataManager } from '@syncfusion/ej2-data';
import { FormArray, FormBuilder, Validators } from '@angular/forms';
import { FieldSettingsModel } from '@syncfusion/ej2-angular-dropdowns';
import { StrategicalAxisDropdownItem, WcmPillarDropdownItem } from '@masterdata/models';
import { Observable } from 'rxjs';

@Component({
  selector: 'am-processes',
  templateUrl: './processes.component.html',
  styleUrls: ['./processes.component.scss'],
})
export class ProcessesComponent implements OnInit {
  @ViewChild('grid') grid: GridComponent | undefined;

  public dataManager: DataManager;
  public loadingIndicator: Partial<LoadingIndicator>;
  public pageSettings: PageSettingsModel;
  public editSettings: EditSettingsModel;

  public currentNameFormArray: FormArray | undefined;
  public nameDialogVisible = false;

  public currentDescriptionFormArray: FormArray | undefined;
  public descriptionDialogVisible = false;

  public wcmPillars$: Observable<WcmPillarDropdownItem[]> | undefined;
  public selectedWcmPillarIds: number[] | undefined;
  public wcmPillarFields: FieldSettingsModel;

  public strategicalAxises$: Observable<StrategicalAxisDropdownItem[]> | undefined;
  public selectedStrategicalAxisIds: number[] | undefined;
  public strategicalAxisFields: FieldSettingsModel;

  private currentRowData: any | undefined;
  private currentColumnName: string | undefined;

  constructor(
    private readonly processesService: ProcessesGridService,
    private readonly translationService: TranslationService,
    private readonly fb: FormBuilder,
    private readonly wcmPillarsService: WcmPillarsService,
    private readonly strategicalAxisesService: StrategicalAxisesService
  ) {}

  get nameField(): string {
    return `name${this.translationService.getCurrentLanguage().toUpperCase()}`;
  }

  get descriptionField(): string {
    return `description${this.translationService.getCurrentLanguage().toUpperCase()}`;
  }

  public ngOnInit(): void {
    this.loadingIndicator = { indicatorType: 'Shimmer' };
    this.dataManager = this.processesService.getGridDataManager();
    this.pageSettings = { pageSize: 20 };
    this.editSettings = {
      allowEditing: true,
      allowAdding: true,
      allowDeleting: true,
      mode: 'Normal',
    };
    this.setupWcmPillars();
    this.setupStrategicalAxises();
  }

  public recordDoubleClick(args: RecordDoubleClickEventArgs) {
    this.currentColumnName = args.column.field;
    this.currentRowData = args.rowData;
    this.currentNameFormArray = this.buildFormArray(args, 'name');
    this.currentDescriptionFormArray = this.buildFormArray(args, 'description');
  }

  public actionBegin(args: any) {
    const requestType = args.requestType;
    if (requestType === 'beginEdit') {
      if (this.currentColumnName.startsWith('name')) {
        args.cancel = true;
        this.nameDialogVisible = true;
      }

      if (this.currentColumnName.startsWith('description')) {
        args.cancel = true;
        this.descriptionDialogVisible = true;
      }
      this.setCurrentWcmPillarValues(args.rowData);
      this.setCurrentStrategicalAxises(args.rowData);
    }

    if (requestType === 'save') {
      args.data.attachedWcmPillars = this.selectedWcmPillarIds;
      args.data.attachedStrategicalAxises = this.selectedStrategicalAxisIds;
      const nameTranslationsAsLocalizedText = this.currentNameFormArray.value.map(
        (translation) => ({
          code: translation.code,
          text: translation.text,
        })
      );
      args.data.names = nameTranslationsAsLocalizedText;
      const descriptionTranslationsAsLocalizedText = this.currentDescriptionFormArray.value.map(
        (translation) => ({
          code: translation.code,
          text: translation.text,
        })
      );
      args.data.descriptions = descriptionTranslationsAsLocalizedText;
    }
  }

  public hideNameDialog() {
    this.nameDialogVisible = false;
  }

  public hideDescriptionDialog() {
    this.descriptionDialogVisible = false;
  }

  public saveNameTranslations() {
    this.saveTranslations(this.currentNameFormArray);
  }

  public saveDescriptionTranslations() {
    this.saveTranslations(this.currentDescriptionFormArray);
  }

  private setCurrentWcmPillarValues(data: any) {
    this.selectedWcmPillarIds = data.wcmPillars.map((x) => x.id);
  }

  private setupWcmPillars() {
    this.wcmPillars$ = this.wcmPillarsService.GetDropdownItems();
    this.wcmPillarFields = { value: 'id', text: 'name' };
  }

  private setCurrentStrategicalAxises(data: any) {
    this.selectedStrategicalAxisIds = data.strategicalAxises.map((x) => x.id);
  }

  private setupStrategicalAxises() {
    this.strategicalAxises$ = this.strategicalAxisesService.getDropdownItems();
    this.strategicalAxisFields = { value: 'id', text: 'name' };
  }

  private buildFormArray(args: RecordDoubleClickEventArgs, fieldPrefix: string): FormArray {
    const newFormArray = new FormArray([]);
    Object.keys(args.rowData)
      .filter((key) => key.startsWith(fieldPrefix) && key !== fieldPrefix && !!args.rowData[key])
      .forEach((key) => {
        newFormArray.push(
          this.fb.group({
            code: this.fb.control(key.slice(-2), Validators.required),
            text: this.fb.control(args.rowData[key], Validators.required),
          })
        );
      });
    return newFormArray;
  }

  private saveTranslations(formArray: FormArray | undefined): void {
    if (!formArray || !this.currentRowData) return;

    const translations = formArray?.value;
    const initialValues = JSON.parse(JSON.stringify(this.currentRowData));
    const columnName = this.currentColumnName.slice(0, -2);

    translations.forEach((translation) => {
      const field = `${columnName}${translation.code.toUpperCase()}`;
      this.currentRowData[field] = translation.text;
    });

    this.currentRowData['names'] = Object.keys(this.currentRowData)
      .filter((key) => key.startsWith('name') && key !== 'name' && !!this.currentRowData[key])
      .map((key) => ({ code: key.slice(-2), text: this.currentRowData[key] }));

    this.currentRowData['descriptions'] = Object.keys(this.currentRowData)
      .filter(
        (key) =>
          key.startsWith('description') && key !== 'description' && !!this.currentRowData[key]
      )
      .map((key) => ({ code: key.slice(-2), text: this.currentRowData[key] }));

    this.currentRowData['attachedWcmPillars'] = this.selectedWcmPillarIds;
    this.currentRowData['attachedStrategicalAxises'] = this.selectedStrategicalAxisIds;

    const allHaveValues = translations.every((translation) => translation.text);
    if (JSON.stringify(initialValues) !== JSON.stringify(this.currentRowData) && allHaveValues) {
      const rowIndex = this.grid.getRowIndexByPrimaryKey(this.currentRowData.id);
      this.grid.updateRowValue(rowIndex, this.currentRowData);
    }

    this.nameDialogVisible = false;
    this.descriptionDialogVisible = false;
  }
}
<mat-card>
  <div class="header">
    <mat-icon class="header-icon">developer_board</mat-icon>
    <div class="title">
      <h3>{{ 'Processes.SubTitle' | translate }}</h3>
      <h2>{{ 'Processes.Title' | translate }}</h2>
    </div>
  </div>

  <ejs-grid
    #grid
    [dataSource]="dataManager"
    [loadingIndicator]="loadingIndicator"
    [pageSettings]="pageSettings"
    [allowPaging]="true"
    [editSettings]="editSettings"
    (recordDoubleClick)="recordDoubleClick($event)"
    (actionBegin)="actionBegin($event)"
  >
    <e-columns>
      <e-column
        field="id"
        [isPrimaryKey]="true"
        [visible]="false"
        [allowEditing]="false"
      ></e-column>
      <e-column
        [field]="nameField"
        [headerText]="'Processes.Headers.Name' | translate"
        [allowEditing]="false"
      ></e-column>
      <e-column
        field="code"
        [headerText]="'Processes.Headers.Code' | translate"
        width="120"
      ></e-column>
      <e-column
        [field]="descriptionField"
        [headerText]="'Processes.Headers.Description' | translate"
        [allowEditing]="false"
      ></e-column>
      <e-column field="wcmPillarNames" [headerText]="'Processes.Headers.WcmPillars' | translate">
        <ng-template #editTemplate>
          <ejs-multiselect
            [dataSource]="wcmPillars$ | async"
            [fields]="wcmPillarFields"
            [(value)]="selectedWcmPillarIds"
          ></ejs-multiselect>
        </ng-template>
      </e-column>
      <e-column
        field="strategicalAxisNames"
        [headerText]="'Processes.Headers.StrategicalAxis' | translate"
      >
        <ng-template #editTemplate>
          <ejs-multiselect
            [dataSource]="strategicalAxises$ | async"
            [fields]="strategicalAxisFields"
            [(value)]="selectedStrategicalAxisIds"
          ></ejs-multiselect>
        </ng-template>
      </e-column>
      <e-column
        [headerText]="'Processes.Headers.IsActive' | translate"
        width="70"
        textAlign="Right"
        [allowEditing]="false"
      >
        <ng-template #template let-data>
          <mat-slide-toggle [checked]="data.isActive"></mat-slide-toggle>
        </ng-template>
      </e-column>
    </e-columns>
  </ejs-grid>
</mat-card>

<ejs-dialog
  #nameDialog
  isModal="true"
  showCloseIcon="true"
  [(visible)]="nameDialogVisible"
  [width]="500"
>
  <ng-template #header>
    <mat-icon>badge</mat-icon>
    <div class="title">
      <h2>{{ 'Processes.Popups.Name.Title' | translate }}</h2>
      <h3>{{ 'Processes.Popups.Name.SubTitle' | translate }}</h3>
    </div>
  </ng-template>
  <ng-template #content>
    <am-translations-form
      *ngIf="currentNameFormArray"
      [translations]="currentNameFormArray"
      [label]="'Roles.Details.Name'"
    ></am-translations-form>
  </ng-template>
  <ng-template #footerTemplate>
    <button mat-button (click)="hideNameDialog()">{{ 'Common.Cancel' | translate }}</button>
    <button mat-raised-button color="accent" (click)="saveNameTranslations()">
      {{ 'Common.Save' | translate }}
    </button>
  </ng-template>
</ejs-dialog>

<ejs-dialog
  #descriptionDialog
  isModal="true"
  showCloseIcon="true"
  [(visible)]="descriptionDialogVisible"
  [width]="500"
>
  <ng-template #header>
    <mat-icon>badge</mat-icon>
    <div class="title">
      <h2>{{ 'Processes.Popups.Description.Title' | translate }}</h2>
      <h3>{{ 'Processes.Popups.Description.SubTitle' | translate }}</h3>
    </div>
  </ng-template>
  <ng-template #content>
    <am-translations-form
      *ngIf="currentDescriptionFormArray"
      [translations]="currentDescriptionFormArray"
      [label]="'Roles.Details.Description'"
    ></am-translations-form>
  </ng-template>
  <ng-template #footerTemplate>
    <button mat-button (click)="hideDescriptionDialog()">{{ 'Common.Cancel' | translate }}</button>
    <button mat-raised-button color="accent" (click)="saveDescriptionTranslations()">
      {{ 'Common.Save' | translate }}
    </button>
  </ng-template>
</ejs-dialog>


1 Reply

VK Vasanthakumar K Syncfusion Team August 13, 2024 04:22 AM UTC

Hi Niels Van Goethem,


Greetings from Syncfusion support.


We have validated your query and understand that you are facing complexity/issue with the grid’s editing (update) with your cancel customization and manual programmatic CRUD operation. However, the provided information is not enough to validate your query on our end.


We are unaware of your service configurations (such as ProcessesGridService, WcmPillarsService, StrategicalAxisesService) and models (StrategicalAxisDropdownItem, WcmPillarDropdownItem) which are imported from other files. Additionally, you are using TranslationService from @arcelormittal-platform/core module, but npm didn’t find that module. This information is related to your grid’s edit templates, column templates, and programmatic editing operation, which are necessary to validate your query. However, we have tried to prepare the sample with dummy data based on your provided customization code information, but due to lack of information and clarity, we weren’t able to prepare the sample properly.


So please provide the complete programmatic edit operation performing customization code and its use case scenario (reason for not using our default editing operation instead of programmatic editing operation after canceling the default operation) or try to replicate the issue with our prepared sample below by modifying it similar to your customization along with a video demonstration of the issue you are facing for further validation of your query on our end.


Prepared sample: 193134 - StackBlitz


Regards,

Vasanthakumar K


Loader.
Up arrow icon