I am trying to pass multiple ng-template
to my reusable component
(my-table component), content projection. Now I need to get the reference value of ea
I've had to build lots of table components that used Angular Material's MatTable
, and at some point I decided to save myself some time in the long run by building a base table that is dynamic and reusable. I've added a bit more context / thought process around how to get up and running with a bare minimum dynamic reusable table, before talking about how to add a specific feature to it.
Advice for Building a Dynamic and Reusable Table
The first thing I did (after adding Angular Material to the project) was determine how I want consumers to use my table. I decided that any table level behavior (enable/disable pagination) would be controlled by @Input
's in the table component. However as I developed it further, I realized most of the new functionality I needed should really be controlled per-column. The rest of this answer is focused on the per-column configuration.
TableColumnConfig
Interface - adding a new feature
I started off by defining an interface for a configuration object (just like OP did with TableColumns
except mine is called TableColumnConfig
. The bare minimum needed for dynamic and reusable functionality are two strings that you use to access the data in each row and to display the column name (I use key
and displayName
).
If we want to add the ability for consumers of the component to pass in a custom cell template, I'd first add a property to the TableColumnConfig
interface like so:
import { TemplateRef } from '@angular/core';
export interface TableColumnConfig {
displayName: string;
key: string;
customCellTemplate?: TemplateRef; // custom cell template!
}
my-table-component.ts
I believe I started with the Angular Material schematic for generating a table component, but I didn't like the amount of boilerplate for something bare minimum like this example (it's easy enough to add pagination and sorting later).
You don't need to do anything special in the table-component.ts for custom the custom cell template functionality (just note we are expecting TableColumnConfig[]
from the consuming component), but showing the code below for completeness. Most of the times when I needed to add a per-column feature, I never even had to mess with this file.
import { Component, OnInit, Input } from '@angular/core';
import { MatTableDataSource } from '@angular/material';
import { TableColumnConfig } from './table-column-config';
@Component({
selector: 'app-my-table',
templateUrl: './my-table.component.html',
styleUrls: ['./my-table.component.css']
})
export class MyTableComponent implements OnInit {
@Input() data: any[];
@Input() columnConfigs: TableColumnConfig[];
dataSource: MatTableDataSource;
// need a string array for *matHeaderRowDef and *matRowDef
displayedColumns: string[];
ngOnInit() {
this.displayedColumns = this.columnConfigs.map(config => config.key);
this.dataSource = new MatTableDataSource(this.data);
}
}
my-table-component.html
Similar approach to what the OP showed in his answer. Since I added customCellTemplate
as a property to TableColumnConfig
, accessing it looks a bit cleaner. Also just a note that for this demo I decided to only expose column data to customCellTemplates, but you could easily return the entire row if necessary by changing $implicit: row[col.key]
to $implicit: row
{{ col.displayName }}
{{ row[col.key] }}
Example: Consuming Component
Sample use case where we want styled text in a column
app-component.html
For this bare minimum example, the table only has two inputs. I like to define the
s for customCellTemplates at the bottom of the file instead of inside of the table tag itself for better readability imo.
{{colorData}}
app-component.ts
export class AppComponent implements OnInit {
@ViewChild("customCell", { static: true })
customCell: TemplateRef;
columnConfigs: TableColumnConfig[];
tableData = [
{ id: 1, name: "Chris", color: "#FF9900" },
{ id: 2, name: "Akash", color: "blue" }
];
// we can't reference our {static:true} TemplateRef until ngOnInit
ngOnInit() {
this.columnConfigs = [
{ key: "id", displayName: "ID" },
{ key: "name", displayName: "Name" },
{
key: "color",
displayName: "Favorite Color",
customCellTemplate: this.customCell
}
];
}
}
Check out my StackBlitz demo for a few more code comments.