Search code examples
angularangular2-changedetectionviewchildchange-detector-ref

Why HTMLElement passed to the component from html via event handler points to the wrong html element?


I have a table and I want to pass HTMLElement of the table cell to the component via click event hadler. At the very begininng I have the pointer to the correct table cell but after I init change detection manually the pointer points to the wrong cell (the next to the correct one)

I can't find out why it happens. I created example with console.log(tableCell) before and after initialization of the change detection (method setEditMode in AppComponent)

https://stackblitz.com/edit/angular-module-sandbox-btszav?file=src/app/app.component.html

app.component.html

    <button (click)="showClipboardText()">Fill the table</button>
    <table id="table" (click)="showTarget($event)">
      <thead>
        <tr>
          <th *ngFor="let colName of colNames">
            {{ colName }}
          </th>
        </tr>
      </thead>
      <tbody>
        <tr *ngFor="let rowData of data; let rowIndex = index">
          <td
            #tableCell
            *ngFor="
              let cellData of rowData.length
                ? rowData
                : [].constructor(colNames.length);
              let colIndex = index
            "
            [attr.data-col-name]="colNames[colIndex]"
            (click)="setEditMode(rowIndex, colIndex, tableCell)"
          >
            {{ cellData?.value }}
            <input
              type="text"
              (keydown)="saveEdit($event, rowIndex, colIndex)"
              (blur)="saveEdit($event, rowIndex, colIndex)"
              *ngIf="!!data[rowIndex][colIndex]?.isEditMode"
            />
          </td>
          <button (click)="addRow()">+</button>
        </tr>
      </tbody>
    </table>

app.component.ts

    import {
          ChangeDetectorRef,
          Component,
          ElementRef,
          QueryList,
          ViewChildren,
        } from '@angular/core';
        
        @Component({
          selector: 'my-app',
          templateUrl: './app.component.html',
          styleUrls: ['./app.component.css'],
        })
        export class AppComponent {
          @ViewChildren('tableCell', { read: ElementRef })
          tableCells: QueryList<ElementRef>;
        
          colNames = ['Client', 'Order', 'Total'];
        
          data: Array<Array<{ isEditMode?: boolean; value?: string }>> = [
            Array(this.colNames.length),
          ];
        
          activeCell: { row: number; col: number };
        
          constructor(private cd: ChangeDetectorRef) {}
        
          addRow(): void {
            this.data.push(Array(this.colNames.length));
          }
        
          setEditMode(row: number, col: number, tableCell: HTMLElement): void {
            console.log(tableCell.dataset['colName']);
        
            if (this.activeCell) {
              const previousCellData =
                this.data[this.activeCell.row][this.activeCell.col];
              previousCellData.isEditMode = false;
            }
            if (!!this.data[row][col]) {
              this.data[row][col].isEditMode = true;
              this.data[row][col].value = '';
            } else {
              this.data[row][col] = { ...this.data[row][col], isEditMode: true };
            }
            this.activeCell = { row, col };
            this.cd.detectChanges();
            console.log(tableCell.dataset['colName']);
          }
        
          saveEdit(event: Event, row: number, col: number): void {
            if ((<KeyboardEvent>event).key && (<KeyboardEvent>event).key !== 'Enter')
              return;
        
            const value = (<HTMLInputElement>event.target).value;
            const previousCellData =
              this.data[this.activeCell.row][this.activeCell.col];
            previousCellData.isEditMode = false;
        
            this.data[row][col].value = value;
          }
        }

enter image description here


Solution

  • Working stackblitz: https://stackblitz.com/edit/angular-module-sandbox-cdbpbq?file=src/app/app.component.ts

    Your issue boils down to the fact that we go from having an array of size 3, but with no default values defined. photo: enter image description here

    Thus, each time we defined a value, the colIndex increased by one. You can check this if you set a data attribute for the colIndex as you did for the colName. The solution involves defining a default value for your row.

    data: Array<Array<{ isEditMode?: boolean; value?: string }>> = [
        [
          {
            isEditMode: false,
            value: '',
          },
          {
            isEditMode: false,
            value: '',
          },
          {
            isEditMode: false,
            value: '',
          },
        ],
      ];
    

    However, this also means we need to update our add row method to look like so:

    addRow(): void {
        this.data.push([
          {
            isEditMode: false,
            value: '',
          },
          {
            isEditMode: false,
            value: '',
          },
          {
            isEditMode: false,
            value: '',
          },
        ]);
      }
    

    Previously all you did was just add an array of length three, but with no cells defined within it.