<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Zonaib Bokhari</title>
    <description>The latest articles on Forem by Zonaib Bokhari (@zonaibbokhari).</description>
    <link>https://forem.com/zonaibbokhari</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F294281%2F1419f07a-19f3-49bb-8a00-5915359d5e3b.jpg</url>
      <title>Forem: Zonaib Bokhari</title>
      <link>https://forem.com/zonaibbokhari</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/zonaibbokhari"/>
    <language>en</language>
    <item>
      <title>I got tired of copy-pasting the same table code, so I built a library</title>
      <dc:creator>Zonaib Bokhari</dc:creator>
      <pubDate>Mon, 20 Apr 2026 08:56:21 +0000</pubDate>
      <link>https://forem.com/zonaibbokhari/i-got-tired-of-copy-pasting-the-same-table-code-so-i-built-a-library-2c3l</link>
      <guid>https://forem.com/zonaibbokhari/i-got-tired-of-copy-pasting-the-same-table-code-so-i-built-a-library-2c3l</guid>
      <description>&lt;p&gt;Every Angular project I've worked on has a table. Usually more than one. And every single time I end up writing the same setup — wire up &lt;code&gt;MatSort&lt;/code&gt;, wire up &lt;code&gt;MatPaginator&lt;/code&gt;, build a &lt;code&gt;SelectionModel&lt;/code&gt; for checkboxes, manage filter state somewhere, figure out export again from scratch.&lt;/p&gt;

&lt;p&gt;It's not hard, it's just tedious. And when you do it enough times across enough projects, slightly differently each time, you start to wonder why you haven't just extracted it.&lt;/p&gt;

&lt;p&gt;So I did. &lt;a href="https://www.npmjs.com/package/ngx-mat-simple-table" rel="noopener noreferrer"&gt;&lt;code&gt;ngx-mat-simple-table&lt;/code&gt;&lt;/a&gt; — an Angular Material table component that takes a column config and data, and handles the rest.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhet5z70pv8ex7jvcfurg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhet5z70pv8ex7jvcfurg.png" alt="ngx-mat-simple-table — sortable, filterable table with multi-row selection and column filters" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The core idea
&lt;/h2&gt;

&lt;p&gt;I wanted to go from this (the usual boilerplate situation) to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;simple-table&lt;/span&gt;
  &lt;span class="na"&gt;[tableColumns]=&lt;/span&gt;&lt;span class="s"&gt;"columns"&lt;/span&gt;
  &lt;span class="na"&gt;[dataSource]=&lt;/span&gt;&lt;span class="s"&gt;"rows"&lt;/span&gt;
  &lt;span class="na"&gt;[tableConfig]=&lt;/span&gt;&lt;span class="s"&gt;"config"&lt;/span&gt;
  &lt;span class="na"&gt;(sortChange)=&lt;/span&gt;&lt;span class="s"&gt;"onSort($event)"&lt;/span&gt;
  &lt;span class="na"&gt;(filterChange)=&lt;/span&gt;&lt;span class="s"&gt;"onFilter($event)"&lt;/span&gt;
  &lt;span class="na"&gt;(selectionChange)=&lt;/span&gt;&lt;span class="s"&gt;"onSelect($event)"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;st-export&lt;/span&gt; &lt;span class="na"&gt;filename=&lt;/span&gt;&lt;span class="s"&gt;"tasks"&lt;/span&gt; &lt;span class="na"&gt;format=&lt;/span&gt;&lt;span class="s"&gt;"xlsx"&lt;/span&gt; &lt;span class="na"&gt;[allDataProvider]=&lt;/span&gt;&lt;span class="s"&gt;"getAllForExport"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/simple-table&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Column config is a plain array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ColumnDef&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;select&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="na"&gt;hasColumnFilters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;assignee&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Assignee&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;hasColumnFilters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;filterType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FilterType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DropDown&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="na"&gt;hasColumnFilters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;filterType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FilterType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DropDown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;displayValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/-/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dueDate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Due Date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fully paginated, sortable, filterable, exportable table. That's the whole host component.&lt;/p&gt;




&lt;h2&gt;
  
  
  Signals from the start
&lt;/h2&gt;

&lt;p&gt;I built this after Angular 17 shipped, so I went all-in on the signals API. No &lt;code&gt;@Input()&lt;/code&gt;, no &lt;code&gt;EventEmitter&lt;/code&gt;, no &lt;code&gt;ChangeDetectorRef&lt;/code&gt;. Everything is &lt;code&gt;input()&lt;/code&gt;, &lt;code&gt;output()&lt;/code&gt;, &lt;code&gt;computed()&lt;/code&gt;, &lt;code&gt;effect()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I wasn't sure how it would feel at first but honestly it's made the component much easier to reason about. I've never once had to think about change detection. Would not go back.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Windows &lt;code&gt;file:&lt;/code&gt; reference trap
&lt;/h2&gt;

&lt;p&gt;This one annoyed me more than it should have.&lt;/p&gt;

&lt;p&gt;When developing a library locally you need the demo app to consume the built output. I used &lt;code&gt;"ngx-mat-simple-table": "file:./dist/ngx-mat-simple-table"&lt;/code&gt; in the root &lt;code&gt;package.json&lt;/code&gt;. On macOS this works fine. On Windows, &lt;code&gt;npm install&lt;/code&gt; with a &lt;code&gt;file:&lt;/code&gt; reference copies the files at install time — so running &lt;code&gt;build:lib:watch&lt;/code&gt; updates &lt;code&gt;dist/&lt;/code&gt; but &lt;code&gt;node_modules/&lt;/code&gt; stays completely stale. I kept seeing old code after rebuilds and couldn't figure out why for longer than I'd like to admit.&lt;/p&gt;

