Creating a Customizable Grid Component in Angular with ngTemplateOutle
In this tutorial, we will build an advanced grid component in Angular that supports customization, dynamic templates, data binding, and event handling. The grid component will allow users to define custom cell templates, specify columns and data, and respond to row click events. Let’s dive into the details of each part of the grid component.
GridTemplateContext Interface
First, let’s define the GridTemplateContext
interface. This interface will serve as the context for the cell templates and provide access to the data and row information.
interface GridTemplateContext {
$implicit: any;
row: any;
[key: string]: any;
}
The GridTemplateContext
interface has two properties:
$implicit
: Represents the data for the current cell.row
: Represents the entire row data.
GridCellTemplateDirective
To enable custom cell templates, we need to create a directive called GridCellTemplateDirective
. This directive will be used to define custom templates for specific columns in the grid.
import { Directive, Input, TemplateRef } from '@angular/core';
@Directive({
selector: '[appGridCellTemplate]'
})
export class GridCellTemplateDirective {
@Input('appGridCellTemplate') columnName!: string;
constructor(public templateRef: TemplateRef<GridTemplateContext>) {}
}
The GridCellTemplateDirective
is decorated with @Directive
and has an input property called columnName
, which represents the name of the column associated with the template. The directive also has a TemplateRef
injected, which will hold the reference to the custom template.
GridComponent
Now, let’s implement the main GridComponent
that will render the grid based on the provided columns, data, and templates.
import { Component, Input, Output, EventEmitter, ContentChildren, QueryList, TemplateRef, ContentChild } from '@angular/core';
@Component({
selector: 'app-grid',
template: `
<table>
<thead>
<tr>
<th *ngFor="let column of columns">{{ column }}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of data" (click)="onRowClick(item)">
<td *ngFor="let column of columns">
<ng-container
*ngTemplateOutlet="
getCellTemplate(column);
context: { $implicit: item[column], row: item }
"
></ng-container>
</td>
</tr>
</tbody>
</table>
`,
styleUrls: ['./grid.component.css']
})
export class GridComponent {
@Input() columns!: string[];
@Input() data!: any[];
@Output() rowClick: EventEmitter<any> = new EventEmitter();
@ContentChildren(GridCellTemplateDirective) cellTemplates!: QueryList<GridCellTemplateDirective>;
@ContentChild('defaultTemplate', { static: true }) defaultTemplate!: TemplateRef<GridTemplateContext>;
getCellTemplate(column: string): TemplateRef<GridTemplateContext> {
const template = this.cellTemplates.find(tmpl => tmpl.columnName === column);
return template ? template.templateRef : this.defaultTemplate;
}
onRowClick(row: any) {
this.rowClick.emit(row);
}
}
The GridComponent
is decorated with @Component
and has several properties:
columns
: Represents the column names of the grid.data
: Represents the data to be rendered in the grid.rowClick
: Represents an output event emitter that emits the clicked row data.cellTemplates
: Represents the list ofGridCellTemplateDirective
instances provided by the user.defaultTemplate
: Represents the default template provided by the user.
The GridComponent
template consists of a <table>
element that renders the grid structure. The <thead>
section renders the column headers, and the <tbody>
section iterates over the data and renders the rows. Inside each row, the <td>
elements iterate over the columns and use ngTemplateOutlet
to dynamically render the appropriate cell template.
The getCellTemplate
method is responsible for determining the correct cell template based on the column name. It searches for a matching template in the cellTemplates
list and returns it. If no matching template is found, it falls back to the default template.
The onRowClick
method handles the row click event and emits the clicked row data using the rowClick
event emitter.
Usage
To use the GridComponent
in your application, follow these steps:
- Import the necessary dependencies in your module:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { GridComponent, GridCellTemplateDirective } from './grid/grid.component';
@NgModule({
declarations: [AppComponent, GridComponent, GridCellTemplateDirective],
imports: [BrowserModule],
bootstrap: [AppComponent]
})
export class AppModule {}
- Define the grid component in your template:
<app-grid [columns]="gridColumns" [data]="gridData" (rowClick)="onRowClicked($event)">
<ng-template appGridCellTemplate="name" let-item let-row>
<span>Name: {{ item }}</span>
<br />
<span>Row Data: {{ row | json }}</span>
</ng-template>
<ng-template appGridCellTemplate="age" let-item>
<span>Age: {{ item }}</span>
</ng-template>
<ng-template #defaultTemplate let-item>
<span>{{ item }}</span>
</ng-template>
</app-grid>
In this example, we provide custom cell templates for the “name” and “age” columns using the appGridCellTemplate
directive. We also define a default template using the #defaultTemplate
reference. The grid component receives the columns and data as inputs, and the (rowClick)
event allows you to handle row click events.
That’s it! You have now successfully built an advanced grid component in Angular that supports customization and dynamic templates.
I hope this blog post helps you understand the implementation of the Angular advanced grid component and how each part contributes to its functionality. Feel free to customize and extend the component based on your specific requirements. Happy coding!