# ngx-tablo

[![npm version](https://badge.fury.io/js/@notiz%2Fngx-tablo.svg)](https://www.npmjs.com/package/@notiz/ngx-tablo)

## Installation

Install `ngx-tablo` and add [@angular/material](https://material.angular.io/guide/getting-started#install-angular-material).

```bash
npm i @notiz/ngx-tablo
```

Import `Tablo` in your component.

```ts
import { Component } from '@angular/core';
import { Tablo, TabloColumns } from '@notiz/ngx-tablo';

interface User {
  name: string;
  age: number;
}

@Component({
  selector: 'niz-default',
  standalone: true,
  imports: [Tablo],
  template: ` <ngx-tablo [data]="users" [columns]="columns"></ngx-tablo> `,
  styles: [],
})
export class DefaultComponent {
  users: User[] = [
    ...
  ];

  columns: TabloColumns<User> = [
    {
      header: 'Name',
      columnName: 'name',
    },
    {
      header: 'Age',
      columnName: 'age',
    },
  ];
}
```

Now add the `<ngx-tablo></ngx-tablo>` component in your template.

```html
<ngx-tablo
  [data]="data"
  [columns]="columnsConfig"
  [showPaging]="true"
  (rowClick)="rowClick($event)"
  (sortChange)="sortChange($event)"
  (pageChange)="pageChange($event)"
></ngx-tablo>
```

```ts
import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { PageEvent } from '@angular/material/paginator';
import { Sort } from '@angular/material/sort';
import { Tablo, TabloColumns } from '@notiz/ngx-tablo';
import { CustomDataSource } from '../custom-data-source';
import { PeriodicElement, customerData, Customer } from '../example.data';

@Component({
  selector: 'niz-home',
  standalone: true,
  imports: [Tablo],
  template: `
    <div>
      <mat-form-field class="w-full" appearance="fill">
        <mat-label>Search</mat-label>
        <span matPrefix class="material-icons"> search </span>
        <input type="search" matInput [(ngModel)]="search" />
      </mat-form-field>

      <mat-spinner
        class="mx-auto"
        *ngIf="dataSource.loading$ | async"
      ></mat-spinner>

      <ngx-tablo
        *ngIf="!(dataSource.loading$ | async)"
        [dataSource]="dataSource"
        [columns]="columnsConfig"
        [showPaging]="true"
        [filter]="search"
        [resetPageOnSort]="true"
        [filterPredicate]="customFilter"
        (rowClick)="rowClick($event)"
        (sortChange)="sortChange($event)"
        (pageChange)="pageChange($event)"
      ></ngx-tablo>

      <ng-template #test let-row="row">
        <span class="rounded-full bg-red-100 px-2 py-1 text-red-600">
          {{ row.symbol }}
        </span>
      </ng-template>
    </div>

    <h2 class="text-2xl font-semibold">Users</h2>
    <ngx-tablo [data]="customerData" [columns]="customerColumns"></ngx-tablo>
  `,
  styles: [],
})
export class HomeComponent implements OnInit {
  @ViewChild('test', { static: true }) test!: TemplateRef<any>;

  search = '';

  dataSource!: CustomDataSource;

  columnsConfig: TabloColumns<PeriodicElement> = [
    {
      columnName: 'position',
      header: 'No.',
      sort: true,
    },
    {
      columnName: 'name',
      header: 'Name',
      cell: (element) => element.name,
    },
    {
      columnName: 'weight',
      header: 'Weight',
      cell: (element) => element.weight,
      format: {
        currency: 'EUR',
      },
    },
    {
      columnName: 'symbol',
      header: 'Symbol',
      cell: (element) => element.symbol,
      lifecycle: {
        onInit: (column) => (column!.cellTemplate = this.test),
      },
    },
    {
      columnName: 'discoveredAt',
      header: 'Discovered',
      format: { date: 'dd.MM.yyyy' },
    },
    { columnName: 'meta.radioactive', header: 'Radioactive' },
  ];

  rowClick(row: PeriodicElement) {
    console.log(row);
  }

  sortChange(sort: Sort) {
    console.log('sortChange', sort);
  }

  pageChange(page: PageEvent) {
    console.log('pageChange', page);
  }

  customFilter(data: PeriodicElement, filter: string): boolean {
    return data.name.toLowerCase().includes(filter.toLowerCase());
  }
}
```

## Display Cell Content

### Default Content

The default content will be the data property value based on the `columnName` -> `row[columnName]`.

```ts
import { Component } from '@angular/core';
import { Tablo, TabloColumns } from '@notiz/ngx-tablo';

@Component({
  selector: 'niz-default',
  standalone: true,
  imports: [Tablo],
  template: ` <ngx-tablo [data]="data" [columns]="columns"></ngx-tablo> `,
  styles: [],
})
export class DefaultComponent {
  data = [
    {
      name: 'Tom Cook',
    },
  ];

  columns: TabloColumns<any> = [
    {
      header: 'Name',
      columnName: 'name',
    },
  ];
}
```

In this [example](./src/app/examples/default/default.component.ts) the column displays the data value `name` for each row. If the `columnName` doesn't match a property in the data the cell remains empty.

### Nested Content

The above example works only if the property is a primitive value. What if your data contains nested objects? How to access a property inside an object?

```ts
import { Component } from '@angular/core';
import { Tablo, TabloColumns } from '@notiz/ngx-tablo';

@Component({
  selector: 'niz-nested',
  standalone: true,
  imports: [Tablo],
  template: ` <ngx-tablo [data]="data" [columns]="columns"></ngx-tablo>`,
  styles: [],
})
export class NestedComponent {
  data = [
    {
      name: 'Tom Cook',
      address: {
        street: 'Onion Park',
      },
    },
  ];

  columns: TabloColumns<any> = [
    {
      header: 'Name',
      columnName: 'name',
    },
    { header: 'Street', columnName: 'address.street' },
  ];
}
```

In this [example](./src/app/examples/nested/nested.component.ts) the data contains an `address` object.
Display the street in the cell by adding the object name followed with a dot to the `columnName`.