&lt;p&gt;The fix is &lt;code&gt;tsconfig.json&lt;/code&gt; paths instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"paths"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ngx-mat-simple-table"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"./dist/ngx-mat-simple-table"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Angular's build system watches files resolved through &lt;code&gt;paths&lt;/code&gt;, so incremental rebuilds are picked up immediately. Should have just done this from the start.&lt;/p&gt;




&lt;h2&gt;
  
  
  CDK drag-reorder was a puzzle
&lt;/h2&gt;

&lt;p&gt;Column drag-reorder uses Angular CDK. My first attempt put &lt;code&gt;cdkDropList&lt;/code&gt; and &lt;code&gt;cdkDrag&lt;/code&gt; on the same &lt;code&gt;&amp;lt;th&amp;gt;&lt;/code&gt; element. CDK silently reported &lt;code&gt;previousContainer === container&lt;/code&gt; on every drop, so the column order never actually changed. Body cells stayed out of sync with headers. No error, just nothing happening.&lt;/p&gt;

&lt;p&gt;The fix: &lt;code&gt;&amp;lt;th&amp;gt;&lt;/code&gt; is the &lt;code&gt;CdkDropList&lt;/code&gt;, a wrapper &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; inside it carries &lt;code&gt;CdkDrag&lt;/code&gt;. Separate elements. Also — and this surprised me — Angular's &lt;code&gt;@for&lt;/code&gt; block doesn't work here. CDK needs to traverse the view tree to find connected drop lists, and &lt;code&gt;@for&lt;/code&gt; uses a different internal structure than &lt;code&gt;*ngFor&lt;/code&gt;. Switching to &lt;code&gt;*ngFor&lt;/code&gt; on the column blocks fixed it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Don't install SheetJS without checking if it actually does what you need
&lt;/h2&gt;

&lt;p&gt;I needed styled Excel headers. I installed SheetJS (xlsx), the most popular option. Spent a while getting it set up, wrote the header styling code, tested it — headers were completely plain. No error, styling just silently had no effect.&lt;/p&gt;

&lt;p&gt;Turns out cell styles in SheetJS community edition are a Pro-only feature. It's in the docs if you look for it, but it's not exactly front and centre.&lt;/p&gt;

&lt;p&gt;Switched to &lt;a href="https://github.com/exceljs/exceljs" rel="noopener noreferrer"&gt;ExcelJS&lt;/a&gt; (MIT, actually free) and it worked immediately. The API is clean and it supports full cell styling. To match the exported header to the rendered grid I just read styles from the DOM at export time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hostEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;th.mat-mdc-header-cell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getComputedStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_cssColorToArgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// → ARGB hex for ExcelJS&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fontWeight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whatever theme or custom CSS the host applies, the Excel header automatically matches it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Export should export everything, not just what's on screen
&lt;/h2&gt;

&lt;p&gt;The first version of export grabbed whatever rows were rendered — so if you were on page 3 of 10, you'd export 10 rows. Obviously wrong in hindsight.&lt;/p&gt;

&lt;p&gt;Client-side mode was easy to fix: export &lt;code&gt;MatTableDataSource.filteredData&lt;/code&gt;, which has all filtered rows regardless of page.&lt;/p&gt;

&lt;p&gt;Server-side mode needed a different approach. The &lt;code&gt;&amp;lt;st-export&amp;gt;&lt;/code&gt; directive accepts an &lt;code&gt;allDataProvider&lt;/code&gt; callback — the host provides a function that fetches everything from the API without pagination params:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;getAllForExport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;firstValueFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TasksResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/tasks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;activeFilterParams&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;st-export&lt;/span&gt; &lt;span class="na"&gt;filename=&lt;/span&gt;&lt;span class="s"&gt;"tasks"&lt;/span&gt; &lt;span class="na"&gt;[allDataProvider]=&lt;/span&gt;&lt;span class="s"&gt;"getAllForExport"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Active filters are forwarded so the export reflects exactly what the user sees — just without the page limit.&lt;/p&gt;




&lt;h2&gt;
  
  
  Vercel 404 after deploy
&lt;/h2&gt;

&lt;p&gt;After one release the demo started returning 404 on every route. Angular 17+'s esbuild builder outputs to &lt;code&gt;dist/&amp;lt;project&amp;gt;/browser/&lt;/code&gt; — Vercel was pointed at &lt;code&gt;dist/&amp;lt;project&amp;gt;/&lt;/code&gt; and finding no &lt;code&gt;index.html&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Fixed with a &lt;code&gt;vercel.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"buildCommand"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm run build:lib &amp;amp;&amp;amp; npm install &amp;amp;&amp;amp; npm run build"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"outputDirectory"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dist/Demo-table/browser"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rewrites"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/(.*)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"destination"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/index.html"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;rewrites&lt;/code&gt; rule matters too. Without it, refreshing any route other than &lt;code&gt;/&lt;/code&gt; returns 404 because Vercel looks for a file at that path instead of letting Angular's router handle it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where it is now
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;npm:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/ngx-mat-simple-table" rel="noopener noreferrer"&gt;&lt;code&gt;ngx-mat-simple-table&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Demo:&lt;/strong&gt; &lt;a href="https://ng-simple-table.vercel.app" rel="noopener noreferrer"&gt;ng-simple-table.vercel.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/xonaib/ng-simple-table" rel="noopener noreferrer"&gt;github.com/xonaib/ng-simple-table&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It has pagination, sorting, multi-select, dropdown and date range filters, column chooser, drag-reorder, column resize, sticky columns, Excel/CSV export with full header styling, and user settings persistence. Client-side and server-side data modes.&lt;/p&gt;

&lt;p&gt;If you're building data-heavy Angular apps, hopefully it saves you some of the boilerplate.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
