<?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>DEV Community: Robin</title>
    <description>The latest articles on DEV Community by Robin (@rbeier).</description>
    <link>https://dev.to/rbeier</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%2F338940%2Fcc0804d0-6ba1-48ad-a00b-781613352cef.jpg</url>
      <title>DEV Community: Robin</title>
      <link>https://dev.to/rbeier</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rbeier"/>
    <language>en</language>
    <item>
      <title>Adding multiple MatPaginators to the same DataSource</title>
      <dc:creator>Robin</dc:creator>
      <pubDate>Wed, 22 Jan 2025 22:13:05 +0000</pubDate>
      <link>https://dev.to/rbeier/adding-multiple-matpaginators-to-the-same-datasource-4fkj</link>
      <guid>https://dev.to/rbeier/adding-multiple-matpaginators-to-the-same-datasource-4fkj</guid>
      <description>&lt;p&gt;This short guide is for all Angular developers using the Angular Material Library. A customer requested the following feature:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Please show the (Mat)Paginator &lt;strong&gt;above&lt;/strong&gt; and &lt;strong&gt;below&lt;/strong&gt; all tables (which are MatTables).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The problem here: You can only connect a single MatPaginator to a DataSource that holds the data for the table. &lt;/p&gt;

&lt;p&gt;First I tried to use templates to display the paginator twice on the same page, but without success. The second paginator didn't work at all. &lt;/p&gt;

&lt;p&gt;Second, I thought about implementing the pagination logic by myself, as you do it with &lt;a href="https://material.angular.io/components/table/examples#table-http" rel="noopener noreferrer"&gt;server-side pagination&lt;/a&gt;. As I had to edit several pages, this did not seem to be the best way to do it.&lt;/p&gt;

&lt;p&gt;One step before the final implementation, I also experimented with using different signals to synchronise the properties to the second paginator instance. The final solution is a bit simpler.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;Template:&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="c"&gt;&amp;lt;!-- first paginator (standard implementation with some additional options) --&amp;gt;&lt;/span&gt; 
&lt;span class="nt"&gt;&amp;lt;mat-paginator&lt;/span&gt;
  &lt;span class="na"&gt;pageSize=&lt;/span&gt;&lt;span class="s"&gt;"50"&lt;/span&gt;
  &lt;span class="na"&gt;[pageSizeOptions]=&lt;/span&gt;&lt;span class="s"&gt;"[10, 25, 50, 100]"&lt;/span&gt;
  &lt;span class="na"&gt;[showFirstLastButtons]=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/mat-paginator&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- MatTable as usual --&amp;gt;&lt;/span&gt; 
&lt;span class="nt"&gt;&amp;lt;table&lt;/span&gt; &lt;span class="na"&gt;mat-table&lt;/span&gt; &lt;span class="na"&gt;[dataSource]=&lt;/span&gt;&lt;span class="s"&gt;"dataSource"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/table&lt;/span&gt;

&lt;span class="err"&gt;&amp;lt;!--&lt;/span&gt; &lt;span class="err"&gt;second&lt;/span&gt; &lt;span class="err"&gt;paginator&lt;/span&gt; &lt;span class="err"&gt;(synced&lt;/span&gt; &lt;span class="err"&gt;explicit&lt;/span&gt; &lt;span class="err"&gt;to&lt;/span&gt; &lt;span class="err"&gt;the&lt;/span&gt; &lt;span class="err"&gt;dataSource)&lt;/span&gt; &lt;span class="err"&gt;--&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
@if (dataSource.paginator; as paginator) {
  &lt;span class="nt"&gt;&amp;lt;mat-paginator&lt;/span&gt;
    &lt;span class="na"&gt;(page)=&lt;/span&gt;&lt;span class="s"&gt;"pageChanged($event)"&lt;/span&gt;
    &lt;span class="na"&gt;[pageSize]=&lt;/span&gt;&lt;span class="s"&gt;"paginator.pageSize"&lt;/span&gt;
    &lt;span class="na"&gt;[length]=&lt;/span&gt;&lt;span class="s"&gt;"paginator.length"&lt;/span&gt;
    &lt;span class="na"&gt;[pageIndex]=&lt;/span&gt;&lt;span class="s"&gt;"paginator.pageIndex"&lt;/span&gt;
    &lt;span class="na"&gt;[pageSizeOptions]=&lt;/span&gt;&lt;span class="s"&gt;"paginator.pageSizeOptions"&lt;/span&gt;
    &lt;span class="na"&gt;[showFirstLastButtons]=&lt;/span&gt;&lt;span class="s"&gt;"paginator.showFirstLastButtons"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/mat-paginator&amp;gt;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Component:&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="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DocumentListComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;AfterViewInit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// input of the data&lt;/span&gt;
  &lt;span class="nx"&gt;documents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Document&lt;/span&gt;&lt;span class="p"&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="c1"&gt;// dataSource of the table&lt;/span&gt;
  &lt;span class="nx"&gt;dataSource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;MatTableDataSource&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Document&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// finds the first MatPaginator instance in the view&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;ViewChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MatPaginator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;static&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="nx"&gt;paginator&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MatPaginator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// listening to changes in the documents input&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;effect&lt;/span&gt;&lt;span class="p"&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&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;documents&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// connecting the first paginator to the dataSource&lt;/span&gt;
  &lt;span class="nf"&gt;ngAfterViewInit&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&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;dataSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paginator&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="nx"&gt;paginator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// handling page changes from the bottom paginator&lt;/span&gt;
  &lt;span class="nf"&gt;pageChanged&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PageEvent&lt;/span&gt;&lt;span class="p"&gt;)&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;dataSourcePaginator&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="nx"&gt;dataSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paginator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dataSourcePaginator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;dataSourcePaginator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pageIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pageIndex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;dataSourcePaginator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_changePageSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pageSize&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Explanation
