Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PrimeNG turbo table template error when sorting

I'm trying to inject a header template in the turbo table element by using ngTemplateOutlet as it can be seen in the following code snippet :

    <p-table [value]="cars1">
      <ng-template pTemplate="header">
        <ng-container *ngTemplateOutlet="defaultHeader"></ng-container>
      </ng-template>
      <ng-template pTemplate="body" let-rowData>
        <tr>
          <td *ngFor="let col of cols">
            {{rowData[col.field]}}
          </td>
        </tr>
      </ng-template>
    </p-table>

And the before mentioned header template with sorting can be seen here :

    <ng-template #defaultHeader>
      <tr>
        <th *ngFor="let col of cols" [pSortableColumn]="col.field">
            {{col.header}}
            <p-sortIcon [field]="col.field"></p-sortIcon>
        </th>
      </tr>
    </ng-template>

After the page loads, the following error gets thrown:

Error: StaticInjectorError(AppModule)[ScrollableView -> Table]: 
StaticInjectorError(Platform: core)[ScrollableView -> Table]: 
NullInjectorError: No provider for Table

Converting circular structure to JSON

Here is a working StackBlitz example

Since using the template header within an ngTemplateOutlet is a requirement for my use case, I would kindly ask to point out what am I doing wrong here ?

Thank you !

like image 393
CharlieCrews Avatar asked Apr 23 '18 19:04

CharlieCrews


2 Answers

I know this is an old question, but I had the same issue and couldn't find anything helpful anywhere.

I figured out that if I added the 'Table' class to my wrapper component's providers the error would go away. But then I also needed providers for DomHandler, ObjectUtils, and TableService.

This made the errors go away, and allows passing templates into the wrapper and down into the p-table. However, pSortableColumn and pSelectableRow will now only work inside the default templates inside the wrapper component.

There were no errors, because placing the Table class into my wrapper's providers list was giving pSortableColumn and pSelectableRow a new instance of a table to satisfy their dependency. They were effectively selecting and sorting an empty table that never gets rendered.

I fixed this by changing the Table provider to useFactory. My factory function requires my wrapper component as a dependency and then returns it's child turbo table as the provided table. So now when pSortableColumn and pSelectableRow ask for a table, they get the correct table inside of my wrapper component.

    ...
    import { DomHandler } from 'primeng/api';
    import { Table, TableService } from 'primeng/table';
    import { ObjectUtils } from 'primeng/components/utils/objectutils';
    ...

    export function tableFactory(datalist: DataListComponent) {
      return datalist.datatable;
    }
    
    @Component({
      selector: 'app-data-list',
      templateUrl: './data-list.component.html',
      styleUrls: ['./data-list.component.scss'],
      providers: [ DomHandler, ObjectUtils, TableService, {
        provide: Table,
        useFactory: tableFactory,
        deps: [DataListComponent]
      }],
    })
    export class DataListComponent implements OnInit {
      @ViewChild(('datatable')) datatable: Table;
    ...
    }

I should mention that if you pass a template in from the parent component using properties, instead of ContentChildren and QueryList, you need to define your templates INSIDE of the wrapper component or else you will run into the same issue all over again.

For example, passing the template as a property with the template inside the wrapper like this WORKS:

    <wrapper-component [headerTemplate]="defaultHeader">
      <ng-template #defaultHeader>
        ...
      </ng-template>
    </wrapper-component>

But passing the template as a property with the template outside of the wrapper like this DOES NOT WORK:

    <ng-template #defaultHeader>
      ...
    </ng-template>
    <wrapper-component [headerTemplate]="defaultHeader">
    </wrapper-component>

And if you are using ContentChildren and QueryList, then you obviously will need the templates inside the wrapper.

like image 180
C. Witt Avatar answered Sep 21 '22 23:09

C. Witt


This is based on C. Witt's answer, but updated to newer primeng (tested on Angular 10.1.6 and PrimeNG 11.3.1).

import { Component, ContentChild, Input, TemplateRef, ViewChild } from '@angular/core';
import { Table, TableService } from 'primeng/table';

export function tableFactory(tableComponent: TableComponent) {
  return tableComponent.primengTable;
}

@Component({
  selector: 'my-table',
  templateUrl: './table.component.html',
  styleUrls: ['./table.component.scss'],
  providers: [
    TableService,
    {
      provide: Table,
      useFactory: tableFactory,
      deps: [TableComponent]
    }
  ]
})
export class TableComponent {

  @Input()
  data: any[];

  @ContentChild('header', { static: false })
  public headerTemplate: TemplateRef<any>;
  
  @ContentChild('body', { static: false })
  public bodyTempate: TemplateRef<any>;

  @ViewChild('primengTable', { static: true }) public primengTable: Table;
}
<div class="my-table">
    <p-table #primengTable [value]="data">
      <ng-template pTemplate="header">
        <ng-container *ngTemplateOutlet="headerTemplate"></ng-container>
      </ng-template>
      <ng-template pTemplate="body" let-item let-rowIndex="rowIndex">
        <ng-container *ngTemplateOutlet="bodyTempate;context:{ $implicit: item, rowIndex: rowIndex }"></ng-container> 
      </ng-template>
    </p-table>
</div>

NOTE: primengTable is made public only because I want to have access to it from outside for complex operations. It can be private for this code to still work.

Usage:

<my-table [data]="data">
    <ng-template #header>
        <tr>
            <th>Index</th>
            <th pSortableColumn="name">Name <p-sortIcon field="name"></p-sortIcon></th>
            <th pSortableColumn="age">Age <p-sortIcon field="age"></p-sortIcon></th>
        </tr>
    </ng-template>
    <ng-template #body let-item let-rowIndex="rowIndex">
        <tr>
            <td>{{ rowIndex }}</td>
            <td>{{ item.name }}</td>
            <td>{{ item.age }}</td>
        </tr>
    </ng-template>
</my-table>
like image 26
Episodex Avatar answered Sep 24 '22 23:09

Episodex