angular-datatables: ngTemplateRef + colvis-button (Turn columns "visible":false) - TypeError: Cannot read property 'appendChild' of null

IMPORTANT: I will try to deliver a stackblitz or minimal reproduction project later. I tried to create a stackblitz or use the stackblitz “angular-datatables-gitter” but I couldn’t get them to work with ngTemplateRef. Please don’t close this issue immediately!

🪲 bug report

I have a datatable that gets initialized within “ngAfterViewInit”. It uses “ajax” to gather data. I use the datatable “server side the angular way”. I have activated the “buttons” plugin to use “colvis” and “stateSave: true”.

In my use-case I need one column with a ngTemplateRef. I initialize “this.columns” and put the column with ngTemplateRef at the end, then pass it into “dtOptions”. The Datatable initializes fine.

Once I turn one column before my ngTemplateRef column invisible and reload the page, the datatable crashes with this error:

TypeError: Cannot read property 'appendChild' of null
    at EmulatedEncapsulationDomRenderer2.appendChild (platform-browser.js:670)
    at angular-datatables.directive.js:79
    at Array.forEach (<anonymous>)
    at S.fn.init.rowCallback (angular-datatables.directive.js:68)
    at jquery.dataTables.js:6682
    at Function.map (jquery.min.js:2)
    at _fnCallbackFire (jquery.dataTables.js:6681)
    at _fnDraw (jquery.dataTables.js:3503)
    at _fnAjaxUpdateDraw (jquery.dataTables.js:4169)
    at jquery.dataTables.js:4009
    at callback (jquery.dataTables.js:3901)
    at SafeSubscriber._next (material-template-dt.component.ts:132)
    at SafeSubscriber.__tryOrUnsub (Subscriber.js:183)
    at SafeSubscriber.next (Subscriber.js:122)
    at Subscriber._next (Subscriber.js:72)
    at Subscriber.next (Subscriber.js:49)
    at CatchSubscriber._next (Subscriber.js:72)
    at CatchSubscriber.next (Subscriber.js:49)
    at MapSubscriber._next (map.js:35)
    at MapSubscriber.next (Subscriber.js:49)

🔬 Minimal Reproduction

IMPORTANT: Will deliver stackblitz or project later!

  • Create a datatable
  • Example code:

x.component.ts

dataTableActions: Array<DataTableAction> = [
    {
      cmd: "edit",
      label: "Bearbeiten"
    },
    {
      cmd: "delete",
      label: "Löschen"
    },
  ];

  @ViewChild('dtActions') dtActions: TemplateRef<ActionsComponent>;

.
.
.

ngAfterViewInit(): void {
    this.columns.push(...[
      {
        title: "Name",
        data: "name"
      },
      {
        title: "Age",
        data: "age"
      },
      {
        title: "Info",
        data: "info"
      }
    ]);

    if (this.dataTableActions.length > 0) {
      this.columns.push({
        title: "Aktionen",
        data: null,
        orderable: false,
        searchable: false,
        defaultContent: "",
        ngTemplateRef: {
          ref: this.dtActions,
          context: {
            captureEvents: this.onCaptureEvent.bind(this)
          }
        }
      });
    }

    this.dtOptions = {
      language: german,
      dom: '<l>Bfrtip',
      buttons: [
        {
          extend: 'colvis',
          columns: ':not(.noVis)'
        },
        'excel',
      ],
      columnDefs: [
        {
          targets: "_all",
          className: "valign-middle",
        },
        {
          targets: [0],
          className: "text-right noVis",
        },
      ],
      stateSave: true,
      serverSide: true,
      processing: true,
      searchDelay: 600,
      ajax: (dataTablesParameters: any, callback) => {
        this.service
          .getJsonLd().subscribe(resp => {
            callback({
              recordsTotal: resp['hydra:totalItems'],
              recordsFiltered: resp['hydra:totalItems'],
              data: resp['hydra:member']
            });
          });
      },
      columns: this.columns
    };

onCaptureEvent(event: DataTableActionsEvent): void {
    // Stuff
  }

x.component.html

<ng-template #dtActions let-data="adtData" let-emitter="captureEvents">
    <app-actions [actions]="dataTableActions" [data]="data" (emitter)="emitter($event)"></app-actions>
  </ng-template>

      <table id="material--template-dt" *ngIf="columns.length" datatable [dtOptions]="dtOptions" class="table table-striped w-100"></table>
  
  • Load the page
  • Use “colvis”-Button to hide one column before our ngTemplateRef column
  • Reload the page

🎱 Expected behavior

The Datatable should render fine.

🌐 Your Environment

  • node version: 16.4
  • angular version: 11.2.8
  • angular-cli version: 11.2.7
  • jquery version: 3.6.0
  • datatables version: ^1.10.20
  • angular-datatables version: 12.0.0

📝 Additional context

It seems like in the line 66-80 within angular-datatables.directive.js are the issue:

// Filter columns using `ngTemplateRef`
  var colsWithTemplate = columns_1.filter(function (x) { return x.ngTemplateRef && !x.ngPipeInstance; });
  colsWithTemplate.forEach(function (el) {
      var _a = el.ngTemplateRef, ref = _a.ref, context = _a.context;
      // get <td> element which holds data using index
      var index = columns_1.findIndex(function (e) { return e.data == el.data; });
      var cellFromIndex = row.childNodes.item(index);
      // render onto DOM
      // finalize context to be sent to user
      var _context = Object.assign({}, context, context === null || context === void 0 ? void 0 : context.userData, {
          adtData: data
      });
      var instance = self.vcr.createEmbeddedView(ref, _context);
      self.renderer.appendChild(cellFromIndex, instance.rootNodes[0]);
  });

I think after disabling some columns and reloading the page, there is a bug with referenced indeces. Naively spoken: The column of ngTemplateRef gets a certain index beforehand (index = 3), a column gets "visible: false, the column.length only goes to index 2 and then it tries to map everything to index 3 causing a null reference error.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 21

Most upvoted comments

Hi @mtrzensky

bug confirmed. Let me see if I can work something out this weekend 😃