&lt;/h2&gt;

&lt;p&gt;How does it work? Implement the MatTable and the first MatPaginator as usual, connect them in the &lt;code&gt;ngAfterViewInit()&lt;/code&gt; hook. &lt;/p&gt;

&lt;p&gt;The second paginator will not be updated automatically, so we need to pass in all the required properties that we can retrieve from the first paginator, which also controls the displayed data in the table.&lt;/p&gt;

&lt;p&gt;Finally we also need to handle the navigation events of the bottom paginator. Therefore we added a &lt;code&gt;pageChanged()&lt;/code&gt; method, that updates the state of the first paginator and the dataSource along with it.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>angular</category>
      <category>frontend</category>
      <category>ui</category>
    </item>
    <item>
      <title>Use jest.fakeTimers correctly</title>
      <dc:creator>Robin</dc:creator>
      <pubDate>Mon, 04 Dec 2023 12:24:39 +0000</pubDate>
      <link>https://dev.to/rbeier/use-jestfaketimers-correctly-2nd</link>
      <guid>https://dev.to/rbeier/use-jestfaketimers-correctly-2nd</guid>
      <description>&lt;p&gt;If you are wondering why useFakeTimers() in jest does not seem to work every time, wonder no more! Here is your solution :)&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;I needed to write a unit test that checks if a form reset action is executed correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should reset form state&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ResetForm&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toStrictEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;testData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;formDefaults&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;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;The problem were the &lt;code&gt;new Date()&lt;/code&gt; calls in the default form state and in the &lt;code&gt;formDefaults()&lt;/code&gt; method. Both created a new date object at runtime that was different in milliseconds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-  "date": 2023-12-04T08:55:27.697Z,
+  "date": 2023-12-04T08:55:27.501Z,
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I remembered there were &lt;a href="https://jestjs.io/docs/jest-object#fake-timers"&gt;fakeTimers&lt;/a&gt; in Jest that I wanted to use. As the docs say, you can call &lt;code&gt;jest.useFakeTimers()&lt;/code&gt; anywhere in your test file. So I called it in &lt;code&gt;beforeAll()&lt;/code&gt; and got another error.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-  "date": 2023-01-01T00:00:00.000Z,
+  "date": 2023-12-04T09:23:24.315Z,
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, the expected date is now correct (2023-01-01), but the received date is still the current date, which means that the fakeTimers from jest were not executed in the &lt;code&gt;ResetForm&lt;/code&gt; action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;Without going into the details of the NGXS state library that was used, the problem is that the fake timers mock are not present when the state is set up with some default values.&lt;/p&gt;

&lt;p&gt;The solution is very simple, just call &lt;code&gt;jest.useFakeTimers();&lt;/code&gt; before importing any modules (very top of file) into your test and you're good to go. 🥳&lt;/p&gt;

&lt;p&gt;Of course, you can use your test-setup.ts to set this globally.&lt;/p&gt;

</description>
      <category>jest</category>
      <category>javascript</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
