Angular Data Grid

ApexGrid is a Lit web component that works with Angular through the CUSTOM_ELEMENTS_SCHEMA or standalone component imports.

Installation

npm install apex-grid

Standalone component setup (Angular 15+, recommended)

Declare CUSTOM_ELEMENTS_SCHEMA on the component itself and register the element before first render.

// app.component.ts
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
import { ApexGrid, ColumnConfiguration } from 'apex-grid'

ApexGrid.register()

interface User {
  id: number
  name: string
  email: string
  role: string
}

@Component({
  selector: 'app-root',
  standalone: true,
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  template: `
    <apex-grid
      [data]="users"
      [columns]="columns"
    ></apex-grid>
  `
})
export class AppComponent {
  users: User[] = [
    { id: 1, name: 'Alice', email: 'alice@example.com', role: 'Admin' },
    { id: 2, name: 'Bob',   email: 'bob@example.com',   role: 'Editor' },
  ]

  columns: ColumnConfiguration<User>[] = [
    { key: 'id',    headerText: 'ID',    type: 'number' },
    { key: 'name',  headerText: 'Name',  sort: true, filter: true },
    { key: 'email', headerText: 'Email', sort: true },
    { key: 'role',  headerText: 'Role',  type: 'select',
      options: ['Admin', 'Editor', 'Viewer'] },
  ]
}

NgModule setup (Angular 14 and earlier)

Add CUSTOM_ELEMENTS_SCHEMA to the schemas array of the root module, then register the element in the component file.

// app.module.ts
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { AppComponent } from './app.component'

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule],
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  bootstrap: [AppComponent]
})
export class AppModule {}
// app.component.ts
import { Component } from '@angular/core'
import { ApexGrid, ColumnConfiguration } from 'apex-grid'

ApexGrid.register()

interface User {
  id: number
  name: string
  email: string
}

@Component({
  selector: 'app-root',
  template: `
    <apex-grid
      [data]="users"
      [columns]="columns"
    ></apex-grid>
  `
})
export class AppComponent {
  users: User[] = [
    { id: 1, name: 'Alice', email: 'alice@example.com' },
    { id: 2, name: 'Bob',   email: 'bob@example.com' },
  ]

  columns: ColumnConfiguration<User>[] = [
    { key: 'id',    headerText: 'ID',    type: 'number' },
    { key: 'name',  headerText: 'Name',  sort: true, filter: true },
    { key: 'email', headerText: 'Email', sort: true },
  ]
}

Binding complex data

Angular property binding uses [property] for objects and arrays. The [data] and [columns] bindings pass values as JavaScript properties on the element, not as HTML attributes. This is the correct approach for web components that accept arrays and objects.

Accessing the grid imperatively

Use ViewChild to get a reference to the native element and call grid methods directly.

import { Component, ViewChild, ElementRef, AfterViewInit, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
import { ApexGrid, ColumnConfiguration } from 'apex-grid'

ApexGrid.register()

interface User {
  id: number
  name: string
  email: string
}

@Component({
  selector: 'app-root',
  standalone: true,
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  template: `
    <apex-grid
      #grid
      [data]="users"
      [columns]="columns"
    ></apex-grid>
  `
})
export class AppComponent implements AfterViewInit {
  @ViewChild('grid') gridEl!: ElementRef<ApexGrid<User>>

  users: User[] = [
    { id: 1, name: 'Alice', email: 'alice@example.com' },
    { id: 2, name: 'Bob',   email: 'bob@example.com' },
  ]

  columns: ColumnConfiguration<User>[] = [
    { key: 'id',    headerText: 'ID',    type: 'number' },
    { key: 'name',  headerText: 'Name',  sort: true },
    { key: 'email', headerText: 'Email' },
  ]

  ngAfterViewInit() {
    this.gridEl.nativeElement.sort({ key: 'name', direction: 'ascending' })
  }
}

Listening to events

Angular's (eventName) template binding does fire for custom DOM events dispatched by web components: Angular attaches a real DOM listener for any binding that is not a directive output, so a CustomEvent('rowSelected') reaches (rowSelected). Bind directly in the template and read the payload from $event.detail.

@Component({
  standalone: true,
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  template: `
    <apex-grid
      [data]="users"
      [columns]="columns"
      (rowSelected)="onRowSelected($event)"
      (sorted)="onSorted($event)"
    ></apex-grid>
  `,
})
export class AppComponent {
  onRowSelected(e: Event) {
    console.log('selected:', (e as CustomEvent).detail)
  }
  onSorted(e: Event) {
    console.log('sorted:', (e as CustomEvent).detail)
  }
}

The binding name is case-sensitive and must match the event exactly ((rowSelected), not (rowselected)). As an alternative, for example when the event name is dynamic, attach a listener imperatively via ViewChild in ngAfterViewInit:

ngAfterViewInit() {
  this.gridEl.nativeElement.addEventListener('rowSelected', (e: Event) => {
    console.log('selected:', (e as CustomEvent).detail)
  })
}

Commonly used events dispatched by ApexGrid (the -ing / -Changing variants fire before the operation and are cancellable; see the React guide for the complete list):

EventDetail
rowSelectedThe selected row data
rowSelectingThe row data about to be selected
cellValueChangedUpdated cell value and row data
filteredActive filter expression tree
sortedActive sort expression
pageChangedNew page index