Angular Data GridRow pre-populating

Populate newly-added rows with predefined template values, using cell renderers.

Example

The example below shows how cell renderers can be used to populate the template values in empty rows. When a cell in the empty row is edited, the beforeChange callback fills the row with the template values.

/* file: app.component.ts */
import {AfterViewInit, Component, ViewChild} from '@angular/core';
import {GridSettings, HotTableComponent} from '@handsontable/angular-wrapper';
import Handsontable from 'handsontable';

@Component({
  selector: 'app-example1',
  template: `
    <hot-table
      [settings]="hotSettings!" [data]="hotData">
    </hot-table>
  `,
  standalone: false
})
export class AppComponent implements AfterViewInit {
  @ViewChild(HotTableComponent, {static: false}) hotTable!: HotTableComponent;

  hotData = [

  ];

  hotSettings: GridSettings = {

  };

  ngAfterViewInit() {
    const templateValues = ['one', 'two', 'three'];
    const data = [
      ['', 'Tesla', 'Nissan', 'Toyota', 'Honda'],
      ['2017', 10, 11, 12, 13],
      ['2018', 20, 11, 14, 13],
      ['2019', 30, 15, 12, 13],
    ];

    function isEmptyRow(instance: Handsontable, row: number) {
      const rowData = instance.getDataAtRow(row);

      for (let i = 0, ilen = rowData.length; i < ilen; i++) {
        if (rowData[i] !== null) {
          return false;
        }
      }

      return true;
    }

    const defaultValueRenderer = (
      instance: any,
      td: any,
      row: any,
      col: any,
      prop: any,
      value: any,
      cellProperties: any
    ) => {
      if (value === null && isEmptyRow(instance, row)) {
        value = templateValues[col];
        td.style.color = '#999';
      } else {
        td.style.color = '';
      }

      Handsontable.renderers.TextRenderer(
        instance,
        td,
        row,
        col,
        prop,
        value,
        cellProperties
      );
    };

    const hot = this.hotTable.hotInstance!;

    this.hotSettings = {
      startRows: 8,
      startCols: 5,
      minSpareRows: 1,
      contextMenu: true,
      height: 'auto',
      licenseKey: 'non-commercial-and-evaluation',
      cells() {
        return { renderer: defaultValueRenderer };
      },
      beforeChange: function (changes) {
        const instance = hot;
        const columns = instance.countCols();
        const rowColumnSeen = {};
        const rowsToFill = {};
        const ch = changes === null ? [] : changes!;

        for (let i = 0; i < changes.length; i++) {
          // if oldVal is empty
          if (ch[i]![2] === null && ch[i]![3] !== null) {
            if (isEmptyRow(instance, ch[i]![0])) {
              // add this row/col combination to the cache so it will not be overwritten by the template
              // @ts-ignore
              rowColumnSeen[`${ch[i]![0]}/${ch[i]![1]}`] = true;
              // @ts-ignore
              rowsToFill[ch[i][0]] = true;
            }
          }
        }

        for (const r in rowsToFill) {
          if (rowsToFill.hasOwnProperty(r)) {
            for (let c = 0; c < columns; c++) {
              // if it is not provided by user in this change set, take the value from the template
              // @ts-ignore
              if (!rowColumnSeen[`${r}/${c}`]) {
                changes.push([Number(r), c, null, templateValues[c]]);
              }
            }
          }
        }
      },
      autoWrapRow: true,
      autoWrapCol: true,
    }

    this.hotTable.hotInstance!.loadData(data);
  }

}



/* file: app.module.ts */
import { NgModule, ApplicationConfig } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { registerAllModules } from 'handsontable/registry';
import { HOT_GLOBAL_CONFIG, HotGlobalConfig, HotTableModule } from '@handsontable/angular-wrapper';
import { CommonModule } from '@angular/common';
import { NON_COMMERCIAL_LICENSE } from '@handsontable/angular-wrapper';

import { AppComponent } from './app.component';

// register Handsontable's modules
registerAllModules();

export const appConfig: ApplicationConfig = {
  providers: [
    {
      provide: HOT_GLOBAL_CONFIG,
      useValue: {
        themeName: 'ht-theme-main',
        license: NON_COMMERCIAL_LICENSE,
      } as HotGlobalConfig
    }
  ],
};

@NgModule({
  imports: [ BrowserModule, HotTableModule, CommonModule ],
  declarations: [ AppComponent ],
  providers: [...appConfig.providers],
  bootstrap: [ AppComponent ]
})

export class AppModule { }