<?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: Maxime</title>
    <description>The latest articles on DEV Community by Maxime (@maxime1992).</description>
    <link>https://dev.to/maxime1992</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%2F174243%2Fa24a9074-b5be-417b-9407-de7a9f4bec06.jpg</url>
      <title>DEV Community: Maxime</title>
      <link>https://dev.to/maxime1992</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/maxime1992"/>
    <language>en</language>
    <item>
      <title>Fun with Angular: Create grouped queues of tasks for APP_INITIALIZER</title>
      <dc:creator>Maxime</dc:creator>
      <pubDate>Wed, 28 Jun 2023 08:02:19 +0000</pubDate>
      <link>https://dev.to/maxime1992/fun-with-angular-create-grouped-queues-of-tasks-for-appinitializer-59il</link>
      <guid>https://dev.to/maxime1992/fun-with-angular-create-grouped-queues-of-tasks-for-appinitializer-59il</guid>
      <description>&lt;h1&gt;
  
  
  Intro
&lt;/h1&gt;

&lt;p&gt;Have you ever heard of &lt;a href="https://angular.io/api/core/APP_INITIALIZER" rel="noopener noreferrer"&gt;&lt;code&gt;APP_INITIALIZER&lt;/code&gt;&lt;/a&gt;? It's a handy DI token that lets you provide one or more initialization functions that'll be run before your entire app starts. They can come in very handy. But did you know that all the &lt;code&gt;APP_INITIALIZER&lt;/code&gt; registered providers &lt;strong&gt;will be created at the same time&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;Let's make that more flexible.&lt;/p&gt;

&lt;p&gt;Because we desperately need it? Hell no.&lt;br&gt;&lt;br&gt;
Because I realized that limitation today and wanted to have some fun? Definitely.&lt;/p&gt;

&lt;p&gt;It's a really good example to practice with dependency injection and observables. What else do we need for fun?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fangular-create-grouped-app-initializer-queue%2Fassets%2Fangular-rxjs-what-else.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fangular-create-grouped-app-initializer-queue%2Fassets%2Fangular-rxjs-what-else.jpg" title="What else?" alt="'What else'"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  APP_INITIALIZER and its limitations
&lt;/h1&gt;

&lt;p&gt;If you want to make sure to execute an initialization function &lt;strong&gt;that is blocking&lt;/strong&gt; before your app starts, &lt;code&gt;APP_INITIALIZER&lt;/code&gt; is exactly what you need. Example from the &lt;a href="https://angular.io/api/core/APP_INITIALIZER#usage-notes" rel="noopener noreferrer"&gt;documentation&lt;/a&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;function&lt;/span&gt; &lt;span class="nf"&gt;initializeApp&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="kr"&gt;any&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&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;// Do some asynchronous stuff&lt;/span&gt;
    &lt;span class="nf"&gt;resolve&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;span class="nd"&gt;NgModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;BrowserModule&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;declarations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;AppComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;AppComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;providers&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;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APP_INITIALIZER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;useFactory&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="nx"&gt;initializeApp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;multi&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="p"&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;AppModule&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Pretty cool, right?&lt;/p&gt;

&lt;p&gt;But what happens you now have multiple &lt;code&gt;APP_INITIALIZER&lt;/code&gt; defined? It's got the &lt;code&gt;multi&lt;/code&gt; argument set to &lt;code&gt;true&lt;/code&gt; after all so we can provide as many as we want to:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="nx"&gt;providers&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;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APP_INITIALIZER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;useFactory&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="nx"&gt;initializeApp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;multi&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="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APP_INITIALIZER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;useFactory&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="nx"&gt;doSomethingElse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;multi&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="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APP_INITIALIZER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;useFactory&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="nx"&gt;wowAnotherThing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;multi&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="p"&gt;];&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Well they all run in parallel. They'll be called in the order they were declared, but won't wait for each others to finish before executing the next one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fangular-create-grouped-app-initializer-queue%2Fassets%2Fstarted-at-the-same-time.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fangular-create-grouped-app-initializer-queue%2Fassets%2Fstarted-at-the-same-time.png" title="Started at the same time" alt="'Started at the same time'"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What if we needed &lt;em&gt;(or rather wanted)&lt;/em&gt; that? Even better, what if we could define different queues were within a queue, they'd execute one after another but all the queues would be started in parallel:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fangular-create-grouped-app-initializer-queue%2Fassets%2Fqueues.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fangular-create-grouped-app-initializer-queue%2Fassets%2Fqueues.png" title="Queues" alt="'Queues'"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Talk is cheap. Show me the code
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;APP_INITIALIZER&lt;/code&gt; is an injection token. We want a similar capability. In order to achieve this, we'll do 2 things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create an injection token that will be define as &lt;code&gt;multi: true&lt;/code&gt; and that we can pass multiple times to build the different tasks of our queues&lt;/li&gt;
&lt;li&gt;Create an injection token that will be provided only once as an &lt;code&gt;APP_INITIALIZER&lt;/code&gt; and will manage all of our queues. Once all the queues are resolved, only then we'll consider that our initilisation fase for this token is complete&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's start with the first point.&lt;/p&gt;
&lt;h2&gt;
  
  
  Defining the token for all of our queues
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AppInitializerQueueToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;symbol&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;number&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;DEFAULT_QUEUE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DEFAULT_QUEUE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;AppInitializerQueueParam&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;AppInitializerQueueToken&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;task$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&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;const&lt;/span&gt; &lt;span class="nx"&gt;APP_INITIALIZER_QUEUE_TOKEN&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;InjectionToken&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AppInitializerQueueParam&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="s2"&gt;`APP_INITIALIZER_QUEUE_TOKEN`&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;Our &lt;code&gt;APP_INITIALIZER_QUEUE_TOKEN&lt;/code&gt; is to be provided &lt;strong&gt;multiple times&lt;/strong&gt;. Hence the array in the type.&lt;/p&gt;

&lt;p&gt;If we look at the &lt;code&gt;AppInitializerQueueParam&lt;/code&gt;, we see that will be able to define a &lt;code&gt;queue&lt;/code&gt; name &lt;em&gt;(could be a string but it could also be a number or a symbol to make sure a queue is really unique)&lt;/em&gt;. It'll also be able to define a &lt;code&gt;task$&lt;/code&gt; as an observable.&lt;/p&gt;

&lt;p&gt;Now let's look at the second point which is the bulk of the work where we'll manage our different queues.&lt;/p&gt;
&lt;h2&gt;
  
  
  Managing all the queues
&lt;/h2&gt;

&lt;p&gt;We'll start by defining our provider's global shape:&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;APP_INITIALIZER_QUEUE_PROVIDER&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APP_INITIALIZER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;multi&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;useFactory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;appInitializerQueueParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Nilable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AppInitializerQueueParam&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="o"&gt;=&amp;gt;&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;appInitializerQueueParams&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// @todo&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;someObservableHere$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;deps&lt;/span&gt;&lt;span class="p"&gt;:&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;Optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;APP_INITIALIZER_QUEUE_TOKEN&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;Already quite a lot going on!&lt;/p&gt;

&lt;p&gt;We &lt;code&gt;provide&lt;/code&gt; as mentioned earlier, using the &lt;code&gt;APP_INITIALIZER&lt;/code&gt; so that this initialisation function is blocking for the app.&lt;/p&gt;

&lt;p&gt;We set &lt;code&gt;multi: true&lt;/code&gt; because we're using &lt;code&gt;APP_INITIALIZER&lt;/code&gt; here and that's how it works, it can be provided multiple times.&lt;/p&gt;

&lt;p&gt;We then use a factory function to build our core logic and make sure to type it accordingly with our &lt;code&gt;appInitializerQueueParams&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That params is marked as &lt;code&gt;Nilable&lt;/code&gt; &lt;em&gt;(using &lt;a href="https://www.npmjs.com/package/tsdef" rel="noopener noreferrer"&gt;&lt;code&gt;tsdef&lt;/code&gt;&lt;/a&gt;)&lt;/em&gt;, because if we do not provide any &lt;code&gt;APP_INITIALIZER_QUEUE_TOKEN&lt;/code&gt;, we'll then get null here. Which leads us to this code:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="nx"&gt;deps&lt;/span&gt;&lt;span class="p"&gt;:&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;Optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;APP_INITIALIZER_QUEUE_TOKEN&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;When we provide the dependencies for our factory function, we say explicitely that providing &lt;code&gt;APP_INITIALIZER_QUEUE_TOKEN&lt;/code&gt; is &lt;strong&gt;optional&lt;/strong&gt;. The reason for this is because one could start by definining this queue provider, but not yet have built the queue tokens and we don't want the app to crash for this. Note the use of a tuple here as we cannot use the decorator &lt;code&gt;@Optional&lt;/code&gt; as we would normally do when injecting an optional dependency from a class constructor.&lt;/p&gt;

&lt;p&gt;Lastly, we do &lt;code&gt;return () =&amp;gt; someObservableHere$&lt;/code&gt; because the &lt;code&gt;APP_INITIALIZER&lt;/code&gt; expects a function that returns an observable &lt;em&gt;(or it can a promise etc but we'll just focus on observables here)&lt;/em&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Create one provider per queue
&lt;/h3&gt;

&lt;p&gt;Remember how we'll have multiple queues, each of them having multiple tasks, and all that provided through DI? Well you have to keep in mind that our code is read line after line:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;How tasks are actually provided&lt;/th&gt;
&lt;th&gt;How we want our queues to be&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/.%2Fassets%2Ftasks-provided-one-by-one.png" title="How tasks are provided" alt="'How tasks are provided')"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fangular-create-grouped-app-initializer-queue%2Fassets%2Fqueues.png" title="Queues" alt="'Queues'"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is just &lt;strong&gt;one&lt;/strong&gt; example of the order in which they could be declared. Note that for a given color &lt;em&gt;(queue)&lt;/em&gt;, all the tasks define the queue order by the order in which they are declared. But it doesn't mean that we have to provide them exactly one after another for the same queue. We could definitely declare tasks from other queues in between.&lt;/p&gt;

&lt;p&gt;In order to achieve that transformation from a single array of tasks to a grouped pool of queues with ordered tasks, the first thing we need is to group our providers by queue:&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;tasksByQueue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;appInitializerQueueParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;x&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queueName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;DEFAULT_QUEUE&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;queueName&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;queueName&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="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;queueName&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;task$&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;acc&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="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AppInitializerQueueToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Because we want to group by queue, we'll have a data structure that is a &lt;code&gt;Record&lt;/code&gt; &lt;em&gt;(a dictionary, an object)&lt;/em&gt; that'll take as key a queue identifier &lt;em&gt;(string, symbol or number)&lt;/em&gt;, and as a value an array of observables, representing all the tasks for the current queue.&lt;/p&gt;
&lt;h3&gt;
  
  
  Instantiate the providers per queue sequentially
&lt;/h3&gt;

&lt;p&gt;Thanks to RxJS, instantiating all the providers of a queue sequentially is a breathe&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fangular-create-grouped-app-initializer-queue%2Fassets%2Fthe-feeling.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fangular-create-grouped-app-initializer-queue%2Fassets%2Fthe-feeling.jpg" title="The feeling" alt="'The feeling'"&gt;&lt;/a&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;tasksByQueue$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tasksByQueue&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;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;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="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;tasks&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;Note that we don't use &lt;code&gt;Object.values&lt;/code&gt; on purpose here as it'd simply cancel all of our previous work to group tasks by queue and put all the tasks into a single array. Instead we use &lt;code&gt;Object.entries&lt;/code&gt; to make sure we loop once per queue, and for 1 queue, we &lt;code&gt;concat&lt;/code&gt; all the related tasks.&lt;/p&gt;
&lt;h3&gt;
  
  
  Start all the queues simultaneously
&lt;/h3&gt;

&lt;p&gt;We now have &lt;code&gt;tasksByQueue$&lt;/code&gt; which is &lt;code&gt;Array&amp;lt;Observable&amp;lt;any&amp;gt;&amp;gt;&lt;/code&gt; and we want to get instead &lt;code&gt;Observable&amp;lt;any&amp;gt;&lt;/code&gt; and make sure that we subscribe to all in paralell:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;tasksByQueue$&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;That's it!&lt;/p&gt;
&lt;h3&gt;
  
  
  Complete code
&lt;/h3&gt;

&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/stackblitz-starters-x4yc7t?embed=1&amp;amp;file=src%2Fapp-initialized-queue.ts&amp;amp;view=editor" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;

&lt;p&gt;Here's an example of how we can then provide our tasks for different queues. Take a look at the console output:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/stackblitz-starters-x4yc7t?embed=1&amp;amp;file=src%2Fmain.ts" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Angular DI system is very powerful and so is RxJS. We've been able to implement a queue system to initialize our app with different tasks ran sequentially, but grouped by queues ran themselves in parallel. The core logic takes &lt;strong&gt;~30 lines of code&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Keep in mind that any initialization attached to &lt;code&gt;APP_INITIALIZER&lt;/code&gt; must be carefully through as this will delay the start of your app. While I'm sure there are a few cases where this could come in handy, I mostly thought of that as a little challenge with Angular and RxJS.&lt;/p&gt;




&lt;p&gt;I hope you enjoyed this article, if you did let me know with a reaction and eventually drop a comment. It's always nice to hear back from people who took the time to read a post 😄!&lt;/p&gt;

&lt;p&gt;If you're interested in more articles about Angular, RxJS, open source, self hosting, data privacy, feel free to hit the &lt;strong&gt;follow button&lt;/strong&gt; for more. Thanks for reading!&lt;/p&gt;

&lt;h1&gt;
  
  
  Found a typo?
&lt;/h1&gt;

&lt;p&gt;If you've found a typo, a sentence that could be improved or anything else that should be updated on this blog post, you can access it through a git repository and make a pull request. Instead of posting a comment, please go directly to &lt;a href="https://github.com/maxime1992/my-dev.to"&gt;https://github.com/maxime1992/my-dev.to&lt;/a&gt; and open a new pull request with your changes. If you're interested how I manage my dev.to posts through git and CI, &lt;a href="https://dev.to/maxime1992/manage-your-dev-to-blog-posts-from-a-git-repo-and-use-continuous-deployment-to-auto-publish-update-them-143j"&gt;read more here&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Follow me
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/maxime1992"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fdev-logo.png" title="Dev" alt="Dev"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fgithub-logo.png" title="Github" alt="Github"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://twitter.com/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Ftwitter-logo.png" title="Twitter" alt="Twitter"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.reddit.com/user/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Freddit-logo.png" title="Reddit" alt="Reddit"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.linkedin.com/in/maximerobert1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Flinkedin-logo.png" title="Linkedin" alt="Linkedin"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://stackoverflow.com/users/2398593/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fstackoverflow-logo.png" title="Stackoverflow" alt="Stackoverflow"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h1&gt;
  
  
  You may also enjoy reading
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;a href="https://dev.to/maxime1992/build-a-reactive-split-flap-display-with-angular-kne"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Freactive-split-flap-display%2Fbuild-a-reactive-split-flap-display-with-angular%2Fassets%2Fcover.png" alt="'Cover'"&gt;&lt;/a&gt;&lt;/th&gt;
&lt;th&gt;&lt;a href="https://dev.to/maxime1992/how-to-detect-unnecessary-renderings-of-dom-elements-in-your-web-app-to-improve-performances-13jd"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fdetect-unnecessary-renderings-of-dom-elements%2Fassets%2Fcover.png" alt="'Cover'"&gt;&lt;/a&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/maxime1992/build-a-reactive-split-flap-display-with-angular-kne"&gt;Build a reactive split-flap display with Angular&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/maxime1992/how-to-detect-unnecessary-renderings-of-dom-elements-in-your-web-app-to-improve-performances-13jd"&gt;How to detect unnecessary renderings of DOM elements in your web app to improve performance&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>angular</category>
      <category>rxjs</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Build a reactive split-flap display with Angular</title>
      <dc:creator>Maxime</dc:creator>
      <pubDate>Fri, 09 Jun 2023 08:33:30 +0000</pubDate>
      <link>https://dev.to/maxime1992/build-a-reactive-split-flap-display-with-angular-kne</link>
      <guid>https://dev.to/maxime1992/build-a-reactive-split-flap-display-with-angular-kne</guid>
      <description>&lt;p&gt;Hello!&lt;/p&gt;

&lt;p&gt;I've decided to make a follow up from &lt;a href="https://dev.to/maxime1992/rxjs-advanced-challenge-to-build-a-reactive-split-flap-display-1ej"&gt;RxJS: Advanced challenge to build a reactive split-flap display&lt;/a&gt; and build UI for it as it's always more pleasant to play around with the final app than emit into a subject to see the text change.&lt;/p&gt;

&lt;p&gt;Here's a &lt;strong&gt;live demo&lt;/strong&gt; of what we'll be building, that you can play with 🔥:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/components-issue-sacg3e?embed=1&amp;amp;file=src%2Fapp%2Fapp.component.ts&amp;amp;view=preview" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;If you haven't read the previous article I mentioned above, you should really read it first before jumping on this one as the &lt;a href="https://stackblitz.com/edit/rxjs-8vou8y?file=index.ts" rel="noopener noreferrer"&gt;reactive split-flap logic is already built&lt;/a&gt; and we'll only be focusing on the Angular side of things here.&lt;/p&gt;

&lt;h1&gt;
  
  
  Porting the split-flap code to an Angular service
&lt;/h1&gt;

&lt;p&gt;I'm going to be creating a new Stackblitz where I'll import most of the code we built in the first part and integrate that in a more Angular way.&lt;/p&gt;

&lt;p&gt;I'll copy over the entire &lt;code&gt;utils.ts&lt;/code&gt; file &lt;em&gt;(except for the part where I was manipulating the DOM manually)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Now, we'll create a new service &lt;code&gt;SplitFlapService&lt;/code&gt; that'll reuse our reactive split-flap code and wrap up the creation of new instances:&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;Injectable&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;providedIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&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;SplitFlapService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;getSplitFlapInstance&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;input$$&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;Subject&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;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;splitFlap$&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="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;inputToBoardLetters&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nf"&gt;switchScan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lettersOnBoard&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
          &lt;span class="nf"&gt;combineLatest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nx"&gt;lettersOnBoard&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;letter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
              &lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getLettersFromTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;letter&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;concatMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;letter&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;letter&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;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;150&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="nx"&gt;BASE&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&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;splitFlap$&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;p&gt;So far, so good.&lt;/p&gt;

&lt;h1&gt;
  
  
  Creating the view
&lt;/h1&gt;

&lt;p&gt;First of all, we know we're going to have an input and that our split-flap only support a given number of chars. So we'll start by creating a custom validator for our &lt;code&gt;FormControl&lt;/code&gt; that validates the input:&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;function&lt;/span&gt; &lt;span class="nf"&gt;allowedLettersValidator&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;ValidatorFn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;control&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormControl&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&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;ValidationErrors&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;allValidLetters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;control&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&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="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&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="nf"&gt;every&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;letter&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;REVERSE_LOOKUP_LETTERS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;letter&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&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;allValidLetters&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;invalidLetters&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing too fancy. We loop on each of the letters in the input and check if they are defined in our allowed chars. If not, we return an object &lt;code&gt;{ invalidLetters: true }&lt;/code&gt; that'll make the input invalid.&lt;/p&gt;

&lt;p&gt;Now, I won't define our &lt;code&gt;split-flap&lt;/code&gt; component here. It's only a presentational component which takes an input for all the letters to display and display them. Nothing more so there's little interest in showing that. Feel free to look the code in the Stackblitz in the &lt;code&gt;src/app/split-flap&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;Finally, we can focus on the component that will use our new service, instanciate a &lt;code&gt;split-flap&lt;/code&gt; and use it to show in the view based on an input, the associated &lt;code&gt;split-flap&lt;/code&gt; display.&lt;/p&gt;

&lt;p&gt;First of all, we inject our service and create an instance of a split-flap:&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;public&lt;/span&gt; &lt;span class="nx"&gt;splitFlapInstance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SplitFlapService&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getSplitFlapInstance&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we create a form control for it, with our custom validator:&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;public&lt;/span&gt; &lt;span class="nx"&gt;fc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormControl&lt;/span&gt;&lt;span class="p"&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;allowedLettersValidator&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Last but not least, we bind the value of our form control to the &lt;code&gt;input$$&lt;/code&gt; of our split-flap &lt;em&gt;(without forgetting to unsubscribe when the component is destroyed)&lt;/em&gt;:&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;private&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Subscription&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscription&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;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;valueChanges&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;filter&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nf"&gt;debounceTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;300&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;subscribe&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;splitFlapInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input$$&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ngAfterDestroy&lt;/span&gt;&lt;span class="p"&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;subscription&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;unsubscribe&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;Our view can then have the input and the associated split-flap component:&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;mat-form-field&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;mat-label&amp;gt;&lt;/span&gt;Input&lt;span class="nt"&gt;&amp;lt;/mat-label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;matInput&lt;/span&gt; &lt;span class="na"&gt;[formControl]=&lt;/span&gt;&lt;span class="s"&gt;"fc"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;mat-error&lt;/span&gt; &lt;span class="na"&gt;*ngIf=&lt;/span&gt;&lt;span class="s"&gt;"fc.invalid"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Invalid chars used&lt;span class="nt"&gt;&amp;lt;/mat-error&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/mat-form-field&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;app-split-flap&lt;/span&gt; &lt;span class="na"&gt;[letters]=&lt;/span&gt;&lt;span class="s"&gt;"splitFlapInstance.splitFlap$ | async"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/app-split-flap&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the final project:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/components-issue-sacg3e?embed=1&amp;amp;file=src%2Fapp%2Fapp.component.ts" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;I always like to start thinking about my streams in a really isolated way and once I've got everything working, then integrate where needed. It allows me to make sure I have a reusable code and keep my focus on the core logic, then the view.&lt;/p&gt;

&lt;p&gt;Observables integrates of course really well with Angular thanks to a lot of the default API using and the wonderful &lt;code&gt;async&lt;/code&gt; pipe.&lt;/p&gt;




&lt;p&gt;I hope you enjoyed this article, if you did let me know with a reaction and eventually drop a comment. It's always nice to hear back from people who took the time to read a post 😄! If you gave a go to the challenge yourself, share a link to your Stackblitz or let us know how far you went too!&lt;/p&gt;

&lt;p&gt;If you're interested in more articles about Angular, RxJS, open source, self hosting, data privacy, feel free to hit the &lt;strong&gt;follow button&lt;/strong&gt; for more. Thanks for reading!&lt;/p&gt;

&lt;h1&gt;
  
  
  Found a typo?
&lt;/h1&gt;

&lt;p&gt;If you've found a typo, a sentence that could be improved or anything else that should be updated on this blog post, you can access it through a git repository and make a pull request. Instead of posting a comment, please go directly to &lt;a href="https://github.com/maxime1992/my-dev.to"&gt;https://github.com/maxime1992/my-dev.to&lt;/a&gt; and open a new pull request with your changes. If you're interested how I manage my dev.to posts through git and CI, &lt;a href="https://dev.to/maxime1992/manage-your-dev-to-blog-posts-from-a-git-repo-and-use-continuous-deployment-to-auto-publish-update-them-143j"&gt;read more here&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Follow me
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/maxime1992"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fdev-logo.png" title="Dev" alt="Dev"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fgithub-logo.png" title="Github" alt="Github"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://twitter.com/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Ftwitter-logo.png" title="Twitter" alt="Twitter"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.reddit.com/user/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Freddit-logo.png" title="Reddit" alt="Reddit"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.linkedin.com/in/maximerobert1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Flinkedin-logo.png" title="Linkedin" alt="Linkedin"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://stackoverflow.com/users/2398593/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fstackoverflow-logo.png" title="Stackoverflow" alt="Stackoverflow"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h1&gt;
  
  
  You may also enjoy reading
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;a href="https://dev.to/maxime1992/building-scalable-robust-and-type-safe-forms-with-angular-3nf9"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fbuilding-scalable-robusts-and-type-safe-forms-with-angular%2Fassets%2Fsub-forms-schema.png" alt="'Cover'"&gt;&lt;/a&gt;&lt;/th&gt;
&lt;th&gt;&lt;a href="https://dev.to/maxime1992/how-to-detect-unnecessary-renderings-of-dom-elements-in-your-web-app-to-improve-performances-13jd"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fdetect-unnecessary-renderings-of-dom-elements%2Fassets%2Fcover.png" alt="'Cover'"&gt;&lt;/a&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/maxime1992/building-scalable-robust-and-type-safe-forms-with-angular-3nf9"&gt;Building scalable robusts and type safe forms with Angular&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/maxime1992/how-to-detect-unnecessary-renderings-of-dom-elements-in-your-web-app-to-improve-performances-13jd"&gt;How to detect unnecessary renderings of DOM elements in your web app to improve performance&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>angular</category>
      <category>rxjs</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>RxJS: Advanced challenge to build a reactive split-flap display</title>
      <dc:creator>Maxime</dc:creator>
      <pubDate>Fri, 09 Jun 2023 05:02:58 +0000</pubDate>
      <link>https://dev.to/maxime1992/rxjs-advanced-challenge-to-build-a-reactive-split-flap-display-1ej</link>
      <guid>https://dev.to/maxime1992/rxjs-advanced-challenge-to-build-a-reactive-split-flap-display-1ej</guid>
      <description>&lt;p&gt;Hello!&lt;/p&gt;

&lt;p&gt;In this article, I'm defining an exercise, challenging you to it, and giving my own answer to it.&lt;/p&gt;

&lt;p&gt;I'll explain in details my thinking and little spoiler alert, we'll be using operators that are not used very often!&lt;/p&gt;

&lt;p&gt;Time to shut down your imperative programming mind and turn on your reactive one. It'll be an interesting ride.&lt;/p&gt;

&lt;h1&gt;
  
  
  Intro
&lt;/h1&gt;

&lt;p&gt;If you've been following for me while, you may know by now that I'm a huge RxJS fan. &lt;em&gt;And if you're not yet following me, it's a really good time to chill a bit and click that follow button before we start melting our brains 🧠🔥!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So far on dev.to, I've written 3 articles that are either heavily using or completely dedicated to RxJS:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;img src="https://media.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%2Fk0xd59voqoexuzge4uws.png" alt="'Cover'"&gt;&lt;/th&gt;
&lt;th&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fbuilding-a-reactive-microwave-for-ryan-cavanaugh-with-rxjs%2Fassets%2Fmicrowave-cover.png" alt="'Cover'"&gt;&lt;/th&gt;
&lt;th&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Frxjs-avoid-an-easy-mistake-when-using-start-with%2Fassets%2Fcover.jpg" alt="'Cover'"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/maxime1992/brute-forcing-an-encrypted-message-from-enigma-using-the-web-worker-api-166b"&gt;Brute-forcing an encrypted message from Enigma using the web worker API&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/maxime1992/building-a-reactive-microwave-for-ryan-cavanaugh-with-rxjs-3b1a"&gt;Building a reactive microwave for Ryan Cavanaugh with RxJs&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/maxime1992/rxjs-avoid-an-easy-mistake-when-using-startwith-4ano"&gt;RxJS: Avoid an easy mistake when using startWith&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This one is by far the most challenging of them all.&lt;/p&gt;

&lt;p&gt;Recently, we bought at work a &lt;a href="https://en.wikipedia.org/wiki/Split-flap_display" rel="noopener noreferrer"&gt;Split flap display&lt;/a&gt;, also known as a "Solari board" or "Solari departure board".&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;a href="https://www.youtube.com/shorts/CO2qMKa4Nqo" rel="noopener noreferrer"&gt;
      youtube.com
    &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;The one we got is connected one where we can from an app set the text and it'll update IRL. It's funny to display all kind of important messages or statistics.&lt;/p&gt;

&lt;p&gt;I'm sure you know how mesmerizing it can be when all the letters are spinning and suddenly, they all stop, one after another, to display the final sentence. Wouldn't it be a cool challenge to build our own with RxJS?&lt;/p&gt;

&lt;h1&gt;
  
  
  Rules of the challenge
&lt;/h1&gt;

&lt;p&gt;Before we get started on this, lets define the expected result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reactive, reactive, reactive. No imperative programming. Everything should be managed through streams and not rely on external state&lt;/strong&gt;. Of course it's fine for example to define the allowed letters in a global variable as a constant. But all the internal state of the app should be self contained in the streams&lt;/li&gt;
&lt;li&gt;20 letters in total on the display &lt;em&gt;(easy to change anyway)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Can display all the letters from &lt;code&gt;A&lt;/code&gt; to &lt;code&gt;Z&lt;/code&gt;, can have spaces between letters/words, and numbers from &lt;code&gt;0&lt;/code&gt; to &lt;code&gt;9&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The page should output the new state of the board whenever one letter is updated&lt;/li&gt;
&lt;li&gt;If the text changes at any point in time, whether there's one ongoing already or if it's currently stopped, it shouldn't start from scratch. Just like in reality, it should continue from where it is currently and roll the letters to the new position&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example of an expected output:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Freactive-split-flap-display%2Frxjs-advanced-challenge-to-build-a-reactive-split-flap-display%2Fassets%2Fwelcome-to-dev-to-split-flap-board.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Freactive-split-flap-display%2Frxjs-advanced-challenge-to-build-a-reactive-split-flap-display%2Fassets%2Fwelcome-to-dev-to-split-flap-board.gif" alt="'Split-flap example'"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Challenging you!
&lt;/h1&gt;

&lt;p&gt;Yes you! I think it'd be a lot of fun if people reading this were to give it a go on their own and share how far they got.&lt;/p&gt;

&lt;p&gt;In order to make this easier and not dwell on painful setup, I've created a &lt;strong&gt;&lt;a href="https://stackblitz.com/edit/rxjs-3x16xb?file=index.ts" rel="noopener noreferrer"&gt;Stackblitz template&lt;/a&gt;&lt;/strong&gt; ready to go!&lt;/p&gt;

&lt;p&gt;It contains some utility functions in &lt;code&gt;utils.ts&lt;/code&gt; to manipulate the letters and the DOM, so that we &lt;strong&gt;remain focused on the streams&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you decide to give this a go, fork the original Stackblitz or start from scratch if you prefer to. Feel free to modify &lt;strong&gt;anything&lt;/strong&gt; from the template I've made. It's just here as a base to help, not here to hold you back.&lt;/p&gt;

&lt;p&gt;Don't forget to save your Stackblitz and share it as a comment. Even if you didn't manage to go all the way. Let's discuss the results!&lt;/p&gt;

&lt;h1&gt;
  
  
  My solution
&lt;/h1&gt;

&lt;p&gt;In programming, it's likely that there is more than one solution to achieve the same result. So keep in mind that this is my own approach and someone could come up with a different or even better solution.&lt;/p&gt;

&lt;p&gt;From now on, be aware that if you want to work on your own solution first, the following will is one possible answer, so it's really a big spoiler ⚠️.&lt;/p&gt;

&lt;p&gt;Without further ado, let's jump straight in.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Freactive-split-flap-display%2Frxjs-advanced-challenge-to-build-a-reactive-split-flap-display%2Fassets%2Fawesome-ride.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Freactive-split-flap-display%2Frxjs-advanced-challenge-to-build-a-reactive-split-flap-display%2Fassets%2Fawesome-ride.png" alt="'A roller coaster'"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Here we gooooooooooooo".&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The first thing I try doing when I have to build a feature that relies on observables, is picture in my head how that should all interact. Outter streams, inner streams. While it may be hard to represent visually, I visualise what I'm trying to build as a mix of &lt;a href="https://rxjs.dev/guide/operators#marble-diagrams" rel="noopener noreferrer"&gt;marble diagrams&lt;/a&gt; and pipes that are all connected. I'll do my best to illustrate this but bear with me as this is no easy task.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Freactive-split-flap-display%2Frxjs-advanced-challenge-to-build-a-reactive-split-flap-display%2Fassets%2Fstreams-representation.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Freactive-split-flap-display%2Frxjs-advanced-challenge-to-build-a-reactive-split-flap-display%2Fassets%2Fstreams-representation.png" alt="'Streams representation'"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is as close as I can represent how things work in a visual way. Now let's dig into the code.&lt;/p&gt;

&lt;p&gt;As a reminder, I'm not going to dive into the utils file I've created to manage letters and DOM manipulation. Feel free to take a look &lt;a href="https://stackblitz.com/edit/rxjs-3x16xb?file=utils.ts" rel="noopener noreferrer"&gt;here&lt;/a&gt; for the whole implementation.&lt;/p&gt;

&lt;p&gt;Now, if we focus on the stream itself and put aside the update of the DOM for now, here's the core of our code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Freactive-split-flap-display%2Frxjs-advanced-challenge-to-build-a-reactive-split-flap-display%2Fassets%2Fcore.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Freactive-split-flap-display%2Frxjs-advanced-challenge-to-build-a-reactive-split-flap-display%2Fassets%2Fcore.png" alt="'Core of our RxJS code'"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before we dig into the specifics, let's contemplate for a second the beauty and the power of RxJS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Freactive-split-flap-display%2Frxjs-advanced-challenge-to-build-a-reactive-split-flap-display%2Fassets%2Fcontemplating.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Freactive-split-flap-display%2Frxjs-advanced-challenge-to-build-a-reactive-split-flap-display%2Fassets%2Fcontemplating.png" alt="'Contemplating our code'"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Keep in mind that the &lt;strong&gt;14 lines of code&lt;/strong&gt; above are capable of displaying a string just like a split-flap board, with animations &lt;em&gt;(delay applied separately on each letter)&lt;/em&gt;, keep the current state and if the text changes, start changing the letters from exactly where we are to the new position. It also manages correctly if we try to apply a text and change to a different one &lt;strong&gt;while the first one is still running, without resetting from scratch&lt;/strong&gt; and picking up exactly where it is 🔥. RxJS is truly a thing of beauty.&lt;/p&gt;

&lt;p&gt;The first thing we use is our input. It's a simple &lt;code&gt;Subject&lt;/code&gt; that we can &lt;code&gt;next&lt;/code&gt; into whenever we want. This could be bound to an input text for example.&lt;/p&gt;

&lt;p&gt;Then, we map that string to an input that'd match the board letters. Meaning, we want it to be an array of chars. One char for each entry of our display. For example, assuming the board supports 10 chars display for simplicity, if we pass &lt;code&gt;'HELLO'&lt;/code&gt; we'd get back &lt;code&gt;['H', 'E', 'L', 'L', 'O', '', '', '', '', '']&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then 🥁... We use a &lt;a href="https://rxjs.dev/api/index/function/switchScan" rel="noopener noreferrer"&gt;&lt;code&gt;switchScan&lt;/code&gt;&lt;/a&gt;, which in all honnestly is the first time I could find a use case for it 🎉. As the name suggests, it's a mix of &lt;code&gt;switch&lt;/code&gt; and &lt;code&gt;scan&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;switch&lt;/code&gt; means that we'll stop the inner stream if it's still running when the parent stream emits again&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scan&lt;/code&gt; is like a &lt;code&gt;reduce&lt;/code&gt;. Meaning it take an accumulator, a current value, and the value you return from it becomes the new accumulator. Except that with &lt;code&gt;reduce&lt;/code&gt;, it'll wait for the stream to complete before emitting and with &lt;code&gt;scan&lt;/code&gt; it'll emit all the intermediate results. Here, &lt;code&gt;switchScan&lt;/code&gt; means that our inner stream will be subscribed to, it'll be able to make multiple emissions that will become the new accumulator, and if the parent streams emits again &lt;em&gt;(our &lt;code&gt;input$$&lt;/code&gt; subject)&lt;/em&gt;, we stop changing the letters and start changing again to reach the new string just provided. Here, this operator is doing a fantastic job, it's the key to being able to stop an animation in the middle of it and continue from where the board is currently without resetting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We then have a &lt;code&gt;combineLatest&lt;/code&gt;. In the diagram I drew above, the &lt;code&gt;combineLatest&lt;/code&gt; is here to assemble all the streams &lt;em&gt;(1 per letter)&lt;/em&gt; that are representend as vertical green arrows.&lt;/p&gt;

&lt;p&gt;The build the array of streams that is passed to the &lt;code&gt;combineLatest&lt;/code&gt;, we loop on the current letters we just received and for each, we compute all the letters in between what we currently have and the target letter for this tile. We then emit each of these intermediate letters that we'll need to go through by using &lt;code&gt;from&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Finally, with the &lt;code&gt;from&lt;/code&gt; emitting all the intermediate letters, we use &lt;code&gt;concatMap&lt;/code&gt; with a &lt;code&gt;delay&lt;/code&gt; to make sure we emit the intermediate letters in the same order, with a delay in between each for simulate the animation.&lt;/p&gt;

&lt;p&gt;Here's the &lt;a href="https://stackblitz.com/edit/rxjs-8vou8y?file=index.ts" rel="noopener noreferrer"&gt;final version live in Stackblitz&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/rxjs-8vou8y?embed=1&amp;amp;file=index.ts" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;This was a &lt;strong&gt;lot&lt;/strong&gt; to take in, but was an interesting ride. Wasn't it?&lt;/p&gt;

&lt;p&gt;Whenever I get the chance to write RxJS code like this, it really reminds me why I love coding. When I found out about reactive programming about 6 years ago, it really blew my mind and I wanted to play with it till I'd be confortable enough to express myself with it, just like most developers are confortable expressing themselves with imperative code that we tend to learn first. Being able to write this now makes me really happy and I try to share that passion for reactive programming whenever possible.&lt;/p&gt;

&lt;p&gt;RxJS is a powerful tool and it takes time to learn. But once you manage to wrap your head around it, it's possible to handle really complex use case, in a relatively easy or at least readable way.&lt;/p&gt;




&lt;p&gt;I hope you enjoyed this article, if you did let me know with a reaction and eventually drop a comment. It's always nice to hear back from people who took the time to read a post 😄! If you gave a go to the challenge yourself, share a link to your Stackblitz or let us know how far you went too!&lt;/p&gt;

&lt;p&gt;If you're interested in more articles about Angular, RxJS, open source, self hosting, data privacy, feel free to hit the &lt;strong&gt;follow button&lt;/strong&gt; for more. Thanks for reading!&lt;/p&gt;

&lt;h1&gt;
  
  
  Found a typo?
&lt;/h1&gt;

&lt;p&gt;If you've found a typo, a sentence that could be improved or anything else that should be updated on this blog post, you can access it through a git repository and make a pull request. Instead of posting a comment, please go directly to &lt;a href="https://github.com/maxime1992/my-dev.to"&gt;https://github.com/maxime1992/my-dev.to&lt;/a&gt; and open a new pull request with your changes. If you're interested how I manage my dev.to posts through git and CI, &lt;a href="https://dev.to/maxime1992/manage-your-dev-to-blog-posts-from-a-git-repo-and-use-continuous-deployment-to-auto-publish-update-them-143j"&gt;read more here&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Follow me
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/maxime1992"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fdev-logo.png" title="Dev" alt="Dev"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fgithub-logo.png" title="Github" alt="Github"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://twitter.com/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Ftwitter-logo.png" title="Twitter" alt="Twitter"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.reddit.com/user/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Freddit-logo.png" title="Reddit" alt="Reddit"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.linkedin.com/in/maximerobert1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Flinkedin-logo.png" title="Linkedin" alt="Linkedin"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://stackoverflow.com/users/2398593/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fstackoverflow-logo.png" title="Stackoverflow" alt="Stackoverflow"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h1&gt;
  
  
  You may also enjoy reading
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;a href="https://dev.to/maxime1992/the-holy-grail-of-note-taking-private-data-efficient-methodology-and-p2p-encrypted-sync-across-all-your-devices-1ih3"&gt;&lt;img src="https://media.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%2Fpx6843z2bjenam0jqgkg.png" alt="'Cover'"&gt;&lt;/a&gt;&lt;/th&gt;
&lt;th&gt;&lt;a href="https://dev.to/maxime1992/paperless-ngx-manage-your-documents-like-never-before-2a3n"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fnext-level-data-privacy%2Fpart-2-paperless-ngx-manage-your-documents-like-never-before%2Fassets%2Fcover.png%3FbustCache%3D1" alt="'Cover'"&gt;&lt;/a&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/maxime1992/the-holy-grail-of-note-taking-private-data-efficient-methodology-and-p2p-encrypted-sync-across-all-your-devices-1ih3"&gt;The holy grail of note-taking: Private data, efficient methodology and P2P encrypted sync across all your devices&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/maxime1992/paperless-ngx-manage-your-documents-like-never-before-2a3n"&gt;Paperless-ngx, manage your documents like never before&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>javascript</category>
      <category>rxjs</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>The holy grail of note-taking: Private data, efficient methodology and P2P encrypted sync across all your devices</title>
      <dc:creator>Maxime</dc:creator>
      <pubDate>Wed, 07 Jun 2023 04:27:56 +0000</pubDate>
      <link>https://dev.to/maxime1992/the-holy-grail-of-note-taking-private-data-efficient-methodology-and-p2p-encrypted-sync-across-all-your-devices-1ih3</link>
      <guid>https://dev.to/maxime1992/the-holy-grail-of-note-taking-private-data-efficient-methodology-and-p2p-encrypted-sync-across-all-your-devices-1ih3</guid>
      <description>&lt;h1&gt;
  
  
  Disclaimer
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;I have learnt very quickly that opiniated articles can get any random stranger with a different opinion to challenge you to death behind his keyboard because you're not enjoying the same things as he does. Therefore I'm going to start this article with a note: &lt;strong&gt;This blog post is strongly opiniated&lt;/strong&gt;. I'm sharing what I like, in case it can be helpful to some. If you've got a different point of view, if you use a different software, if you use a different methodology, &lt;strong&gt;don't be offended&lt;/strong&gt;. Simply share a comment with your opinion explaining why you prefer something different. We don't have to all agree and having multiple feedback will always help others as long as it's constructive. Without further ado, let's get started 🙂!&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Intro
&lt;/h1&gt;

&lt;p&gt;Evernote, Microsoft OneNote, Apple Notes, Google Keep, Notion, ... There is a huge number of note-taking apps out there! From dead simple to more sophisticated ones.&lt;/p&gt;

&lt;p&gt;One thing the apps I listed above have in common though, isn't to please me: &lt;strong&gt;They're in charge of our data&lt;/strong&gt;. &lt;strong&gt;Our notes&lt;/strong&gt;. While this can surely make our lives easier at first, we often end up writing a lot of precious data in those. If these apps are free, that is for a very good reason: &lt;strong&gt;Nowadays, data is gold for tech giants&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But what if I told you there was a non painful way to keep all your notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In &lt;strong&gt;sync&lt;/strong&gt; across all your devices&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Private&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;In a &lt;strong&gt;standard&lt;/strong&gt; format, easily exportable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All that quite easily. Which pill do you choose?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fthe-holy-grail-of-note-taking%2Fassets%2Fpills.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fthe-holy-grail-of-note-taking%2Fassets%2Fpills.png" title="Blue or red pill" alt="Blue or red pill"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;When Midjourney makes you choose Morpheus style, you've got enough pills for a lifetime. Won't need another prescription.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Red pill
&lt;/h1&gt;

&lt;p&gt;Welcome to the rabbit hole 🐇.&lt;/p&gt;

&lt;h2&gt;
  
  
  Note-taking software
&lt;/h2&gt;

&lt;p&gt;I've always been taking notes. Loads of notes. Not super organized, not very thought through, but at least, I had something. About a year ago, I decided it was time to investigate into a better solution as my number of notes started to grow up and it was all becoming a mess.&lt;/p&gt;

&lt;p&gt;With data privacy as the key factor, I was looking for a local application, that'd not be in charge of hosting my data and which was using a standard format: &lt;strong&gt;Markdown&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;After a bunch of research and comparisons, one stood out based on feedback I read: &lt;strong&gt;&lt;a href="https://obsidian.md" rel="noopener noreferrer"&gt;Obsidian&lt;/a&gt;&lt;/strong&gt;. It was time to give it a go.&lt;/p&gt;

&lt;p&gt;Obsidian is described as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The private and flexible note‑taking app that adapts to the way you think.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's how it looks &lt;em&gt;(yes they do respect developers, there's a dark theme)&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fthe-holy-grail-of-note-taking%2Fassets%2Fobsidian-home-page.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fthe-holy-grail-of-note-taking%2Fassets%2Fobsidian-home-page.png" title="Obsidian home page" alt="Obsidian home page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🍒 on the cake, it's been built to be super modular and &lt;strong&gt;supports plugins&lt;/strong&gt;. Because of the huge community, there's nearly &lt;strong&gt;1000 community plugins&lt;/strong&gt; available when I'm writing those lines. 988 to be exact.&lt;/p&gt;

&lt;p&gt;What I like about Obsidian:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cross platform (including mobile apps)&lt;/li&gt;
&lt;li&gt;Beautiful UI&lt;/li&gt;
&lt;li&gt;Local&lt;/li&gt;
&lt;li&gt;Offline by default&lt;/li&gt;
&lt;li&gt;Tags&lt;/li&gt;
&lt;li&gt;Graph view to see connections between notes&lt;/li&gt;
&lt;li&gt;Community plugins

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Advanced tables&lt;/strong&gt;. Makes it really easy to manage tables in Markdown without having a mess&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Excalidraw&lt;/strong&gt;. I've been using &lt;a href="https://excalidraw.com" rel="noopener noreferrer"&gt;Excalidraw&lt;/a&gt; since it came out. It was love at first sight. It felt trivial to draw anything I had in mind to illustrate ideas quickly. This integration makes it easy to integrate drawings in notes. While I'm not using it (yet?!), it's worth noting that a few months back, Obsidian added "&lt;a href="https://obsidian.md/canvas" rel="noopener noreferrer"&gt;Canvas&lt;/a&gt;" in its core which is similar and maybe more integrated with Obsidian&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linter&lt;/strong&gt;. Add a &lt;a href="https://help.obsidian.md/Editing+and+formatting/Metadata" rel="noopener noreferrer"&gt;frontmatter&lt;/a&gt; with metadata about the creation date of notes and edit date as well&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Navigate cursor history&lt;/strong&gt;. While coding, having on your mouse the side buttons to go back and forth in your code is fantastic. This plugin gives you 2 arrows in Obsidian to have the same feature if you only have a trackpad&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prettier format&lt;/strong&gt;. I prefer to use this instead of the linter mentioned above as Prettier has a huge community and is strongly opiniated&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reading time&lt;/strong&gt;. To know how long I'm going to bore you with my articles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tag wrangler&lt;/strong&gt;. To manage existing tags, for example rename them. It detects all the tags in a smart way instead of just doing a string comparison which could affect some text as well if you were to make a global search and replace&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Obsidian Git&lt;/strong&gt;. I'll talk later on how to sync your notes on different devices but be aware of this plugin that can be useful as well&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Whatever OS you're using, just go to the &lt;a href="https://obsidian.md/download" rel="noopener noreferrer"&gt;download page&lt;/a&gt; and pick the one you want.&lt;/p&gt;

&lt;p&gt;Before moving on, I have to say that I have a wish for Obsidian: &lt;strong&gt;May it become open source one day&lt;/strong&gt;. I know the data remains in the users' hands but if Obsidian was to open its code, I feel like it could become a lot stronger than it already is. You can find out more &lt;a href="https://www.reddit.com/r/ObsidianMD/comments/tv6uql/is_obsidian_open_source" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now that we've got the note-taking software, do we just start creating all of our notes as usual?&lt;/p&gt;

&lt;h2&gt;
  
  
  Methodology
&lt;/h2&gt;

&lt;p&gt;As mentioned previously, I started to look into note-taking apps alternatives when my number of notes started to grow and it felt messy, hard to find the notes I was looking for.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fthe-holy-grail-of-note-taking%2Fassets%2Fnotes-everywhere.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fthe-holy-grail-of-note-taking%2Fassets%2Fnotes-everywhere.png" title="Notes, notes everywhere" alt="Notes, notes everywhere"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So I started investigating how to take notes properly. While there are an infinite number of solutions out here, eventually I came across a method called &lt;strong&gt;PARA&lt;/strong&gt;, which stands for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;P&lt;/strong&gt;roject&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A&lt;/strong&gt;rea&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;R&lt;/strong&gt;esource&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A&lt;/strong&gt;rchive&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'll summarise what it's all about but feel free to read this &lt;a href="https://fortelabs.com/blog/para" rel="noopener noreferrer"&gt;excellent article&lt;/a&gt; about it.&lt;/p&gt;

&lt;p&gt;The PARA method gives you a way to organise all your notes, into 4 main categories &lt;em&gt;(just folders)&lt;/em&gt;, which are the ones I listed above.&lt;/p&gt;

&lt;p&gt;I've kept the overall idea but renamed a few of them for my notes as it just made more sense to me.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PARA&lt;/th&gt;
&lt;th&gt;My folders equivalent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Project&lt;/td&gt;
&lt;td&gt;Projects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Area&lt;/td&gt;
&lt;td&gt;Areas of responsibility&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resource&lt;/td&gt;
&lt;td&gt;Resources&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Archive&lt;/td&gt;
&lt;td&gt;Archives&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Not much difference, but it's just clearer IMO.&lt;/p&gt;

&lt;p&gt;I'll give some examples from my own vault to make it clearer where notes should go. I use a lot of sub folders as well in those. It's just important to limit the top level architecture to these 4 folders.&lt;/p&gt;

&lt;h3&gt;
  
  
  Projects
&lt;/h3&gt;

&lt;p&gt;I've got &lt;code&gt;Art&lt;/code&gt;, &lt;code&gt;Domotic&lt;/code&gt;, &lt;code&gt;House work&lt;/code&gt; and &lt;code&gt;Wood working&lt;/code&gt;. Each of these can have sub folders again and as many notes as I want.&lt;/p&gt;

&lt;p&gt;This is for ongoing projects. Active ones. Some other examples could be notes for blog posts, YouTube videos you want to create, organising an event, etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  Areas of responsibility
&lt;/h3&gt;

&lt;p&gt;I've got &lt;code&gt;Car&lt;/code&gt;, &lt;code&gt;House work&lt;/code&gt;, &lt;code&gt;Professional development&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The name is quite self explanatory. Here are some examples that could go in there: &lt;code&gt;Kids&lt;/code&gt;, &lt;code&gt;Health&lt;/code&gt;, &lt;code&gt;Taxes&lt;/code&gt;, etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resources
&lt;/h3&gt;

&lt;p&gt;I've got &lt;code&gt;Family&lt;/code&gt;, &lt;code&gt;Games&lt;/code&gt;, &lt;code&gt;House&lt;/code&gt;, &lt;code&gt;IT&lt;/code&gt;, &lt;code&gt;Medias&lt;/code&gt;, &lt;code&gt;Travels&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Some of these folders sounds like they could be in "projects" or "areas of responsibility" but I could have the same folder name in those. Here it's really for notes that are not "projects" or "areas of responsibility" related. General information notes. For examples, I've got "House work" in "Projects" but also "Areas of responsibility" and here "House". In the first one, I put really important house work like renovating the house before for next winter. In the second one, I put some active projects like replacing a few things not working etc. As for here, in resources I put general knowledge like references of different equipments so I don't have to look for them all the time when I need an info, what I think is important to look for if I ever move to another house, etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  Archives
&lt;/h3&gt;

&lt;p&gt;For this one, I like to have one sub folder per year, and for each year one sub folder for each of the PARA points. Then I just move notes that are not relevant to me anymore from the other 3 folders to here. If I ever need them back, I can find them, if I don't, they're not shifting my focus away while looking in the 3 main folders.&lt;/p&gt;




&lt;p&gt;This method can easily be applied along the way and if you wish to give it a go but you have loads of notes already, you can migrate one file at a time, when reading one for example. I've been using this method for over a year and I find it very effective to organise all my notes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Synchronisation software
&lt;/h2&gt;

&lt;p&gt;We've got our Obsdian app setup, we know how to organise our notes. The last question remaining is how to synchronise our notes across several devices, for example between a laptop, a desktop and a phone.&lt;/p&gt;

&lt;p&gt;While this is not the solution I'm going to present, be aware that Obsidian offers its own solution for this, it's called &lt;a href="https://obsidian.md/sync" rel="noopener noreferrer"&gt;Sync&lt;/a&gt;. It's not free, but may be a good way to support the development of Obsidian as well!&lt;/p&gt;

&lt;p&gt;Personally, I've been using an open source equivalent of Dropbox that is called &lt;a href="https://syncthing.net" rel="noopener noreferrer"&gt;Syncthing&lt;/a&gt;. I invite you to read on their main page what they offer but essentially it's here for secure data exchange between devices, privately.&lt;/p&gt;

&lt;p&gt;Syncthing is available on all the different OS, you can check the &lt;a href="https://syncthing.net/downloads" rel="noopener noreferrer"&gt;download page&lt;/a&gt;. If you wish to use it from a server as well, you can install it easily with Docker &lt;em&gt;(either from &lt;a href="https://hub.docker.com/r/linuxserver/syncthing" rel="noopener noreferrer"&gt;LinuxServer package&lt;/a&gt; or the &lt;a href="https://hub.docker.com/r/syncthing/syncthing" rel="noopener noreferrer"&gt;official one&lt;/a&gt;).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here's how the UI looks &lt;em&gt;(image taken from the official webpage as an example)&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fthe-holy-grail-of-note-taking%2Fassets%2Fsyncthing.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fthe-holy-grail-of-note-taking%2Fassets%2Fsyncthing.png" title="Syncthing UI" alt="'Syncthing UI'"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On the left, it's all the folders we want to sync&lt;/li&gt;
&lt;li&gt;On the top right, "this device" shows several info for the current device (upload/download speed of files, number of files in sync and total weight, etc)&lt;/li&gt;
&lt;li&gt;On the bottom right, "Remote devices" shows all the devices you've linked to this one. For example if we're looking at the UI of your desktop, you could see your laptop and your phone here. It shows whether they're online and up to date or not&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To add a new device, select "Actions" at the top right, and "Show ID".&lt;/p&gt;

&lt;p&gt;It'll show a modal like this one:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fthe-holy-grail-of-note-taking%2Fassets%2Fsyncthing-device-id.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fthe-holy-grail-of-note-taking%2Fassets%2Fsyncthing-device-id.png" title="Syncthing device ID" alt="'Syncthing device ID'"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Anonymized screenshot, it shows a QR code instead and at the top, some random letters different that xxxxx of course.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You can then either scan the QR code from your phone or if it's another computer, just send the unique ID displayed at the top.&lt;/p&gt;

&lt;p&gt;After a few seconds, it'll show you on a modal at the top of syncthing with the ID of the remote device &lt;em&gt;(that you should always double check)&lt;/em&gt; and if you accept, the devices will be linked together.&lt;/p&gt;

&lt;p&gt;After this, you can select a folder to share:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fthe-holy-grail-of-note-taking%2Fassets%2Fsyncthing-add-folder.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fthe-holy-grail-of-note-taking%2Fassets%2Fsyncthing-add-folder.png" title="Syncthing folder to share" alt="'Syncthing folder to share'"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Like the one containing your Obsidian vault with all your notes for example &lt;em&gt;(or anything else really, could even be a folder to share pictures from your phone in real time when you take them!)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;There's then a tab "Sharing" in which you can select which device you want to share this folder with. What's really nice about this is that &lt;strong&gt;you can have more than 2 devices in sync for the same folder&lt;/strong&gt;. Meaning that you can increase the chance of having them always up to date because the sync is done in all the direction whenever the devices are online.&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Important&lt;/strong&gt; ⚠️&lt;/p&gt;

&lt;p&gt;As noted by &lt;a href="https://www.reddit.com/r/Syncthing/comments/1434gu7/comment/jn8punm/?context=3" rel="noopener noreferrer"&gt;&lt;em&gt;bouche_bag&lt;/em&gt; on Reddit&lt;/a&gt;, Syncthing is here synchronise files across devices but &lt;strong&gt;does not replace a backup strategy&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
I plan on making an article about &lt;a href="https://kopia.io" rel="noopener noreferrer"&gt;Kopia&lt;/a&gt; to explain how to make regular backups of your data easily but meanwhile you can look into it yourself if you want. It's definitely not the only solution out there, just an idea. Feel free to compare with other backup softwares.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;If you care about data privacy, this could be a solid way to have your notes in sync across several devices without sharing all your data with a big corp. They're also going to be always available, even offline as once the sync is done, your notes are on your device.&lt;/p&gt;

&lt;p&gt;As explained, this is how I do things for now, but &lt;strong&gt;I'd be delighted to hear more from you and especially which note-taking app is your favourite and why&lt;/strong&gt;. Who knows, we may all discover the next best app thanks to 1 comment.&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;




&lt;p&gt;I hope you enjoyed this article, if you did let me know with a reaction and eventually drop a comment. It's always nice to hear back from people who took the time to read a post 😄!&lt;/p&gt;

&lt;p&gt;If you're interested in more articles about Angular, RxJS, open source, self hosting, data privacy, feel free to hit the &lt;strong&gt;follow button&lt;/strong&gt; for more. Thanks for reading!&lt;/p&gt;

&lt;h1&gt;
  
  
  Found a typo?
&lt;/h1&gt;

&lt;p&gt;If you've found a typo, a sentence that could be improved or anything else that should be updated on this blog post, you can access it through a git repository and make a pull request. Instead of posting a comment, please go directly to &lt;a href="https://github.com/maxime1992/my-dev.to"&gt;https://github.com/maxime1992/my-dev.to&lt;/a&gt; and open a new pull request with your changes. If you're interested how I manage my dev.to posts through git and CI, &lt;a href="https://dev.to/maxime1992/manage-your-dev-to-blog-posts-from-a-git-repo-and-use-continuous-deployment-to-auto-publish-update-them-143j"&gt;read more here&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Follow me
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/maxime1992"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fdev-logo.png" title="Dev" alt="Dev"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fgithub-logo.png" title="Github" alt="Github"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://twitter.com/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Ftwitter-logo.png" title="Twitter" alt="Twitter"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.reddit.com/user/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Freddit-logo.png" title="Reddit" alt="Reddit"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.linkedin.com/in/maximerobert1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Flinkedin-logo.png" title="Linkedin" alt="Linkedin"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://stackoverflow.com/users/2398593/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fstackoverflow-logo.png" title="Stackoverflow" alt="Stackoverflow"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h1&gt;
  
  
  You may also enjoy reading
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;a href="https://dev.to/maxime1992/next-level-data-privacy-with-easy-free-and-secure-self-hosting-at-home-2c84"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fnext-level-data-privacy%2Fpart-1-next-level-data-privacy-with-easy-free-and-secure-self-hosting-at-home%2Fassets%2Fcover.png%3FbustCache%3D2" alt="'Cover'"&gt;&lt;/a&gt;&lt;/th&gt;
&lt;th&gt;&lt;a href="https://dev.to/maxime1992/how-to-detect-unnecessary-renderings-of-dom-elements-in-your-web-app-to-improve-performances-13jd"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fdetect-unnecessary-renderings-of-dom-elements%2Fassets%2Fcover.png" alt="'Cover'"&gt;&lt;/a&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/maxime1992/next-level-data-privacy-with-easy-free-and-secure-self-hosting-at-home-2c84"&gt;Next level data privacy with easy, free and secure self hosting at home&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/maxime1992/how-to-detect-unnecessary-renderings-of-dom-elements-in-your-web-app-to-improve-performances-13jd"&gt;How to detect unnecessary renderings of DOM elements in your web app to improve performance&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>opensource</category>
      <category>selfhosting</category>
      <category>privacy</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Paperless-ngx, manage your documents like never before</title>
      <dc:creator>Maxime</dc:creator>
      <pubDate>Mon, 05 Jun 2023 07:15:30 +0000</pubDate>
      <link>https://dev.to/maxime1992/paperless-ngx-manage-your-documents-like-never-before-2a3n</link>
      <guid>https://dev.to/maxime1992/paperless-ngx-manage-your-documents-like-never-before-2a3n</guid>
      <description>&lt;p&gt;Hi there 👋!&lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://dev.to/maxime1992/next-level-data-privacy-with-easy-free-and-secure-self-hosting-at-home-2c84"&gt;previous post&lt;/a&gt; of this series, we've seen how to setup a server to self host any web application, at home and expose it safely to the web.&lt;/p&gt;

&lt;p&gt;In this one, &lt;strong&gt;we will be adding our first "real" application: &lt;a href="https://github.com/paperless-ngx/paperless-ngx" rel="noopener noreferrer"&gt;Paperless-ngx&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Paperless-ngx
&lt;/h1&gt;

&lt;p&gt;The description of the project on Github is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A community-supported supercharged version of paperless: scan, index and archive all your physical documents&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I've been using Paperless-ngx for months now and I'm truly impressed that an &lt;strong&gt;open source, non commercial project&lt;/strong&gt; &lt;em&gt;(in any way)&lt;/em&gt; can be this good. Start uploading all your documents, add a few tags, correspondents, documents types, start assigning those to the documents you've imported and let the magic happen 🪄. &lt;strong&gt;Paperless-ngx will learn from your documents and habits, and then be able to automatically assign tags, correspondents, types, dates etc&lt;/strong&gt;. The initial phase on which you have to upload and tag all your documents &lt;em&gt;(could be thousands)&lt;/em&gt; can be time consuming based on how many you want to triage at first. But once that's done and you enter "routine mode", uploading documents one at a time when you receive them, will take only seconds.&lt;/p&gt;

&lt;p&gt;It's 100 times better than saving documents on a Google Drive like service where you organize documents by folder IMO. I was never satisfied with any of the option I had. Organized folders by year? Types? Correspondents? What happens the day you want to search by another criteria? Not a very good experience.&lt;/p&gt;

&lt;p&gt;Here are some of the features highlights for Paperless-ngx:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;💅 Nice web UI&lt;/li&gt;
&lt;li&gt;⚙️ Upload multiple documents and they'll be queued/processed quickly and analysed/indexed&lt;/li&gt;
&lt;li&gt;👀 OCR is ran on all the documents, meaning that even if you upload pictures, you'd still be able to search by text&lt;/li&gt;
&lt;li&gt;👫 Users and groups permissions. You can have multiple users on the same instance and easily share documents&lt;/li&gt;
&lt;li&gt;🔎 Fantastic filtering to find any document quickly. Date range, tags, correspondents, document types, actual content, even though it was a picture thanks to the OCR mode...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fnext-level-data-privacy%2Fpart-2-paperless-ngx-manage-your-documents-like-never-before%2Fassets%2Fpaperless-ngx-screenshot.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fnext-level-data-privacy%2Fpart-2-paperless-ngx-manage-your-documents-like-never-before%2Fassets%2Fpaperless-ngx-screenshot.png" title="Paperless-ngx screenshot" alt="Paperless-ngx screenshot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Setup in our Docker Compose
&lt;/h1&gt;

&lt;p&gt;The first thing we need to do is to get the service up and running. We'll see how to expose it through swag as a new sub domain right after.&lt;/p&gt;

&lt;p&gt;Open up the &lt;code&gt;docker-compose.yaml&lt;/code&gt; file we created in the previous blog post and add the following 3 services:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Remember to edit the volumes path if you don't want to save the data in &lt;code&gt;~/opt&lt;/code&gt; folder.&lt;/em&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="nn"&gt;---&lt;/span&gt; &lt;span class="c1"&gt;# ----------------------------&lt;/span&gt;
&lt;span class="c1"&gt;# ----------------------------&lt;/span&gt;
&lt;span class="c1"&gt;# PAPERLESS-NGX&lt;/span&gt;
&lt;span class="c1"&gt;# Source: https://github.com/paperless-ngx/paperless-ngx/blob/main/docker/compose/docker-compose.mariadb.yml&lt;/span&gt;
&lt;span class="c1"&gt;# to add a user: `docker compose run --rm paperless-ngx-webserver createsuperuser`&lt;/span&gt;
&lt;span class="na"&gt;paperless-ngx-broker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker.io/library/redis:7&lt;/span&gt;
  &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;paperless-ngx-broker&lt;/span&gt;
  &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;common.env&lt;/span&gt;
  &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
  &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;~/opt/paperless-ngx/broker:/data&lt;/span&gt;

&lt;span class="na"&gt;paperless-ngx-db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker.io/library/mariadb:10&lt;/span&gt;
  &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;paperless-ngx-db&lt;/span&gt;
  &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
  &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;~/opt/paperless-ngx/db:/var/lib/mysql&lt;/span&gt;
  &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;common.env&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;paperless-ngx.database.env&lt;/span&gt;
  &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MARIADB_HOST=paperless&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MARIADB_DATABASE=paperless&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MARIADB_USER=paperless&lt;/span&gt;

&lt;span class="na"&gt;paperless-ngx-webserver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/paperless-ngx/paperless-ngx:latest&lt;/span&gt;
  &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;paperless-ngx-webserver&lt;/span&gt;
  &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
  &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;paperless-ngx-db&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;paperless-ngx-broker&lt;/span&gt;
  &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CMD'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;curl'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-f'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8000'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
    &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
  &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;~/opt/paperless-ngx/webserver/data:/usr/src/paperless/data&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;~/opt/paperless-ngx/webserver/media:/usr/src/paperless/media&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;~/opt/paperless-ngx/webserver/export:/usr/src/paperless/export&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;~/opt/paperless-ngx/webserver/consume:/usr/src/paperless/consume&lt;/span&gt;
  &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;common.env&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;paperless-ngx.database.env&lt;/span&gt;
  &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PAPERLESS_REDIS=redis://paperless-ngx-broker:6379&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PAPERLESS_DBENGINE=mariadb&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PAPERLESS_DBHOST=paperless-ngx-db&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PAPERLESS_DBUSER=paperless&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PAPERLESS_DBPORT=3306&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PAPERLESS_URL=https://paperless-ngx.yourdomain.duckdns.org&lt;/span&gt;
&lt;span class="c1"&gt;# ----------------------------&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Also remember to edit the &lt;code&gt;PAPERLESS_URL&lt;/code&gt; with &lt;code&gt;yourdomain&lt;/code&gt; and replace accordingly.&lt;/p&gt;

&lt;p&gt;Then create at the same level of the &lt;code&gt;docker-compose.yaml&lt;/code&gt; a file called &lt;code&gt;paperless-ngx.database.env&lt;/code&gt; with the following content:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="s"&gt;MARIADB_ROOT_PASSWORD=GENERATE_STRONG_PASSWORD_1_HERE&lt;/span&gt;
&lt;span class="s"&gt;MARIADB_PASSWORD=GENERATE_STRONG_PASSWORD_2_HERE&lt;/span&gt;
&lt;span class="s"&gt;PAPERLESS_DBPASS=PUT_THE_SAME_AS_STRONG_PASSWORD_2_HERE&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let's spin those up!&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

docker compose up -d


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;and add a user:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

docker compose run --rm paperless-ngx-webserver createsuperuser


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;It'll prompt you from the command line to enter a few info and then you should end up with this message:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

Superuser created successfully.


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Time to expose our application to access it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Expose the service with SWAG and NGINX
&lt;/h1&gt;

&lt;p&gt;Whenever you want to expose a new application, the first thing to do is check if there's a pre-defined templates offered already for us in &lt;code&gt;swag/config/nginx/proxy-confs&lt;/code&gt;. Unfortunately, that's not the case for Paperless-ngx. Maybe it'll be added later on!&lt;/p&gt;

&lt;p&gt;This is not an issue at all though, because they offer a default template that works straight away in most cases. Copy &lt;code&gt;swag/config/nginx/proxy-confs/_template.subdomain.conf.sample&lt;/code&gt; to &lt;code&gt;swag/config/nginx/site-confs&lt;/code&gt; and rename it &lt;code&gt;paperless-ngx.conf&lt;/code&gt; in the new folder.&lt;/p&gt;

&lt;p&gt;Edit it to end up with:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    server_name paperless-ngx.*;

    include /config/nginx/ssl.conf;

    client_max_body_size 0;

    # enable for ldap auth (requires ldap-location.conf in the location block)
    #include /config/nginx/ldap-server.conf;

    # enable for Authelia (requires authelia-location.conf in the location block)
    include /config/nginx/authelia-server.conf;

    location / {
        # enable the next two lines for http auth
        #auth_basic "Restricted";
        #auth_basic_user_file /config/nginx/.htpasswd;

        # enable for ldap auth (requires ldap-server.conf in the server block)
        #include /config/nginx/ldap-location.conf;

        # enable for Authelia (requires authelia-server.conf in the server block)
        include /config/nginx/authelia-location.conf;

        include /config/nginx/proxy.conf;
        include /config/nginx/resolver.conf;
        set $upstream_app paperless-ngx-webserver;
        set $upstream_port 8000;
        set $upstream_proto http;
        proxy_pass $upstream_proto://$upstream_app:$upstream_port;
    }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Note that the line &lt;code&gt;server_name paperless-ngx.*;&lt;/code&gt; is the one defining the name of our sub domain which in this case will be &lt;code&gt;paperless-ngx&lt;/code&gt;. You can totally change that to whatever you prefer, could be &lt;code&gt;paperless&lt;/code&gt; or &lt;code&gt;documents&lt;/code&gt; for example. Just remember to update our environment variable &lt;code&gt;PAPERLESS_URL&lt;/code&gt; in our &lt;code&gt;docker-compose.yaml&lt;/code&gt; accordingly.&lt;/p&gt;

&lt;p&gt;Once this file is added, we're pretty much done already! All we've got to do now is to restart SWAG so that it takes it into consideration.&lt;/p&gt;

&lt;p&gt;Open up the URL &lt;a href="https://paperless-ngx.yourdomain.duckdns.org" rel="noopener noreferrer"&gt;https://paperless-ngx.yourdomain.duckdns.org&lt;/a&gt; and 🥁...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fnext-level-data-privacy%2Fpart-2-paperless-ngx-manage-your-documents-like-never-before%2Fassets%2Fauthelia-login-page.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fnext-level-data-privacy%2Fpart-2-paperless-ngx-manage-your-documents-like-never-before%2Fassets%2Fauthelia-login-page.png" title="Authelia login page" alt="Authelia login page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is because we've made sure above to uncomment the 2 lines&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

# enable for Authelia (requires authelia-location.conf in the location block)
include /config/nginx/authelia-server.conf;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;and&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

# enable for Authelia (requires authelia-server.conf in the server block)
include /config/nginx/authelia-location.conf;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;But once we authenticate:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fnext-level-data-privacy%2Fpart-2-paperless-ngx-manage-your-documents-like-never-before%2Fassets%2Fpaperless-ngx-landing-page.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fnext-level-data-privacy%2Fpart-2-paperless-ngx-manage-your-documents-like-never-before%2Fassets%2Fpaperless-ngx-landing-page.png" title="Paperless-ngx landing page" alt="Paperless-ngx landing page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We've got our app running 🎉!&lt;/p&gt;

&lt;p&gt;Log in with the user you created earlier and:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fnext-level-data-privacy%2Fpart-2-paperless-ngx-manage-your-documents-like-never-before%2Fassets%2Fpaperless-ngx-landing-page-once-logged-in.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fnext-level-data-privacy%2Fpart-2-paperless-ngx-manage-your-documents-like-never-before%2Fassets%2Fpaperless-ngx-landing-page-once-logged-in.png" title="Paperless-ngx landing page once logged in" alt="Paperless-ngx landing page once logged in"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can now enjoy your Paperless-ngx instance and start uploading your documents! While it uploads and before you start organizing them, I'd definitely recommend to read the &lt;a href="https://docs.paperless-ngx.com/usage/#basic-searching" rel="noopener noreferrer"&gt;best practices&lt;/a&gt; guide from the official documentation.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Adding a new service is as simple as finding the Docker Compose configuration, often offered by the project you're trying to run, in their documentation and adding one NGINX config file in SWAG to expose it, safely behind your 2FA security with Authelia.&lt;/p&gt;

&lt;p&gt;Happy document triaging!&lt;/p&gt;




&lt;p&gt;I hope you enjoyed this article, if you did let me know with a reaction and eventually drop a comment. It's always nice to hear back from people who took the time to read a post 😄!&lt;/p&gt;

&lt;p&gt;If you're interested in more articles about Angular, RxJS, open source, self hosting, data privacy, feel free to hit the &lt;strong&gt;follow button&lt;/strong&gt; for more. Thanks for reading!&lt;/p&gt;

&lt;h1&gt;
  
  
  Found a typo?
&lt;/h1&gt;

&lt;p&gt;If you've found a typo, a sentence that could be improved or anything else that should be updated on this blog post, you can access it through a git repository and make a pull request. Instead of posting a comment, please go directly to &lt;a href="https://github.com/maxime1992/my-dev.to"&gt;https://github.com/maxime1992/my-dev.to&lt;/a&gt; and open a new pull request with your changes. If you're interested how I manage my dev.to posts through git and CI, &lt;a href="https://dev.to/maxime1992/manage-your-dev-to-blog-posts-from-a-git-repo-and-use-continuous-deployment-to-auto-publish-update-them-143j"&gt;read more here&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Follow me
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/maxime1992"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fdev-logo.png" title="Dev" alt="Dev"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fgithub-logo.png" title="Github" alt="Github"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://twitter.com/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Ftwitter-logo.png" title="Twitter" alt="Twitter"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.reddit.com/user/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Freddit-logo.png" title="Reddit" alt="Reddit"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.linkedin.com/in/maximerobert1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Flinkedin-logo.png" title="Linkedin" alt="Linkedin"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://stackoverflow.com/users/2398593/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fstackoverflow-logo.png" title="Stackoverflow" alt="Stackoverflow"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h1&gt;
  
  
  You may also enjoy reading
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;a href="https://dev.to/maxime1992/the-holy-grail-of-note-taking-private-data-efficient-methodology-and-p2p-encrypted-sync-across-all-your-devices-1ih3"&gt;&lt;img src="https://media.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%2Fpx6843z2bjenam0jqgkg.png" alt="'Cover'"&gt;&lt;/a&gt;&lt;/th&gt;
&lt;th&gt;&lt;a href="https://dev.to/maxime1992/how-to-detect-unnecessary-renderings-of-dom-elements-in-your-web-app-to-improve-performances-13jd"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fdetect-unnecessary-renderings-of-dom-elements%2Fassets%2Fcover.png" alt="'Cover'"&gt;&lt;/a&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/maxime1992/the-holy-grail-of-note-taking-private-data-efficient-methodology-and-p2p-encrypted-sync-across-all-your-devices-1ih3"&gt;The holy grail of note-taking: Private data, efficient methodology and P2P encrypted sync across all your devices&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/maxime1992/how-to-detect-unnecessary-renderings-of-dom-elements-in-your-web-app-to-improve-performances-13jd"&gt;How to detect unnecessary renderings of DOM elements in your web app to improve performance&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>docker</category>
      <category>opensource</category>
      <category>selfhosting</category>
      <category>privacy</category>
    </item>
    <item>
      <title>How to detect unnecessary renderings of DOM elements in your web app to improve performance</title>
      <dc:creator>Maxime</dc:creator>
      <pubDate>Sat, 03 Jun 2023 23:41:49 +0000</pubDate>
      <link>https://dev.to/maxime1992/how-to-detect-unnecessary-renderings-of-dom-elements-in-your-web-app-to-improve-performances-13jd</link>
      <guid>https://dev.to/maxime1992/how-to-detect-unnecessary-renderings-of-dom-elements-in-your-web-app-to-improve-performances-13jd</guid>
      <description>&lt;p&gt;&lt;em&gt;The code examples I'm giving is using Angular but it's only a very low amount of code to illustrate the main issue and that issue can happen in any Javascript application that interacts with the DOM.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Intro
&lt;/h1&gt;

&lt;p&gt;Unnecessary repaints in a web app can have a critical impact on performance.&lt;/p&gt;

&lt;p&gt;While you may be developing it on a high end device, you may not even notice. But a user running it on a lower end device may definitely.&lt;/p&gt;

&lt;p&gt;One question remains: &lt;strong&gt;How to be aware of unnecessary repaints?&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Illustrate the main issue
&lt;/h1&gt;

&lt;p&gt;Before we dig into this, let me illustrate the original issue.&lt;/p&gt;

&lt;p&gt;Here's a very basic app that simulates the fetching of a users array and display each of them, line by line, in a list.&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/stackblitz-starters-udsxgy?embed=1&amp;amp;file=src%2Fapp%2Fapp.component.html" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fdetect-unnecessary-renderings-of-dom-elements%2Fassets%2Ffetching-users-list-nothing-too-fancy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fdetect-unnecessary-renderings-of-dom-elements%2Fassets%2Ffetching-users-list-nothing-too-fancy.png" title="Fetching users list, nothing too fancy" alt="Fetching users list, nothing too fancy"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What happens if you click on the "Fetch people" button several times? Apprently nothing. Our list is still the same, nothing is removed, added... And yet...&lt;/p&gt;

&lt;h1&gt;
  
  
  Finding unnecessary repaints
&lt;/h1&gt;

&lt;p&gt;When we click on the button to fetch people, in this example we emulate a network call'd return an array of people. It'd just happen to be the same result. But behind the scenes, we'd receive a brand new reference for the array... And all the objects in it! Therefore Angular assumes it has to repaint the whole list.&lt;/p&gt;

&lt;p&gt;But how to detect this in our entire app? Do we have to be mentally aware of everything that could trigger our framework to repaint unnecessary bits of the app?&lt;/p&gt;

&lt;p&gt;There's a very straightforward way to find out, you just have to be aware of a feature in the chrome devtool that is a little bit hidden: &lt;strong&gt;Paint flashing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It can be found in: Dev tools &amp;gt; Hamburger menu &amp;gt; More tools &amp;gt; Rendering &amp;gt; Paint flashing.&lt;/p&gt;

&lt;p&gt;Here's where to find it and a demo of what happens with this feature on:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fdetect-unnecessary-renderings-of-dom-elements%2Fassets%2Fpaint-flashing-with-issue.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fdetect-unnecessary-renderings-of-dom-elements%2Fassets%2Fpaint-flashing-with-issue.gif" title="Fetching the users list triger a repaint for the whole list" alt="Fetching the users list triger a repaint for the whole list"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice how whenever we fetch the users list, the whole list flashes green? It's the dev tools showing us which DOM elements have been repainted. And if we've got the same list, surely it shouldn't repaint!&lt;/p&gt;

&lt;h1&gt;
  
  
  Fixing the issue
&lt;/h1&gt;

&lt;p&gt;This will need some investigation and there's unfortunately no silver bullet I can give you here. In this particular case, we can tell Angular to track items by a unique ID to it's aware whether it can reuse an element or not. This blog post isn't about Angular so I won't dwell on the fix but if we use a &lt;code&gt;trackBy&lt;/code&gt; on our &lt;code&gt;ngFor&lt;/code&gt; like this &lt;code&gt;*ngFor="let person of people; trackBy: trackById"&lt;/code&gt; and define the &lt;code&gt;trackById&lt;/code&gt; function as&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;public&lt;/span&gt; &lt;span class="nf"&gt;trackById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;Then Angular will be able to optimize the rendering.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fdetect-unnecessary-renderings-of-dom-elements%2Fassets%2Fpaint-flashing-without-issue.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fdetect-unnecessary-renderings-of-dom-elements%2Fassets%2Fpaint-flashing-without-issue.gif" title="Fetching the users list does not triger a repaint for the whole list" alt="Fetching the users list does not triger a repaint for the whole list"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice here that the first time we fetch the list, of course it has to be rendered once. So it flashes green. But whenever I try to fetch it again, only the button flashes (as I'm clicking onto it and it animates).&lt;/p&gt;

&lt;p&gt;From there, we know we've optimized our app to avoid unnecessary renderings ✅.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;This method will help you find out items that are repainted while they shouldn't, in no time. Of course, the illustration I've made isn't problematic because it's a demo app, but in real life you render hundreds or thousands of DOM elements at an intense pace to reflect fast changes in the data, it could become problematic.&lt;/p&gt;

&lt;p&gt;I hope you enjoyed this article. If you're interested in more tips about Angular, RxJS, open source, self hosting, data privacy, and others, feel free to hit the &lt;strong&gt;follow button&lt;/strong&gt; for more. Thanks for reading!&lt;/p&gt;

&lt;h1&gt;
  
  
  Found a typo?
&lt;/h1&gt;

&lt;p&gt;If you've found a typo, a sentence that could be improved or anything else that should be updated on this blog post, you can access it through a git repository and make a pull request. Instead of posting a comment, please go directly to &lt;a href="https://github.com/maxime1992/my-dev.to"&gt;https://github.com/maxime1992/my-dev.to&lt;/a&gt; and open a new pull request with your changes. If you're interested how I manage my dev.to posts through git and CI, &lt;a href="https://dev.to/maxime1992/manage-your-dev-to-blog-posts-from-a-git-repo-and-use-continuous-deployment-to-auto-publish-update-them-143j"&gt;read more here&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Follow me
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/maxime1992"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fdev-logo.png" title="Dev" alt="Dev"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fgithub-logo.png" title="Github" alt="Github"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://twitter.com/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Ftwitter-logo.png" title="Twitter" alt="Twitter"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.reddit.com/user/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Freddit-logo.png" title="Reddit" alt="Reddit"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.linkedin.com/in/maximerobert1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Flinkedin-logo.png" title="Linkedin" alt="Linkedin"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://stackoverflow.com/users/2398593/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fstackoverflow-logo.png" title="Stackoverflow" alt="Stackoverflow"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h1&gt;
  
  
  You may also enjoy reading
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;a href="https://dev.to/maxime1992/brute-forcing-an-encrypted-message-from-enigma-using-the-web-worker-api-166b"&gt;&lt;img src="https://media.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%2Fk0xd59voqoexuzge4uws.png" alt="'Cover'"&gt;&lt;/a&gt;&lt;/th&gt;
&lt;th&gt;&lt;a href="https://dev.to/maxime1992/next-level-data-privacy-with-easy-free-and-secure-self-hosting-at-home-2c84"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fnext-level-data-privacy%2Fpart-1-next-level-data-privacy-with-easy-free-and-secure-self-hosting-at-home%2Fassets%2Fcover.png%3FbustCache%3D2" alt="'Cover'"&gt;&lt;/a&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/maxime1992/brute-forcing-an-encrypted-message-from-enigma-using-the-web-worker-api-166b"&gt;Brute-forcing an encrypted message from Enigma using the web worker API&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/maxime1992/next-level-data-privacy-with-easy-free-and-secure-self-hosting-at-home-2c84"&gt;Next level data privacy with easy, free and secure self hosting at home&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>webdev</category>
      <category>html</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Next level data privacy with easy, free and secure self hosting at home</title>
      <dc:creator>Maxime</dc:creator>
      <pubDate>Sat, 03 Jun 2023 00:44:44 +0000</pubDate>
      <link>https://dev.to/maxime1992/next-level-data-privacy-with-easy-free-and-secure-self-hosting-at-home-2c84</link>
      <guid>https://dev.to/maxime1992/next-level-data-privacy-with-easy-free-and-secure-self-hosting-at-home-2c84</guid>
      <description>&lt;p&gt;Hello 👋!&lt;/p&gt;

&lt;p&gt;Yes you read this well. &lt;strong&gt;Self hosting made: Easy. Free. Secure&lt;/strong&gt;.&lt;br&gt;
With a machine running at your house, not in the cloud. Even if you've never done that before.&lt;/p&gt;

&lt;p&gt;That said, if you prefer to apply all this on a server you rent to some provider, it'll work the exact same way. You'll just skip the chapter on the router configuration.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Let's clarify from the start what I mean by:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&lt;strong&gt;Easy&lt;/strong&gt;: The first setup isn't necessarily easy (nor complicated). But it takes a bit of time. We'll go through it all together. Once it's done, adding new apps will take a few seconds or minutes based on how complex the docker strategy is (integrated database or separate container to run Postgres for example)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;strong&gt;Free&lt;/strong&gt;: You will not have to pay monthly or yearly bills for a server you rent online and a domain name because I'll explain how to setup everything locally using your desktop, laptop or a spare computer that you can keep open. You'd still have to pay for a machine if you don't have a spare one, as well as electricity to run it. That said, as a metric for this, my server has consumed ~60kwh in 6 months which makes it ~10kwh/month or ~2€/month.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Intro
&lt;/h1&gt;

&lt;p&gt;Have you ever wished to self host one of the +1000 brilliant open source project listed in &lt;a href="https://github.com/awesome-selfhosted/awesome-selfhosted" rel="noopener noreferrer"&gt;awesome-self&lt;/a&gt; hosted? A project of your own? A school project? Or anything else that is web based? Sky &lt;em&gt;(or the RAM of your server)&lt;/em&gt; is the limit!&lt;/p&gt;

&lt;p&gt;If you're afraid of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🕵️‍♂️ Online solutions that have no respect for your &lt;strong&gt;privacy&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;💸 Online hosting solutions where you're 100% in control of the server and apps, but it can be &lt;strong&gt;expensive&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🤷‍♂️ How to configure NGINX as it seems too &lt;strong&gt;complicated&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🛡️ Expose your services &lt;strong&gt;safely&lt;/strong&gt; on the internet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fear no more! In this blog post we'll be starting from scratch, all the way up to have a local stack safely accessible from the outside of our own network.&lt;/p&gt;

&lt;h1&gt;
  
  
  What we will achieve
&lt;/h1&gt;

&lt;p&gt;Here's the high level breakdown of what we'll do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install and use &lt;strong&gt;&lt;a href="https://www.docker.com" rel="noopener noreferrer"&gt;Docker&lt;/a&gt;&lt;/strong&gt; + &lt;strong&gt;&lt;a href="https://docs.docker.com/compose" rel="noopener noreferrer"&gt;Docker Compose&lt;/a&gt;&lt;/strong&gt; in order to have &lt;strong&gt;self contained applications&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;&lt;a href="https://www.duckdns.org" rel="noopener noreferrer"&gt;DuckDNS&lt;/a&gt;&lt;/strong&gt; to create a &lt;strong&gt;free domain name&lt;/strong&gt; that we can point to our public IP to have access to our apps from outside our home network &lt;em&gt;(note, you could skip this step and use your own domain name of course if you prefer to)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;&lt;a href="https://docs.linuxserver.io/general/swag" rel="noopener noreferrer"&gt;SWAG&lt;/a&gt;&lt;/strong&gt; to manage our NGINX server, SSL certificates and &lt;code&gt;fail2ban&lt;/code&gt; to ban people trying to brute force our services&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;&lt;a href="https://www.authelia.com" rel="noopener noreferrer"&gt;Authelia&lt;/a&gt;&lt;/strong&gt; &lt;em&gt;(combined to our NGINX in SWAG)&lt;/em&gt; to add a double authentication layer in front of all our services&lt;/li&gt;
&lt;li&gt;Discuss about how to open up the ports on a &lt;strong&gt;router&lt;/strong&gt; to be able to have access to your apps from the internet&lt;/li&gt;
&lt;li&gt;Access the default monitoring dashboard of SWAG from internet, behind our double authentication layer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a bonus and real life demo I'll soon write another &lt;strong&gt;blog post for this serie&lt;/strong&gt;, where we'll add 3 brilliant applications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/paperless-ngx/paperless-ngx" rel="noopener noreferrer"&gt;Paperless-ngx&lt;/a&gt;&lt;/strong&gt; to manage all your digital documents&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/photoprism/photoprism" rel="noopener noreferrer"&gt;Photoprism&lt;/a&gt;&lt;/strong&gt; to manage all your pictures and videos&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/kopia/kopia" rel="noopener noreferrer"&gt;Kopia&lt;/a&gt;&lt;/strong&gt; to backup all your data from all the containers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At the end of this blog post, you will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Be able to have all this stack up and running&lt;/li&gt;
&lt;li&gt;Be in a position to add any other web app easily&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's get started! 🔥&lt;br&gt;
Hang tight for the initial setup. Things will get way easier once this is done.&lt;/p&gt;




&lt;h1&gt;
  
  
  Architecture overview
&lt;/h1&gt;

&lt;p&gt;As an image is often worth a thousands words, here's the high level overview of what we'll be setting up:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fnext-level-data-privacy%2Fpart-1-next-level-data-privacy-with-easy-free-and-secure-self-hosting-at-home%2Fassets%2Fglobal-architecture.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fnext-level-data-privacy%2Fpart-1-next-level-data-privacy-with-easy-free-and-secure-self-hosting-at-home%2Fassets%2Fglobal-architecture.png" title="Global architecture" alt="Global architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note that we'll add the 3 apps at the bottom only in the next post of the series.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  DuckDNS setup
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;As mentioned previously, this part is optional and if you prefer to use your domain name instead you can.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Head over &lt;a href="https://www.duckdns.org" rel="noopener noreferrer"&gt;DuckDNS&lt;/a&gt; website and log in with the provider of your choice. You'll be setup in a matter of seconds. You should land on this page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fnext-level-data-privacy%2Fpart-1-next-level-data-privacy-with-easy-free-and-secure-self-hosting-at-home%2Fassets%2Fanonymized-duck-dns-page-example.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fnext-level-data-privacy%2Fpart-1-next-level-data-privacy-with-easy-free-and-secure-self-hosting-at-home%2Fassets%2Fanonymized-duck-dns-page-example.png" title="Anonymized DuckDNS page example" alt="Anonymized DuckDNS page example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that the token displayed in the middle of the page must be kept secret, never share it. We'll get back to it soon.&lt;/p&gt;

&lt;p&gt;In the domain input, type the domain name you wish to have pointing to your local setup. This will be the base of the public URL to access all your service. Something like &lt;code&gt;https://yourdomain.duckdns.org&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You will only need one as we'll be using sub domains so don't name it for a specific app. For example, with the 3 apps we'll be setting up in the next post, we'll end up with the following URLs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;https://photoprism.yourdomain.duckdns.org&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://paperless.yourdomain.duckdns.org&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://kopia.yourdomain.duckdns.org&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;From this point, all the commands we run should be run on the machine you decide to use as the server. If you only want to try out this whole stack without having a server, you can definitely give it a go from your current computer as well and migrate the setup to a server if you wish later on. I am running on Ubuntu so all the command will be Ubuntu based. That said it should be quite trivial to change the OS specific commands to match yours.&lt;/p&gt;




&lt;h1&gt;
  
  
  Docker and Docker Compose setup
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;I'll assume we start from scratch here. If you have Docker and Docker Compose installed already, you can skip this chapter.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker
&lt;/h2&gt;

&lt;p&gt;Run the following:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update &lt;span class="nt"&gt;-y&lt;/span&gt;

&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     ca-certificates &lt;span class="se"&gt;\&lt;/span&gt;
     curl &lt;span class="se"&gt;\&lt;/span&gt;
     gnupg &lt;span class="se"&gt;\&lt;/span&gt;
     lsb-release

curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://download.docker.com/linux/ubuntu/gpg | &lt;span class="nb"&gt;sudo &lt;/span&gt;gpg &lt;span class="nt"&gt;--dearmor&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /usr/share/keyrings/docker-archive-keyring.gpg

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"deb [arch=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;dpkg &lt;span class="nt"&gt;--print-architecture&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;lsb_release &lt;span class="nt"&gt;-cs&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; stable"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/apt/sources.list.d/docker.list &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null

&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update &lt;span class="nt"&gt;-y&lt;/span&gt;

&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; docker-ce docker-ce-cli containerd.io


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Docker Compose
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nb"&gt;cd&lt;/span&gt; ~
&lt;span class="nb"&gt;mkdir&lt;/span&gt; .docker
&lt;span class="nv"&gt;DOCKER_CONFIG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DOCKER_CONFIG&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="p"&gt;/.docker&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nv"&gt;$DOCKER_CONFIG&lt;/span&gt;/cli-plugins
curl &lt;span class="nt"&gt;-SL&lt;/span&gt; https://github.com/docker/compose/releases/download/v2.2.3/docker-compose-linux-x86_64 &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;$DOCKER_CONFIG&lt;/span&gt;/cli-plugins/docker-compose
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x &lt;span class="nv"&gt;$DOCKER_CONFIG&lt;/span&gt;/cli-plugins/docker-compose


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let's make sure we can run Docker without being an admin:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Replace &lt;code&gt;XXXXXXXXXX&lt;/code&gt; by your user name.&lt;/em&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;-G&lt;/span&gt; docker XXXXXXXXXX
newgrp docker


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h1&gt;
  
  
  SWAG setup
&lt;/h1&gt;

&lt;p&gt;If you wish to dig more into SWAG setup, here's the &lt;em&gt;&lt;a href="https://docs.linuxserver.io/general/swag" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;SWAG is no exception to the rule and it'll be ran as a Docker container. We will now create our &lt;code&gt;docker-compose.yaml&lt;/code&gt; file that'll let us define all the containers we want to run.&lt;/p&gt;

&lt;p&gt;I'd recommend to create a new folder so that all the data from our several containers will be hosted in the same folder, making our lives easier for when we look into Kopia and the backup system. In my case, I've defined it at &lt;code&gt;~/opt&lt;/code&gt; which is a folder I've created myself. If you point to a different folder, make sure to update the paths accordingly in the docker compose file.&lt;/p&gt;

&lt;p&gt;Create a file called &lt;code&gt;docker-compose.yaml&lt;/code&gt; and paste the following in it:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.0'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;swag&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;lscr.io/linuxserver/swag:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;swag&lt;/span&gt;
    &lt;span class="na"&gt;cap_add&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NET_ADMIN&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;common.env&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;swag.env&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;URL=yourdomain.duckdns.org&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;VALIDATION=duckdns&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SUBDOMAINS=wildcard&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DOCKER_MODS=linuxserver/mods:swag-dashboard&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;~/opt/swag/config:/config&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;443:443&lt;/span&gt;
      &lt;span class="c1"&gt;# https://github.com/linuxserver/docker-mods/tree/swag-dashboard#internal-access-using-server-ip81&lt;/span&gt;
      &lt;span class="c1"&gt;# open port 81 for the dashboard&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;81:81&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Make sure to update the &lt;code&gt;URL&lt;/code&gt; in the &lt;code&gt;environment&lt;/code&gt;. It's the one you've defined in your DuckDNS earlier&lt;/li&gt;
&lt;li&gt;Don't forget to change the path for the volume if you've decided to put your files somewhere else than &lt;code&gt;~/opt&lt;/code&gt;. You could just put &lt;code&gt;./swag/config:/config&lt;/code&gt; but then you'd need to make sure to always launch the docker compose from that directory&lt;/li&gt;
&lt;li&gt;Feel free to change the ports &lt;code&gt;443&lt;/code&gt; and &lt;code&gt;81&lt;/code&gt; if they're already taken to anything you'd like. Remember to only edit the port on the &lt;strong&gt;left side&lt;/strong&gt; of the &lt;code&gt;:&lt;/code&gt; as the one on the right is the internal binding for the container&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then create 2 files at the same level:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;common.env&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="s"&gt;PUID=1000&lt;/span&gt;
&lt;span class="s"&gt;PGID=1000&lt;/span&gt;
&lt;span class="s"&gt;TZ=Europe/Paris&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To find out your own &lt;code&gt;PUID&lt;/code&gt; and &lt;code&gt;PGID&lt;/code&gt;, type in your console &lt;code&gt;id&lt;/code&gt; and you'll something like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;id
&lt;/span&gt;&lt;span class="nv"&gt;uid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1000&lt;span class="o"&gt;(&lt;/span&gt;maxime&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;gid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1000&lt;span class="o"&gt;(&lt;/span&gt;maxime&lt;span class="o"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Use these 2 values.&lt;/p&gt;

&lt;p&gt;As for the timezone &lt;code&gt;TZ&lt;/code&gt;, you can find it &lt;a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones" rel="noopener noreferrer"&gt;here on Wikipedia&lt;/a&gt;, in the &lt;code&gt;TZ Identifier&lt;/code&gt; column.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;swag.env&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="s"&gt;DUCKDNSTOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Of course, replace it with the token displayed on your DuckDNS page.&lt;/p&gt;

&lt;p&gt;Time to start our first container!&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;It should pull the container if you don't have it already and you should see a message like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

✔ Container swag Started


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To make double sure everything went well, we can also check the logs of the container:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;docker logs swag


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You'll see a bunch of logs but the most important line being the last one: &lt;code&gt;Server ready&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Notice as well that a &lt;code&gt;swag&lt;/code&gt; folder was created!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fnext-level-data-privacy%2Fpart-1-next-level-data-privacy-with-easy-free-and-secure-self-hosting-at-home%2Fassets%2Fswag-folder-created.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fnext-level-data-privacy%2Fpart-1-next-level-data-privacy-with-easy-free-and-secure-self-hosting-at-home%2Fassets%2Fswag-folder-created.png" title="SWAG folder created" alt="SWAG folder created"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Without further ado, let's access the integrated SWAG dashboard... From the internet by configuring our router.&lt;/p&gt;

&lt;h1&gt;
  
  
  Router configuration
&lt;/h1&gt;

&lt;p&gt;Whether you have your own router or the default "box" provided your internet provider, you will have access to all the settings. That said, each router has it's own UI and I cannot cover all of these. So you will have to search a little bit in your router configuration to find out where you need to access the settings I'll mention. If you don't find them, Google is your friend for this part!&lt;/p&gt;

&lt;p&gt;Search in the settings for &lt;strong&gt;DHCP&lt;/strong&gt;. This will let us attribute a local static IP to our computer running SWAG. Create a new rule. It'll ask you what's the device you wish to configure. If it's a little bit smart, it'll list the devices and their IP and you'll be able to select from there. If not, you'll have to enter the current IP and the MAC address of the computer.&lt;/p&gt;

&lt;p&gt;Once done, search for &lt;strong&gt;&lt;code&gt;NAT &amp;amp; PAT&lt;/code&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;code&gt;NAT forwarding&lt;/code&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;code&gt;Port forwarding&lt;/code&gt;&lt;/strong&gt;. This will let us bind a port of our &lt;strong&gt;public&lt;/strong&gt; IP and redirect to a given &lt;strong&gt;local&lt;/strong&gt; IP + port. In our case, we will need to have only 1 rule here as we'll be using sub domains to have multiple apps under the same base domain. Create a new rule, for all the protocols &lt;em&gt;(&lt;code&gt;TCP&lt;/code&gt;+&lt;code&gt;UDP&lt;/code&gt;)&lt;/em&gt;. Define the external port to be &lt;code&gt;443&lt;/code&gt;. You'll then need to point to a given internal IP and a given port. Specify your computer IP as for the port it'll be &lt;code&gt;443&lt;/code&gt; as well &lt;em&gt;(or if you changed the &lt;code&gt;443&lt;/code&gt; port of our &lt;code&gt;docker-compose.yaml&lt;/code&gt; file, put the one you wrote here)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;As we've passed the environment variable &lt;code&gt;DOCKER_MODS=linuxserver/mods:swag-dashboard&lt;/code&gt;, SWAG gives us access to an admin dashboard. Therefore, if everything went well with the router configuration, we should now have access to it from internet! Try to access your domain name with the subdomain &lt;code&gt;dashboard&lt;/code&gt;: &lt;a href="https://dashboard.yourdomain.duckdns.org" rel="noopener noreferrer"&gt;https://dashboard.yourdomain.duckdns.org&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should see this 🎉:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fnext-level-data-privacy%2Fpart-1-next-level-data-privacy-with-easy-free-and-secure-self-hosting-at-home%2Fassets%2Fswag-dashboard.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fnext-level-data-privacy%2Fpart-1-next-level-data-privacy-with-easy-free-and-secure-self-hosting-at-home%2Fassets%2Fswag-dashboard.png" title="SWAG Dashboard" alt="SWAG Dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feels awesome right?!&lt;/p&gt;

&lt;p&gt;You may think though that it's not really a good idea to expose an admin dashboard publicly on the internet. And you'd be right! Let's jump into the next chapter to setup some additional security.&lt;/p&gt;

&lt;h1&gt;
  
  
  Authelia and double authentication
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://www.authelia.com" rel="noopener noreferrer"&gt;Authelia&lt;/a&gt; is a fantastic piece of open source software which:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Authelia is an open-source authentication and authorization server and portal fulfilling the identity and access management (IAM) role of information security in providing multi-factor authentication and single sign-on (SSO) for your applications via a web portal. It acts as a companion for common reverse&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Essentially, it'll let you put an (extra) &lt;strong&gt;authentication layer 🔐 in front of any deployed services 🛡️&lt;/strong&gt;. You can setup 2 factor authentication &lt;em&gt;(&lt;strong&gt;2FA&lt;/strong&gt;)&lt;/em&gt; for reinforced security as well.&lt;br&gt;
🍒 on the cake, you can even define per user access to your apps. To be clear, if an app you deploy has already a login/password access, you can decide to expose it on the internet. But if the security of that app is weak, you may be in troubles. Authelia lets you plug an extra layer with 2FA in front. You'll only have to log once to Authelia and then still log independently to any app that has its own id/password login. Let's crack on!&lt;/p&gt;

&lt;p&gt;Add the following service to our &lt;code&gt;docker-compose.yaml&lt;/code&gt; file:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# to add a user, add directly to `authelia/users_database.yml`&lt;/span&gt;
&lt;span class="c1"&gt;# then get the encrypted password with&lt;/span&gt;
&lt;span class="c1"&gt;# docker run --rm ghcr.io/authelia/authelia:4.34.6 authelia hash-password yourpassword&lt;/span&gt;
&lt;span class="c1"&gt;# https://www.linuxserver.io/blog/2020-08-26-setting-up-authelia#users_database-yml&lt;/span&gt;
&lt;span class="na"&gt;authelia&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/authelia/authelia:4.34.6&lt;/span&gt;
  &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;authelia&lt;/span&gt;
  &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;common.env&lt;/span&gt;
  &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./authelia:/config&lt;/span&gt;
  &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Run &lt;code&gt;docker compose up -d&lt;/code&gt; so that the Authelia container gets started as well.&lt;/p&gt;

&lt;p&gt;You'll see that a new folder &lt;code&gt;authelia&lt;/code&gt; will be created with 1 file. But if we look into the logs, we can see that something needs to be fixed before we can actually use Authelia:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;docker logs authelia


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Gives us:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

time="2023-06-02T22:34:09+02:00" level=error msg="Configuration: storage: option 'encryption_key' must is required"
time="2023-06-02T22:34:09+02:00" level=fatal msg="Can't continue due to the errors loading the configuration"


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is normal, it's because it's the first time the app is launched and for security reason, we need to change some default values in the config. Let's edit &lt;code&gt;authelia/configuration.yml&lt;/code&gt; and replace it with the following:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# https://www.linuxserver.io/blog/2020-08-26-setting-up-authelia&lt;/span&gt;

&lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.0.0.0&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;9091&lt;/span&gt;
  &lt;span class="na"&gt;read_buffer_size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4096&lt;/span&gt;
  &lt;span class="na"&gt;write_buffer_size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4096&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;authelia'&lt;/span&gt;
&lt;span class="na"&gt;log&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;info&lt;/span&gt;
  &lt;span class="na"&gt;file_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/config/logs/authelia.log&lt;/span&gt;
&lt;span class="na"&gt;jwt_secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TODO_SOME_RANDOM_SECRET_HERE&lt;/span&gt;
&lt;span class="na"&gt;default_redirection_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://authelia.yourdomain.duckdns.org&lt;/span&gt;
&lt;span class="na"&gt;totp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;issuer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;authelia.yourdomain.duckdns.org&lt;/span&gt;
&lt;span class="na"&gt;authentication_backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;disable_reset_password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/config/users_database.yml&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;algorithm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argon2id&lt;/span&gt;
      &lt;span class="na"&gt;iterations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;key_length&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;32&lt;/span&gt;
      &lt;span class="na"&gt;salt_length&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt;
      &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;512&lt;/span&gt;
      &lt;span class="na"&gt;parallelism&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt;
&lt;span class="na"&gt;access_control&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;default_policy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deny&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;yourdomain.duckdns.org&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*.yourdomain.duckdns.org'&lt;/span&gt;
      &lt;span class="na"&gt;policy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;two_factor&lt;/span&gt;
      &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user:TODO_YOUR_AUTHELIA_USER_NAME_HERE'&lt;/span&gt;
&lt;span class="na"&gt;session&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;authelia_session&lt;/span&gt;
  &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TODO_SOME_OTHER_RANDOM_SECRET_HERE&lt;/span&gt;
  &lt;span class="na"&gt;expiration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1h&lt;/span&gt;
  &lt;span class="na"&gt;inactivity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5m&lt;/span&gt;
  &lt;span class="na"&gt;remember_me_duration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1M&lt;/span&gt;
  &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yourdomain.duckdns.org&lt;/span&gt;
&lt;span class="na"&gt;regulation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;max_retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;find_time&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2m&lt;/span&gt;
  &lt;span class="na"&gt;ban_time&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5m&lt;/span&gt;
&lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;encryption_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TODO_SOME_RANDOM_ENCRYPTION_KEY_HERE&lt;/span&gt;
  &lt;span class="na"&gt;local&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/config/db.sqlite3&lt;/span&gt;
&lt;span class="na"&gt;notifier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;disable_startup_check&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;filesystem&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/config/notification.txt&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Update all the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TODO_SOME_RANDOM_SECRET_HERE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TODO_YOUR_AUTHELIA_USER_NAME_HERE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TODO_SOME_OTHER_RANDOM_SECRET_HERE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;yourdomain&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TODO_SOME_RANDOM_ENCRYPTION_KEY_HERE&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For secrets and encryption keys, generate long and random strings❗&lt;/p&gt;

&lt;p&gt;To avoid an error with the container, create an empty file: &lt;code&gt;authelia/logs/authelia.log&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then do &lt;code&gt;docker compose down &amp;amp;&amp;amp; docker compose up -d&lt;/code&gt;.&lt;br&gt;
You should see a bunch of new files created in the &lt;code&gt;authelia&lt;/code&gt; folder and &lt;code&gt;docker logs authelia&lt;/code&gt; shall show a few &lt;code&gt;level=info&lt;/code&gt; but no errors ✅.&lt;/p&gt;

&lt;p&gt;Last but not least, we need to add a user otherwise it'll be hard to log in!&lt;/p&gt;

&lt;p&gt;Launch the following command to encrypt your chosen password for Authelia:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; authelia/authelia:latest authelia hash-password yourpassword


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Change of course &lt;code&gt;yourpassword&lt;/code&gt; to a very strong password as this will be your entry point to Authelia. Generating it using a password manager is a good idea.&lt;/p&gt;

&lt;p&gt;It'll then print the encrypted password to the console. Keep it there for now and head over &lt;code&gt;authelia/users_database.yml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Edit this file with the following content:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;users&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;your-user-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;displayname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;your-user-name'&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Put&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;hashed&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;generated&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;here&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;starting&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;with&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$argon2'&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Don't forget to change the username &lt;em&gt;(twice)&lt;/em&gt; to whatever you want and update the password with the one we just generated.&lt;/p&gt;

&lt;p&gt;Now that Authelia is configured, let's expose it through a given subdomain. For this, thanks to all the templates that SWAG has, it's really easy for most apps that we want to add!&lt;/p&gt;

&lt;p&gt;In this case, rename &lt;code&gt;swag/config/nginx/proxy-confs/authelia.subdomain.conf.sample&lt;/code&gt; to &lt;code&gt;authelia.subdomain.conf&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Restart both services with &lt;code&gt;docker compose down &amp;amp;&amp;amp; docker compose up -d&lt;/code&gt; then go to &lt;a href="https://authelia.yourdomain.duckdns.org" rel="noopener noreferrer"&gt;https://authelia.yourdomain.duckdns.org&lt;/a&gt; and...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fnext-level-data-privacy%2Fpart-1-next-level-data-privacy-with-easy-free-and-secure-self-hosting-at-home%2Fassets%2Fauthelia-login-page.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fnext-level-data-privacy%2Fpart-1-next-level-data-privacy-with-easy-free-and-secure-self-hosting-at-home%2Fassets%2Fauthelia-login-page.png" title="Authelia login page" alt="Authelia login page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;VICTORY! 🎉&lt;/p&gt;

&lt;p&gt;If you try to log in, you'll get a message saying that you need to activate double authentication to access that resource. And it makes sense as we've specified this as the default in our config! Click on this button:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fnext-level-data-privacy%2Fpart-1-next-level-data-privacy-with-easy-free-and-secure-self-hosting-at-home%2Fassets%2Fauthelia-activate-double-authentication.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fnext-level-data-privacy%2Fpart-1-next-level-data-privacy-with-easy-free-and-secure-self-hosting-at-home%2Fassets%2Fauthelia-activate-double-authentication.png" title="Authelia activate double authentication" alt="Authelia activate double authentication"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll see a notification:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fnext-level-data-privacy%2Fpart-1-next-level-data-privacy-with-easy-free-and-secure-self-hosting-at-home%2Fassets%2Fauthelia-notification.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fnext-level-data-privacy%2Fpart-1-next-level-data-privacy-with-easy-free-and-secure-self-hosting-at-home%2Fassets%2Fauthelia-notification.png" title="Authelia notification" alt="Authelia notification"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's obviously not true because we haven't setup any email provider but Authelia has a clever trick and writes the content instead to &lt;code&gt;authelia/notification.txt&lt;/code&gt;. It'll look like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

Date: 2023-06-02 23:13:28.051443925 +0200 CEST m=+216.364147029
Recipient:
Subject: Register your mobile
Body:
This email has been sent to you in order to validate your identity.
If you did not initiate the process your credentials might have been compromised. You should reset your password and contact an administrator.

To setup your 2FA please visit the following URL: https://authelia.yourdomain.duckdns.org/one-time-password/register?token=some-random-token

Please contact an administrator if you did not initiate the process.


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Open up the link that's written and it'll show you a page with a QR code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fnext-level-data-privacy%2Fpart-1-next-level-data-privacy-with-easy-free-and-secure-self-hosting-at-home%2Fassets%2F2FA-QR-code.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fnext-level-data-privacy%2Fpart-1-next-level-data-privacy-with-easy-free-and-secure-self-hosting-at-home%2Fassets%2F2FA-QR-code.jpg" title="Authelia double authentification QR code" alt="Authelia double authentification QR code"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With your favourite 2FA authentication app, add it. For example you can use Google Authenticator.&lt;/p&gt;

&lt;p&gt;Brilliant! We've got Authelia and 2FA setup 🔥!&lt;/p&gt;

&lt;p&gt;But wait, our dashboard at &lt;a href="https://dashboard.yourdomain.duckdns.org" rel="noopener noreferrer"&gt;https://dashboard.yourdomain.duckdns.org&lt;/a&gt; is still not protected. Let's edit &lt;code&gt;swag/config/nginx/proxy-confs/dashboard.subdomain.conf&lt;/code&gt; but first: Note that, usually the &lt;code&gt;proxy-confs&lt;/code&gt; contains all the templates and for the apps you want to expose, you rename the file to remove the &lt;code&gt;.sample&lt;/code&gt;. In this case the dashboard template is here by default! &lt;/p&gt;

&lt;p&gt;Anyway, let's edit &lt;code&gt;dashboard.subdomain.conf&lt;/code&gt;. All you have to do for every new &lt;code&gt;.conf&lt;/code&gt; file that you use to expose a new service, is to check for the lines with a comment&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

# enable for Authelia


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;and uncomment the next line. &lt;strong&gt;Make sure you do that on all occurrences of the comment&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There should be at least:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;include /config/nginx/authelia-server.conf&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;include /config/nginx/authelia-location.conf&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this case, there are 5 lines to uncomment ⚠️!&lt;/p&gt;

&lt;p&gt;Now reload both services: &lt;code&gt;docker compose down &amp;amp;&amp;amp; docker compose up -d&lt;/code&gt; and head over your dashboard. It should not be accessible directly and you shall see the Authelia authentication page.&lt;/p&gt;

&lt;p&gt;If you get a 403 Forbidden error, you may need to adjust your allowed IP addresses in &lt;code&gt;dashboard.subdomain.conf&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Pro tip 💡:&lt;/strong&gt; Notice how the URL goes from &lt;code&gt;https://dashboard.yourdomain.duckdns.org&lt;/code&gt; to something like &lt;code&gt;https://dashboard.yourdomain.duckdns.org/authelia/?rd=https%3A%2F%2Fdashboard.yourdomain.duckdns.org%2F&lt;/code&gt; ?&lt;/p&gt;

&lt;p&gt;If you're using a password manager, assuming it's got regex support for URL detection, it's possible to separate your apps from the Authelia login which is very convenient instead of having a domain match that'd just always show you the Authelia entry as the domain doesn't change when Authelia login shows up. It's just the end of the URL.&lt;/p&gt;

&lt;p&gt;For your Authelia entry in your password manager, enter this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

https:\/\/([1-9-a-z-]*)\.yourdomain\.duckdns\.org\/authelia


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;For all your apps, enter this &lt;em&gt;(example with Paperless that we'll setup later)&lt;/em&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

^https\:\/\/paperless\.yourdomain\.duckdns\.org(?!(/authelia))


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This way, when your app doesn't open the Authelia page your password manager will only show 1 entry for the app, and when Authelia shows up it'll only show Authelia, not all of your apps entries.&lt;/p&gt;




&lt;p&gt;I believe &lt;strong&gt;data privacy is important&lt;/strong&gt; and being able to self host applications where you own your data is like a super power. In the next post of the series, I'll show how to setup 3 of my favourites &lt;strong&gt;open source apps to manage your documents, pictures and backup&lt;/strong&gt; all that safely.&lt;/p&gt;

&lt;p&gt;If you're interested in more articles about Angular, RxJS, open source, self hosting, data privacy, feel free to hit the &lt;strong&gt;follow button&lt;/strong&gt; for more. Thanks for reading!&lt;/p&gt;

&lt;h1&gt;
  
  
  Found a typo?
&lt;/h1&gt;

&lt;p&gt;If you've found a typo, a sentence that could be improved or anything else that should be updated on this blog post, you can access it through a git repository and make a pull request. Instead of posting a comment, please go directly to &lt;a href="https://github.com/maxime1992/my-dev.to"&gt;https://github.com/maxime1992/my-dev.to&lt;/a&gt; and open a new pull request with your changes. If you're interested how I manage my dev.to posts through git and CI, &lt;a href="https://dev.to/maxime1992/manage-your-dev-to-blog-posts-from-a-git-repo-and-use-continuous-deployment-to-auto-publish-update-them-143j"&gt;read more here&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Follow me
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/maxime1992"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fdev-logo.png" title="Dev" alt="Dev"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fgithub-logo.png" title="Github" alt="Github"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://twitter.com/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Ftwitter-logo.png" title="Twitter" alt="Twitter"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.reddit.com/user/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Freddit-logo.png" title="Reddit" alt="Reddit"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.linkedin.com/in/maximerobert1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Flinkedin-logo.png" title="Linkedin" alt="Linkedin"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://stackoverflow.com/users/2398593/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fstackoverflow-logo.png" title="Stackoverflow" alt="Stackoverflow"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h1&gt;
  
  
  You may also enjoy reading
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;a href="https://dev.to/maxime1992/the-holy-grail-of-note-taking-private-data-efficient-methodology-and-p2p-encrypted-sync-across-all-your-devices-1ih3"&gt;&lt;img src="https://media.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%2Fpx6843z2bjenam0jqgkg.png" alt="'Cover'"&gt;&lt;/a&gt;&lt;/th&gt;
&lt;th&gt;&lt;a href="https://dev.to/maxime1992/manage-your-dev-to-blog-posts-from-a-git-repo-and-use-continuous-deployment-to-auto-publish-update-them-143j"&gt;&lt;img src="https://media.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%2Fhgumnjxalqoi5rbl8i0t.png" alt="'Cover'"&gt;&lt;/a&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/maxime1992/the-holy-grail-of-note-taking-private-data-efficient-methodology-and-p2p-encrypted-sync-across-all-your-devices-1ih3"&gt;The holy grail of note-taking: Private data, efficient methodology and P2P encrypted sync across all your devices&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/maxime1992/manage-your-dev-to-blog-posts-from-a-git-repo-and-use-continuous-deployment-to-auto-publish-update-them-143j"&gt;Manage your dev.to blog posts from a GIT repo and use continuous deployment to auto publish/update them&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>docker</category>
      <category>opensource</category>
      <category>selfhosting</category>
      <category>privacy</category>
    </item>
    <item>
      <title>RxJS: Avoid an easy mistake when using startWith</title>
      <dc:creator>Maxime</dc:creator>
      <pubDate>Fri, 26 May 2023 04:45:00 +0000</pubDate>
      <link>https://dev.to/maxime1992/rxjs-avoid-an-easy-mistake-when-using-startwith-4ano</link>
      <guid>https://dev.to/maxime1992/rxjs-avoid-an-easy-mistake-when-using-startwith-4ano</guid>
      <description>&lt;p&gt;&lt;em&gt;As an example in this blog post, I'll use a really basic example with Angular to illustrate a real world example, but we'll focus on RxJS code, not Angular.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;startWith&lt;/code&gt; can be a really useful operator to make sure an observable emits a value straight away when it gets subscribed to. A good example is when we want to display a form value.&lt;/p&gt;

&lt;p&gt;In Angular, we have access to a form value through an observable like this: &lt;code&gt;formControl.valueChanges&lt;/code&gt;. Except that this observable &lt;strong&gt;only emits when the form value changes&lt;/strong&gt;. So if we subscribe to it, we won't get notified about the initial value.&lt;/p&gt;

&lt;p&gt;In order to have the current form value from the start, we can then do the following:&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;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;formValue$&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;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;valueChanges&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;startWith&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;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&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;We can see that so far, it works as expected and the initial &lt;code&gt;null&lt;/code&gt; value is the one displayed from the start.&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/stackblitz-starters-dqbjkq?embed=1&amp;amp;file=src%2Fapp%2Fapp.component.ts" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;That said, this can lead to sneaky issues.&lt;/p&gt;

&lt;p&gt;In the example above, try to change the form value to anything you like. You'll then see that the binding will display the new value live. Then, use the toggle button to hide the value and show it again. It'll display &lt;code&gt;null&lt;/code&gt; instead of the real/current form value.&lt;/p&gt;

&lt;p&gt;It can be a bit misleading at first why this happens. The reason is because of the &lt;code&gt;startWith&lt;/code&gt;. Despite the observable being cold by nature, the &lt;code&gt;startWith&lt;/code&gt; function is ran as soon as the class is instantiated. it's just a function, that isn't part of any callback. Initially, the form value is &lt;code&gt;null&lt;/code&gt; as we've set it like that. So the first time we subscribe to the observable, it's fine.&lt;br&gt;
Then we unsubscribe from it, in that case with the toggle + the &lt;code&gt;ngIf&lt;/code&gt; removing the entire node and the &lt;code&gt;async&lt;/code&gt; pipe unsubscribing for us. When we toggle the value again and the &lt;code&gt;ngIf&lt;/code&gt; becomes &lt;code&gt;true&lt;/code&gt;, we subscribe to the observable again, which as we mentioned had the &lt;code&gt;startWith&lt;/code&gt; function ran as soon as the class was created, so it's permanently set to be the initial form value: &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To go around this, here's what we can do instead:&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;public&lt;/span&gt; &lt;span class="nx"&gt;formValue$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;defer&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;of&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;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&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;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;valueChanges&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Using &lt;code&gt;concat&lt;/code&gt;, we say that we'll start our stream with one inner stream, and once it's complete, continue from another one.&lt;br&gt;
The first one is the key here. Using &lt;code&gt;defer&lt;/code&gt;, it'll run the defer callback whenever it gets subscribed to, instead of as soon as the class is instantiated with &lt;code&gt;startWith&lt;/code&gt;.&lt;br&gt;
It means that whenever we subscribe again to the observable, we'll get the form value &lt;strong&gt;at this time&lt;/strong&gt;, not at the time at which the class was instantiated.&lt;/p&gt;

&lt;p&gt;Here's the final code and a live demo:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/stackblitz-starters-w5kpjs?embed=1&amp;amp;file=src%2Fapp%2Fapp.component.ts" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Do note that this particular case was to have a somewhat realistic example, even though to display the current form value we'd just use &lt;code&gt;form.value&lt;/code&gt; and be done with it, without any observable or async pipe. This can be applied anywhere you're using a &lt;code&gt;startWith&lt;/code&gt;, &lt;code&gt;from&lt;/code&gt;, &lt;code&gt;of&lt;/code&gt; to get a value at the time of &lt;strong&gt;subscription&lt;/strong&gt;, not at the time at which the code is read. It can also be useful when a stream can be retried if there's an error.&lt;/p&gt;

&lt;p&gt;If you're interested in more articles about Angular, RxJS, open source, self hosting, data privacy, feel free to hit the follow button for more. Thanks for reading!&lt;/p&gt;

&lt;h1&gt;
  
  
  Found a typo?
&lt;/h1&gt;

&lt;p&gt;If you've found a typo, a sentence that could be improved or anything else that should be updated on this blog post, you can access it through a git repository and make a pull request. Instead of posting a comment, please go directly to &lt;a href="https://github.com/maxime1992/my-dev.to"&gt;https://github.com/maxime1992/my-dev.to&lt;/a&gt; and open a new pull request with your changes. If you're interested how I manage my dev.to posts through git and CI, &lt;a href="https://dev.to/maxime1992/manage-your-dev-to-blog-posts-from-a-git-repo-and-use-continuous-deployment-to-auto-publish-update-them-143j"&gt;read more here&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Follow me
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/maxime1992"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fdev-logo.png" title="Dev" alt="Dev"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fgithub-logo.png" title="Github" alt="Github"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://twitter.com/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Ftwitter-logo.png" title="Twitter" alt="Twitter"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.reddit.com/user/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Freddit-logo.png" title="Reddit" alt="Reddit"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.linkedin.com/in/maximerobert1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Flinkedin-logo.png" title="Linkedin" alt="Linkedin"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://stackoverflow.com/users/2398593/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fstackoverflow-logo.png" title="Stackoverflow" alt="Stackoverflow"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>rxjs</category>
      <category>angular</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Stackoverflow: Increase the chances to get answers to your questions</title>
      <dc:creator>Maxime</dc:creator>
      <pubDate>Wed, 17 May 2023 04:32:00 +0000</pubDate>
      <link>https://dev.to/maxime1992/stackoverflow-increase-the-chances-to-get-answers-to-your-questions-5gn9</link>
      <guid>https://dev.to/maxime1992/stackoverflow-increase-the-chances-to-get-answers-to-your-questions-5gn9</guid>
      <description>&lt;p&gt;I love Stackoverflow. Probably like a lot of other developers out here. It has helped me thousands of times and saved me countless hours of debugging.&lt;/p&gt;

&lt;p&gt;This has lead me to start contributing onto it as well, with the idea that I could give back to the community. I can't say I contribute regularly, but every once in a while I go through topics I like and see if there are some questions I can help on.&lt;/p&gt;

&lt;p&gt;While reading a lot of questions, I've noticed that a lot of people don't realise how much quicker they could get an answer by following simple rules. It'd give them a tremendously better views/replies ratio and hopefully let them get the answer they're looking for.&lt;/p&gt;

&lt;p&gt;In this blog post, I'm going to share a few tips that may sound obvious for some, but may really help others get answers in general. It's based on my personal feeling and experience using Stackoverflow. I believe most of the following could also apply for example when creating an issue on Github.&lt;/p&gt;

&lt;p&gt;The most important tip I'll start with, is also a global summary: &lt;strong&gt;Help yourself by making it easy for people to help you. You have to put yourself in the readers' shoes&lt;/strong&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Realise that people are reading questions and trying to help you out &lt;strong&gt;for free&lt;/strong&gt;. So if the question is poorly written, chances are, they'll just move on without trying to put much effort into answering. Assume that people will put as much energy answering, than you did when writing the question&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When sharing code, &lt;strong&gt;never copy paste entire blocks of code directly&lt;/strong&gt;. Isolate the code with the issue to the best of your knowledge. Then remove everything that isn't essential to your issue. For example if you're sharing some HTML, keep the overall structure, but remove tags, classes, comments that you know are not part of the issue. Even better, create a minimal reproduction highlighting the issue with only what's needed to repro. It takes time. It is not simple. But if you move that burden to the readers, it's unlikely they'll stick around&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When possible, &lt;strong&gt;create a live reproduction&lt;/strong&gt;. There are plenty of tools available out here for pretty much all the languages! When it comes to anything relating to Javascript/Typescript, my go to solution is &lt;a href="https://stackblitz.com"&gt;Stackblitz&lt;/a&gt;. Blazingly fast, can save projects, fork them, they're easy to share and there's no need to register an account to read or write code by default. Definitely recommend it. But if you're not using Javascript/Typescript, do check for equivalent in your language. Another example I've got in mind is Kotlin. You can use &lt;a href="https://play.kotlinlang.org"&gt;https://play.kotlinlang.org&lt;/a&gt; which lets you write, test and share your code easily as well. Find a way for people to play easily with the code you're sharing. It'll help them try solutions on their side and iterate quickly&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In conclusion, always ask yourself this question: "If someone who doesn't know anything about my business case was to read this question, would he understand and being able to debug it himself?".&lt;/p&gt;

</description>
      <category>stackoverflow</category>
      <category>tutorial</category>
      <category>developer</category>
      <category>community</category>
    </item>
    <item>
      <title>I had no idea Typescript generics were capable of doing that 🤯!</title>
      <dc:creator>Maxime</dc:creator>
      <pubDate>Sun, 10 Oct 2021 11:46:18 +0000</pubDate>
      <link>https://dev.to/maxime1992/5-years-working-on-a-daily-basis-with-typescript-and-i-had-no-idea-typescript-generics-were-capable-of-doing-that-21p6</link>
      <guid>https://dev.to/maxime1992/5-years-working-on-a-daily-basis-with-typescript-and-i-had-no-idea-typescript-generics-were-capable-of-doing-that-21p6</guid>
      <description>&lt;h1&gt;
  
  
  Context
&lt;/h1&gt;

&lt;p&gt;An API which we don't own can return an object containing the same property, with a suffix going from 1 to 3. Example:&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Order&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// some unique keys&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// some keys "grouped" by 3&lt;/span&gt;
  &lt;span class="nl"&gt;price1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;price2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;price3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;quantity1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;quantity2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;quantity3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;shippingDate1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;shippingDate2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;shippingDate3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;&lt;em&gt;Notes:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Why is that API not exposing &lt;code&gt;prices&lt;/code&gt;, &lt;code&gt;quantities&lt;/code&gt; and &lt;code&gt;shippingDates&lt;/code&gt; as arrays is beyond the point here. Let's just assume we consume an API that we cannot modify&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;This is just an example and the returned type could have way more properties like that, so we don't want to have to make the whole remapping manually&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  The challenge
&lt;/h1&gt;

&lt;p&gt;While the backend may expose it this way for a good reason, in our case, on the frontend side, we'd rather prefer to have a data structure matching our needs in a better way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1
&lt;/h2&gt;

&lt;p&gt;Build a &lt;code&gt;getGroupedValues&lt;/code&gt; function which will have&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input 1: An object&lt;/li&gt;
&lt;li&gt;Input 2: A key for one of the repeated properties. Example if we pass an &lt;code&gt;Order&lt;/code&gt; as first argument, we could pass as second argument either &lt;code&gt;price&lt;/code&gt;, &lt;code&gt;quantity&lt;/code&gt; or &lt;code&gt;shippingDate&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Output: An array of the type matching the union of all the keys for that common one. Example with &lt;code&gt;Order&lt;/code&gt;, if the second argument is &lt;code&gt;price&lt;/code&gt; we'd expect as an output &lt;code&gt;number[]&lt;/code&gt; but if it's &lt;code&gt;shippingDate&lt;/code&gt; we'd expect &lt;code&gt;string[]&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This could be a building block to do something like this:&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;OrderRemap&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;quantities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;shippingDates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// no need to know where it's coming from for the example&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;orderRemap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OrderRemap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getGroupedValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;price&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;quantities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getGroupedValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quantity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;shippingDates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getGroupedValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shippingDate&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;Main goal being: Have that function as type safe as possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2
&lt;/h2&gt;

&lt;p&gt;Let's try to build a function that'd handle all the following for us:&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;orderRemap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OrderRemap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getGroupedValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;price&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;quantities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getGroupedValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quantity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;shippingDates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getGroupedValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shippingDate&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;Instead, if the types are defined as such:&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;OrderDetail&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;shippingDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;OrderRemapGrouped&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;orderDetails&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OrderDetail&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;We'd simply have to do:&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="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// no need to know where it's coming from for the example&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;orderRemapGrouped&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OrderRemapGrouped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;groupProperties&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;orderDetails&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to give those challenges a go yourself before reading the solutions, now is a good time! You can simply open &lt;a href="https://www.typescriptlang.org/play" rel="noopener noreferrer"&gt;https://www.typescriptlang.org/play&lt;/a&gt; and eventually share the URL of your playground as a comment 😄.&lt;/p&gt;

&lt;h1&gt;
  
  
  Implementation
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Part 1
&lt;/h2&gt;

&lt;p&gt;We know this API will always return those properties, 3 times each (e.g. &lt;code&gt;price1&lt;/code&gt;, &lt;code&gt;price2&lt;/code&gt;, &lt;code&gt;price3&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;So we start here by using a recent feature of Typescript: &lt;a href="https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html" rel="noopener noreferrer"&gt;Template literal types&lt;/a&gt;.&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;type&lt;/span&gt; &lt;span class="nx"&gt;Indices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;GroupedKeys&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="nx"&gt;U&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;Indices&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;U&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pretty cool how easy it is to extract the base keys of all properties that appears 3 times right?&lt;/p&gt;

&lt;p&gt;If we test it out, here's the output:&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Order&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;price1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;price2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;price3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;quantity1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;quantity2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;quantity3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;shippingDate1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;shippingDate2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;shippingDate3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Indices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;GroupedKeys&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="nx"&gt;U&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;Indices&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;U&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;GroupedKeys&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;Order&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;// "price" | "quantity" | "shippingDate" 🎉&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But don't worry, that's not where I was getting at. There's more.&lt;/p&gt;

&lt;p&gt;Now let's build our generic &lt;code&gt;getGroupedValues&lt;/code&gt; function (not the implementation as it's not the point) but rather its definition:&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;function&lt;/span&gt; &lt;span class="nf"&gt;getGroupedValues&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;BaseKey&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;GroupedKeys&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;baseKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BaseKey&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BaseKey&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;Indices&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// we're not implementing the function, just focusing on its definition&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It feels like this could be exactly what we want!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We accept any object type, therefore &lt;code&gt;Obj&lt;/code&gt; above has no constraint&lt;/li&gt;
&lt;li&gt;The base key can be one of grouped keys of that &lt;code&gt;Obj&lt;/code&gt;, therefore we write &lt;code&gt;BaseKey extends GroupedKeys&amp;lt;keyof Obj&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;We type the inputs (nothing fancy here): &lt;code&gt;object: Obj, baseKey: BaseKey&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;As for the return type, we know that if we want to get an array of values for the prices (&lt;code&gt;price1&lt;/code&gt;, &lt;code&gt;price2&lt;/code&gt;, &lt;code&gt;price3&lt;/code&gt;) then the &lt;code&gt;baseKey&lt;/code&gt; passed as input will be &lt;code&gt;price&lt;/code&gt;. Therefore if we access in the object the value of &lt;code&gt;${BaseKey}${Indices}&lt;/code&gt;, we'll get &lt;code&gt;price1&lt;/code&gt; | &lt;code&gt;price2&lt;/code&gt; | &lt;code&gt;price3&lt;/code&gt; which is exactly what we want&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fantastic! All done then! But wait... Typescript doesn't seem to be really happy here. On our return type:&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="nx"&gt;Obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BaseKey&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;Indices&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Type '&lt;code&gt;${BaseKey}1&lt;/code&gt; | &lt;code&gt;${BaseKey}2&lt;/code&gt; | &lt;code&gt;${BaseKey}3&lt;/code&gt;' cannot be used to index type 'Obj'&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And it looks legit. We're trying to access properties on a generic which doesn't extends anything (our &lt;code&gt;Obj&lt;/code&gt; type).&lt;br&gt;&lt;br&gt;
But how can we keep this function generic and specify that our object will have keys that are composed of the base key and indices 🤔...&lt;/p&gt;

&lt;p&gt;Would&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="nx"&gt;Obj&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BaseKey&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;Indices&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Work? Surely it can't work, because &lt;code&gt;BaseKey&lt;/code&gt; is defined after and itself uses &lt;code&gt;Obj&lt;/code&gt;:&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="nx"&gt;Obj&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BaseKey&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;Indices&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&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;BaseKey&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;GroupedKeys&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well this is just fine for Typescript 🤯.&lt;br&gt;&lt;br&gt;
Read this again.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;It's fine to say that a given type &lt;code&gt;Obj&lt;/code&gt; will be an object (&lt;code&gt;Record&lt;/code&gt;) which contains keys of another type (&lt;code&gt;BaseKey&lt;/code&gt;), which itself is defined by reading the keys of that &lt;code&gt;Obj&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Me:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Ftypescript-generics%2Fassets%2Fmind-blown.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Ftypescript-generics%2Fassets%2Fmind-blown.png" title="Mind blown" alt="Mind blown"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I knew that Typescript could handle recursion just fine, like if you define a list which can have a list, which can have a list, ...&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;List&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;propA&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;list&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;List&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;list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;List&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;propA&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;propA&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;propA&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&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;p&gt;But this? While I think it's amazing, I'm not sure how Typescript manage to settle on the type.&lt;/p&gt;

&lt;p&gt;To wrap this up, here's the complete function (without the implementation but with all the types):&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;function&lt;/span&gt; &lt;span class="nf"&gt;getGroupedValues&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Obj&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BaseKey&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;Indices&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&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;BaseKey&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;GroupedKeys&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;baseKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BaseKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BaseKey&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;Indices&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// we're not implementing the function, just focusing on its definition&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we try it out we see the following:&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="c1"&gt;// some mock for an order&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;order-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Order 1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;price1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;price2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;price3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;quantity1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;quantity2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;quantity3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;shippingDate1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;10 Oct&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;shippingDate2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;11 Oct&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;shippingDate3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;12 Oct&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;orderRemap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OrderRemap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getGroupedValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;price&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// inferred return type: `number[]`&lt;/span&gt;
  &lt;span class="na"&gt;quantities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getGroupedValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quantity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// inferred return type: `number[]`&lt;/span&gt;
  &lt;span class="na"&gt;shippingDates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getGroupedValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shippingDate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// inferred return type: `string[]`&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I just tried this trick on a Friday afternoon without much hope after being stuck for a while on the error&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Type '&lt;code&gt;${BaseKey}1&lt;/code&gt; | &lt;code&gt;${BaseKey}2&lt;/code&gt; | &lt;code&gt;${BaseKey}3&lt;/code&gt;' cannot be used to index type 'Obj'&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I was amazed to see that somehow Typescript is ok with those generics defined at the same level and using each others&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="nx"&gt;Obj&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BaseKey&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;Indices&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&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;BaseKey&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;GroupedKeys&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Am I the only one? Did you know about this? Is anyone able to explain how Typescript can be ok with this? In any case leave a comment and tell me what you think about it and if this was useful 😄!&lt;/p&gt;

&lt;p&gt;Here's the &lt;a href="https://www.typescriptlang.org/play?ts=4.4.3#code/JYOwLgpgTgZghgYwgAgPJQCbWQbwLABQyyA9CcgM4D2AtigK4jACO9KA1hAJ4WHHAYAXJTBRQAcwDcfZCDh1hFUROkEZZSrQ7cKyAETioVegAcIGPcgBGXZAGZkMk2KQBGYSHo0r0VcWfASABMHl4+UH7IAUh2od6+hDKscODAYFzusmEJRMjJqekhWfERSfQpYGlcscXhqjIUABbAJiYSACJwkJlKYiBSDc2tHV0QRb0qgy1t-Z2QNRP9qgC+iQTpZsgAkiAYgRC6ALzIrsgAPshB5-b161ybAOJGpuYA0joAPAAqAHzIx19kBAAB6QXa6AAGABIcKAYNgAKrLGE7PZICjLCHIAD8yARyA8EAAbjlCBsUAAlA70AA2YH+yCexjMGHePA+nC4VBgaEw0B+klI5D00QglguenylXS4v0TWmI0glkAPBuASP21hoYFQaTSqAB3CTIYC6LqQGgmemeEonZB6xrQFB6lAQXbmZCmW1pRrIFJAqBGKCEDRfe4oADk0JwACE4BQIGzlq4sRdIzG4wmgsnkKnY-HuMs7BCw8gECkQFR6T53XGMMgwFQja7gXXQ8gw6grAArMOEGCMBCVKggZDiCBgJkvDAANTgNLYFAAgmAzRbXB8O52ADTINN52wgsEYXQTllsigc7jctBdn4-AAUVC7EAHwg326subZwl3bIAlMIF39OAuHXLsAG0c3TfMUV2fYMQhABdP58FyKAx3oKBh08HUfRNEAuEFDQnTDNDZArI1zRpCA6FSfo63tZA+xAAdgCHbdO3oJRGKoBBOMNIcjTAXQsBgUA0lYkBCFWNQCE1bVdQNOjjXox0qCgdhDSYlih17ftB2HUdx2eFkZznA5QM7IFQRdI9kCpBA1IwD5IL3ZEcFRODMW3FIuB+bcf24KzD2PYy3k+Tkrw3W87xkR9O2fMBXy7TcZA-KCuG-T9uBSgh-2QQCoGAiyIJhAKuDcjz0UxJDcBkNCwAwrDaRpXCfXwwjyGI0jy3pYBKOol1KjosAGK0-T2M4+ktV4ih+OHNJhIgUSmH0qS1iwBAaTgUiHJALjHOgV8+VKGSNB6iBhEaKgSSgFTkCJbbgDgKwqN0etKAgFARpQOFoDQ2tyUIXauNFKkjhHMcT3MUz5wfY7tzDUUw1-DqyMgbdvuzUUsW28QvEGtqAYY4wwBMeh6W2r7WwoOB4QAQiBocuKlKowYZQyoenWdYYOqAEZZ9JkdUYH6XlYZZlGNnjg50KubMig4awPm2zFmZxDmCAhcIIA" rel="noopener noreferrer"&gt;Typescript Playground link&lt;/a&gt; with all the code from above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2
&lt;/h2&gt;

&lt;p&gt;While creating the API in part 1, we notice that we have to call the &lt;code&gt;getGroupedValues&lt;/code&gt; function multiple times, pass a key, recreate the whole object for the remaining properties, etc. It's quite heavy and... Could be simpler!&lt;/p&gt;

&lt;p&gt;So now we'll see how to write a function which does all of this for us and groups the different properties based on their index:&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;orderRemapGrouped&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OrderRemapGrouped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;groupProperties&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;orderDetails&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So here, &lt;code&gt;orderDetails&lt;/code&gt; will be an array containing objects of type &lt;code&gt;{price: number, quantity: number; shippingDate: string}&lt;/code&gt; where the values would be coming from the same index. Example for &lt;code&gt;orderDetails[0]&lt;/code&gt;, it'd have the &lt;code&gt;price&lt;/code&gt;, &lt;code&gt;quantity&lt;/code&gt; and &lt;code&gt;shippingDate&lt;/code&gt; of &lt;code&gt;price1&lt;/code&gt;, &lt;code&gt;quantity1&lt;/code&gt; and &lt;code&gt;shippingDate1&lt;/code&gt;. Etc.&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;function&lt;/span&gt; &lt;span class="nf"&gt;groupProperties&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Obj&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;Keys&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;Indices&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&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;Keys&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;GroupedKeys&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;Obj&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;NewKey&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;string&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;object&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;newKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NewKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Omit&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;Keys&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;Indices&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;NewKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;Keys&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;Indices&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="o"&gt;&amp;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="kc"&gt;null&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// we're not implementing the function, just focusing on its definition&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See some similarities here?&lt;/p&gt;

&lt;p&gt;Exactly! The biggest difference being the return type. So let's break it down:&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="nb"&gt;Omit&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;Keys&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;Indices&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we know that the new object we return should not have any of the properties with the indices (e.g. &lt;code&gt;price1&lt;/code&gt;, &lt;code&gt;price2&lt;/code&gt;, &lt;code&gt;price3&lt;/code&gt;). So we use the built in &lt;code&gt;Omit&lt;/code&gt; type to exclude from the common keys concatenated to the indices.&lt;/p&gt;

&lt;p&gt;Then:&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="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;NewKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;Keys&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;Indices&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We add to the return one property, which will have the key passed as second parameter of the function (of type &lt;code&gt;NewKey&lt;/code&gt;). That key, will have a value that will be an array of objects.&lt;/p&gt;

&lt;p&gt;These objects are going to be all the common keys (&lt;code&gt;price&lt;/code&gt;, &lt;code&gt;quantity&lt;/code&gt; and &lt;code&gt;shippingDate&lt;/code&gt;), associated to the union type of all those properties. For example if we start with &lt;code&gt;price&lt;/code&gt; we'll get the union type of &lt;code&gt;price1&lt;/code&gt;, &lt;code&gt;price2&lt;/code&gt;, &lt;code&gt;price3&lt;/code&gt; which is &lt;code&gt;number&lt;/code&gt; as they're all numbers. And same for the others.&lt;/p&gt;

&lt;p&gt;Here's the &lt;a href="https://www.typescriptlang.org/play?ts=4.4.3#code/JYOwLgpgTgZghgYwgAgPJQCbWQbwLABQyyA9CcgM4D2AtigK4jACO9KA1hAJ4WHHAYAXJTBRQAcwDcfZCDh1hFUROkEZZSrQ7cKyAETioVegAcIGPcgBGXZAGZkMk2KQBGYSHo0r0VcWfASABMHl4+UH7IAUh2od6+hDKscODAYFzusmEJRMjJqekhWfERSfQpYGlcscXhqjIUABbAJiYSACJwkJlKYiBSDc2tHV0QRb0qgy1t-Z2QNRP9qgC+iQTpZsgAkiAYgRC6ALzIrsgAPshB5-b161ybAOJGpuYA0joAPAAqAHzIx19kBAAB6QXa6AAGABIcKAYNgAKrLGE7PZICjLCHIAD8yARyA8EAAbjlCBsUAAlA70AA2YH+yCexjMGHePA+nC4VBgaEw0B+klI5D00QglguenylXS4v0TWmI0glkAPBuASP21jBGAhKlQQMhDMyAApGMxQSoHD6oKwAKyBoIg4OQVIQVEwH2hODZGJRu32GIhABpkCkuD8g167WCMLomS9WZ9Ody0Daw8gAHIQADubMjDujIj64h+AAoZFQbRBtcIrdaAzIQFm2cIM9nuHWCABKas0NKWm1Bj1e5E4VF+zF-ABkTsrrowHxbbKDAEEoFA4FwPjgANqc5CgZBegC61ZtW49nOHo-RmMPyx+f3wuSgEDA9Cges8NJpwd0IcFGkzCAAHJn1kKh6WAGgTBpCA6FSfpkDARoUE1EBtWAXUg2teglGQGAqAQHCJGQXU9zAXQsBgUA0gwkBCFWNQCCwBAaTgUCXRAXDZ2gas+VKRiNBAcCIGERoqBJKBEOQ5AiTY4A4CsGDdDAKhKAgFAkJQOFoGfDBEPuCBCA43DnyOfVnhMY0qFNc0KGLbioCDICHPaF84GAGkKCAjtbg0URbA2YiIVMgA6LEVLUjTGi6ZAuXofUX2QCEBEDJK5DoVKIRctyPIoCFCA0OBdDEiTiKaKhM2U6TKOonU9STTT9LMXRM2aBBGj3XQlN0FiIDYtZBIqoNgB5OLkBK7Asr41ywHczysVIxr8K-CriJpUAUDioCv0isjlIMwQCvIZAVzXDdHw0YgohcETahyS7iClKo4jqI6rsoIYZnEOZbsWAYCA0O9CFC7LZtywggA" rel="noopener noreferrer"&gt;Typescript Playground link&lt;/a&gt; with all the code from above.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;I didn't think that referencing 2 generics between an object and its keys (part 1) would work. But it does and for the best. The combination of that trick with template literals to extract the common bit of some properties is quite powerful and gives us robust typings on our functions to change a data structure into a fairly different one.&lt;/p&gt;

&lt;p&gt;I just love Typescript ❤️✨. If you do as well and you found this article useful, let me know in the comments. I'd also be really interested to see some attempts/solutions from people who gave it a go!&lt;/p&gt;

&lt;h1&gt;
  
  
  Found a typo?
&lt;/h1&gt;

&lt;p&gt;If you've found a typo, a sentence that could be improved or anything else that should be updated on this blog post, you can access it through a git repository and make a pull request. Instead of posting a comment, please go directly to &lt;a href="https://github.com/maxime1992/my-dev.to"&gt;https://github.com/maxime1992/my-dev.to&lt;/a&gt; and open a new pull request with your changes. If you're interested how I manage my dev.to posts through git and CI, &lt;a href="https://dev.to/maxime1992/manage-your-dev-to-blog-posts-from-a-git-repo-and-use-continuous-deployment-to-auto-publish-update-them-143j"&gt;read more here&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Follow me
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/maxime1992"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fdev-logo.png" title="Dev" alt="Dev"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fgithub-logo.png" title="Github" alt="Github"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://twitter.com/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Ftwitter-logo.png" title="Twitter" alt="Twitter"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.reddit.com/user/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Freddit-logo.png" title="Reddit" alt="Reddit"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.linkedin.com/in/maximerobert1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Flinkedin-logo.png" title="Linkedin" alt="Linkedin"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://stackoverflow.com/users/2398593/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fstackoverflow-logo.png" title="Stackoverflow" alt="Stackoverflow"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>typescript</category>
      <category>types</category>
      <category>generics</category>
    </item>
    <item>
      <title>Implement a generic oneOf type with Typescript</title>
      <dc:creator>Maxime</dc:creator>
      <pubDate>Mon, 24 Aug 2020 20:36:21 +0000</pubDate>
      <link>https://dev.to/maxime1992/implement-a-generic-oneof-type-with-typescript-22em</link>
      <guid>https://dev.to/maxime1992/implement-a-generic-oneof-type-with-typescript-22em</guid>
      <description>&lt;p&gt;Have you ever came across a case in your Typescript codebase where you were trying to describe a &lt;code&gt;oneOf&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;I did multiple times and previously, I've had to choose between readability and type safety 😑.&lt;/p&gt;

&lt;p&gt;Let say we want to express the fact that a player of our game can choose before an adventure an animal to come with him. Not all the animals that are available in the game though, he'd have to choose only 1 between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a dog 🐶&lt;/li&gt;
&lt;li&gt;a wolf 🐺&lt;/li&gt;
&lt;li&gt;an eagle 🦅&lt;/li&gt;
&lt;li&gt;a mouse 🐭&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Readability over type safety
&lt;/h1&gt;

&lt;p&gt;If we care more about readability than type safety, we could simply describe an animal and a player as the following:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Animal&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// the animal can be ONE of the following&lt;/span&gt;
  &lt;span class="nl"&gt;dog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Dog&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;wolf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Wolf&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;eagle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Eagle&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;mouse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Mouse&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Player&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;nickname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Animal&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;In this case, the interface would be pretty easy to read, but we could define a &lt;code&gt;dog&lt;/code&gt; and a &lt;code&gt;wolf&lt;/code&gt; for the player and Typescript would still be happy.&lt;/p&gt;

&lt;h1&gt;
  
  
  Type safety over readability
&lt;/h1&gt;

&lt;p&gt;We know that the player can only choose between 🐶, 🐺, 🦅 and 🐭. We can take advantage of that information and instead of having a comment &lt;code&gt;// the animal can be ONE of the following&lt;/code&gt;, we can express that with Typescript (which will bring us some additional type safety later on!).&lt;/p&gt;

&lt;p&gt;To keep things simple, let's start with only 2: 🐶, 🐺.&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;type&lt;/span&gt; &lt;span class="nx"&gt;Animal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;amp;&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="na"&gt;dog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Dog&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;wolf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&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="na"&gt;dog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;wolf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Wolf&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Player&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ... same ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This way we do express the fact that &lt;strong&gt;the animal can either be a dog OR a wolf but we can't have both at the same time&lt;/strong&gt;. While it may seem reasonable to have the type above, let see how it'd look with our 4 animals now:&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;type&lt;/span&gt; &lt;span class="nx"&gt;Animal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;amp;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;dog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Dog&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;wolf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;eagle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;mouse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&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="na"&gt;dog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;wolf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Wolf&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;eagle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;mouse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&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="na"&gt;dog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Dog&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;wolf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Wolf&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;eagle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Eagle&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;mouse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&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="na"&gt;dog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;wolf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;eagle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;mouse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Mouse&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Player&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ... same ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Uh 😵... Now can you imagine what it'd look like if we made an update to the game to add 5, 10 or 15 new animals? 🤕&lt;/p&gt;

&lt;p&gt;This simply doesn't scale.&lt;/p&gt;

&lt;h1&gt;
  
  
  Best of both worlds
&lt;/h1&gt;

&lt;p&gt;How cool would it be to have something like the following:&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;type&lt;/span&gt; &lt;span class="nx"&gt;Animal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;amp;&lt;/span&gt; &lt;span class="nx"&gt;OneOf&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;dog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Dog&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;wolf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Wolf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;eagle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Eagle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;mouse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Mouse&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Player&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ... same ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Assuming that we had all the type safety as the example before, I think that'd be pretty nice!&lt;/p&gt;

&lt;p&gt;But can we? Let's give it a go, step by step.&lt;/p&gt;

&lt;p&gt;The first thing to do would be to have a generic type to which if we pass&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

{
  dog: Dog;
  wolf: Wolf;
  eagle: Eagle;
  mouse: Mouse;
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;and one key from the type above, let say &lt;code&gt;dog&lt;/code&gt; we'd then get the following type:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

{
  dog: Dog;
  wolf: Wolf | null;
  eagle: Eagle | null;
  mouse: Mouse | null;
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We'll call this type &lt;code&gt;OneOnly&lt;/code&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;type&lt;/span&gt; &lt;span class="nx"&gt;OneOnly&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Key&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="o"&gt;&amp;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="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;Exclude&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;Pick&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fimplement-a-generic-one-of-type-with-typescript%2Fassets%2Fhow-to-draw-an-owl.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fimplement-a-generic-one-of-type-with-typescript%2Fassets%2Fhow-to-draw-an-owl.jpg" title="How to draw an owl" alt="How to draw an owl"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's try to breakdown and understand the type above:&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;type&lt;/span&gt; &lt;span class="nx"&gt;OneOnly&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Key&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;So far, it should be fine. We can pass a type that we call &lt;code&gt;Obj&lt;/code&gt; and we then have to pass one key of that type as the second argument.&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;Exclude&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now, we loop over all the keys of the &lt;code&gt;Obj&lt;/code&gt; type, &lt;strong&gt;except&lt;/strong&gt; the one we passed as the second argument of the generic. For all of those keys, we say that the only value they can accept is &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Last missing piece of the puzzle:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="nb"&gt;Pick&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We extract from the &lt;code&gt;Obj&lt;/code&gt; type the &lt;code&gt;Key&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So in the end we've got&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;type&lt;/span&gt; &lt;span class="nx"&gt;OneOnly&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Key&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="o"&gt;&amp;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="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;Exclude&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;Pick&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Which if we use it with a &lt;code&gt;dog&lt;/code&gt; for example would give us:&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;type&lt;/span&gt; &lt;span class="nx"&gt;OnlyDog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;OneOnly&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;dog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Dog&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;wolf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Wolf&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;eagle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Eagle&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;mouse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Mouse&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&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;dog&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And the &lt;code&gt;OnlyDog&lt;/code&gt; type would then be equivalent to:&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="nl"&gt;dog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Dog&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;wolf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Wolf&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;eagle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Eagle&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;mouse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Mouse&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&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;Cool! I think we've got the most complicated part of our final &lt;code&gt;OneOf&lt;/code&gt; type done ✅.&lt;/p&gt;

&lt;p&gt;Now, you may have seen this coming... We've got to loop over our &lt;code&gt;Obj&lt;/code&gt; type and generate all the &lt;code&gt;OneOnly&lt;/code&gt; types for every key:&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;type&lt;/span&gt; &lt;span class="nx"&gt;OneOfByKey&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;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="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;OneOnly&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Nothing too fancy here, and yet, this line is pretty powerful! If we do:&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;type&lt;/span&gt; &lt;span class="nx"&gt;OnlyOneAnimal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;OneOfByKey&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;dog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Dog&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;wolf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Wolf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;eagle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Eagle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;mouse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Mouse&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;It'll give us a type like the following:&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="nl"&gt;dog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OneOnly&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Dog&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;wolf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OneOnly&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Wolf&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;eagle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OneOnly&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Eagle&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;mouse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OneOnly&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Mouse&lt;/span&gt;&lt;span class="o"&gt;&amp;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;That is suuuuuuper coooool right? But... 🤔&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We want a union type of all the values from the type above&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Something like:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="nx"&gt;OneOnly&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Dog&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;OneOnly&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Wolf&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;OneOnly&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Eagle&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;OneOnly&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Mouse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;How can we achieve that? Simply with the following:&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;type&lt;/span&gt; &lt;span class="nx"&gt;ValueOf&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This will create a union type of all the values from the &lt;code&gt;Obj&lt;/code&gt; type.&lt;/p&gt;

&lt;p&gt;So finally, our &lt;code&gt;OneOf&lt;/code&gt; is now just:&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;type&lt;/span&gt; &lt;span class="nx"&gt;OneOfType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ValueOf&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;OneOfByKey&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;🤩 Nice, isn't it? 🤩&lt;/p&gt;

&lt;p&gt;Here's the full code as summary:&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;type&lt;/span&gt; &lt;span class="nx"&gt;ValueOf&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;OneOnly&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Key&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="o"&gt;&amp;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="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;Exclude&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;Pick&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;OneOfByKey&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="o"&gt;&amp;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="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;OneOnly&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&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;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;OneOfType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ValueOf&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;OneOfByKey&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Obj&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And how we can now use it:&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;type&lt;/span&gt; &lt;span class="nx"&gt;Animal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;amp;&lt;/span&gt; &lt;span class="nx"&gt;OneOf&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;dog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Dog&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;wolf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Wolf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;eagle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Eagle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;mouse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Mouse&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Player&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ... same ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The cool thing now is, when defining a player and its animal, we can only define &lt;strong&gt;one of&lt;/strong&gt; the animals. Not 0, not 2, not 3, not 4. Only one 😁.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fimplement-a-generic-one-of-type-with-typescript%2Fassets%2Fdemo-one-of-type-safety.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fimplement-a-generic-one-of-type-with-typescript%2Fassets%2Fdemo-one-of-type-safety.gif" title="Demo one of type safety" alt="Demo one of type safety"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's a &lt;a href="https://www.typescriptlang.org/play?#code/C4TwDgpgBAaghgGwK4QPIDMA8qBGArAPigF4pc8BtAawhAHt0z8BdAbgFgAoUSMgOzR8EIbPgA0UANJQIAD2AQ+AEwDOUGvUbkipAN5RqtKAEs+UAKKyAxsiURMGhkzwTJBZgC4ofJAgRQAXygAMih9QxATM0lPZwoYwI5ucGhUAQwAIRBJWlFCEjCDDSj1WidyWLTBYTyJDSIApLkwOgAnYCgeVPSsABUdWEQUDGwerJyRfoIkrlMFVvQ4K2gAEToAczCuKB2Ael3OuiU6LgDZvnnF5agAdToERl1tvYPgI5POM845iAWl6HMcHWCGgT04Oyg+0Ox1O50u-ygAFk6EgVKDnpDXu9YZwuF0oABBPjGAC2iAKYIhxiUXhUwFapnWSQhfDgJIgtPpjKSQVCVRGlJ2x3WXjWTIxAHd7ugvHcHsydhAgSCvIDgRAFVASSi0V5kaiNadplw4b8rtAAAoIOAgX5bcE7amchl8cUO7zGKxUVns53cjEggBuEAQXh8JJwv01cGJZNDhNjiB5Js4VjofDpUDA1ttrS8Vptdr0GKdUAA5NnC60ALQABjLYgxxK9Po55bJslJEAAjABOXsAJgbAYgwfjtcb7pjpMQXkFEJMNPL07jdeH7pZbLbZYA4gALOh09cLoUbOcBScnmTKts+PyXk-ag1h3wIB8LqUPF-3jFfL6zRgAApK1zAA6FdEFA4UAEp7QhKEJT3RR1D4OgJU6Pc4A6AADYVsJMNQ7HQUwICUCQJWgFQ4HQENIkw4MoCWZYVDUN5GL8DDoGFMs1DAVo6EgdpjAgFQMShGMlCgCj2JUOgULQjCsKgbDP3QbCJGwpV1XwiTlKfNEdNaaA7wQDEQN+cDEwQUDVMxbxXzMnMLIg6ytJBOyTMcqtLJnaz9OgKFPM+LggA" rel="noopener noreferrer"&gt;Typescript Playground&lt;/a&gt; which you can have fun with. It contains all the code we've seen and the final demo shown in the gif above.&lt;/p&gt;

&lt;h1&gt;
  
  
  Found a typo?
&lt;/h1&gt;

&lt;p&gt;If you've found a typo, a sentence that could be improved or anything else that should be updated on this blog post, you can access it through a git repository and make a pull request. Instead of posting a comment, please go directly to &lt;a href="https://github.com/maxime1992/my-dev.to"&gt;https://github.com/maxime1992/my-dev.to&lt;/a&gt; and open a new pull request with your changes. If you're interested how I manage my dev.to posts through git and CI, &lt;a href="https://dev.to/maxime1992/manage-your-dev-to-blog-posts-from-a-git-repo-and-use-continuous-deployment-to-auto-publish-update-them-143j"&gt;read more here&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Follow me
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/maxime1992"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fdev-logo.png" title="Dev" alt="Dev"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fgithub-logo.png" title="Github" alt="Github"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://twitter.com/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Ftwitter-logo.png" title="Twitter" alt="Twitter"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.reddit.com/user/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Freddit-logo.png" title="Reddit" alt="Reddit"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.linkedin.com/in/maximerobert1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Flinkedin-logo.png" title="Linkedin" alt="Linkedin"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://stackoverflow.com/users/2398593/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fstackoverflow-logo.png" title="Stackoverflow" alt="Stackoverflow"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>typescript</category>
    </item>
    <item>
      <title>Building a reactive microwave for Ryan Cavanaugh with RxJs</title>
      <dc:creator>Maxime</dc:creator>
      <pubDate>Sun, 19 Apr 2020 12:23:58 +0000</pubDate>
      <link>https://dev.to/maxime1992/building-a-reactive-microwave-for-ryan-cavanaugh-with-rxjs-3b1a</link>
      <guid>https://dev.to/maxime1992/building-a-reactive-microwave-for-ryan-cavanaugh-with-rxjs-3b1a</guid>
      <description>&lt;p&gt;Few weeks ago, I noticed while browsing Twitter that &lt;a href="https://twitter.com/SeaRyanC" rel="noopener noreferrer"&gt;Ryan Cavanaugh&lt;/a&gt; had some issues with his microwave :&lt;br&gt;
&lt;iframe class="tweet-embed" id="tweet-1245057506662952960-934" src="https://platform.twitter.com/embed/Tweet.html?id=1245057506662952960"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1245057506662952960-934');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1245057506662952960&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;Let's try to fix it for him, shall we? 😁&lt;/p&gt;

&lt;h1&gt;
  
  
  Requirements
&lt;/h1&gt;

&lt;p&gt;First, let's define the scope and requirements of our microwave.&lt;/p&gt;

&lt;p&gt;As a user, I want my microwave to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Have 5 buttons so I can interact with it:

&lt;ul&gt;
&lt;li&gt;+10s: No matter what the current state is, add 10s to the remaining time&lt;/li&gt;
&lt;li&gt;+60s: No matter what the current state is, add 60s to the remaining time&lt;/li&gt;
&lt;li&gt;Start:&lt;/li&gt;
&lt;li&gt;If the current state is "reset", simply start the microwave&lt;/li&gt;
&lt;li&gt;If the current state is "stopped", resume the microwave&lt;/li&gt;
&lt;li&gt;Stop: If the current state is "started", pause the microwave&lt;/li&gt;
&lt;li&gt;Reset: If the current state is "started" or "stopped", stop the microwave and reset the remaining time to 0&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;See the remaining time displayed at all time&lt;/li&gt;

&lt;li&gt;See the remaining time going down every second when the microwave is started&lt;/li&gt;

&lt;li&gt;Automatically stop when it's started and reaches 0s remaining&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  Pick your weapons
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Language
&lt;/h2&gt;

&lt;p&gt;The idea for this app and blog post came from &lt;a href="https://twitter.com/SeaRyanC" rel="noopener noreferrer"&gt;Ryan Cavanaugh&lt;/a&gt;'s &lt;a href="https://twitter.com/SeaRyanC/status/1245057506662952960" rel="noopener noreferrer"&gt;tweet&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Typescript&lt;/strong&gt; has to be our default 🙏.&lt;/p&gt;
&lt;h2&gt;
  
  
  Libs
&lt;/h2&gt;

&lt;p&gt;We'll use only &lt;strong&gt;1&lt;/strong&gt; library: &lt;strong&gt;RxJs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;As you've noticed in the requirements, a microwave is &lt;strong&gt;time based&lt;/strong&gt; and also look like a &lt;strong&gt;state machine&lt;/strong&gt;. RxJs will come really handy to handle such a case 🚀.&lt;/p&gt;
&lt;h1&gt;
  
  
  State VS streams?
&lt;/h1&gt;

&lt;p&gt;Before we start sketching out our main data flow, I'd like to clarify the difference between the &lt;strong&gt;state&lt;/strong&gt; of our app VS the &lt;strong&gt;streams&lt;/strong&gt; we can use.&lt;/p&gt;

&lt;p&gt;A common pitfall I see quite often with RxJs is when someone creates a lot of &lt;code&gt;Subject&lt;/code&gt;s or &lt;code&gt;BehaviorSubject&lt;/code&gt;s to &lt;strong&gt;hold some state&lt;/strong&gt;. It's making things quite hard to follow and then we have to combine multiple streams to build our main state using for example &lt;code&gt;combineLatest&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;While this could work nicely for a few streams, the more streams you add, the hardest it'll be to maintain. A pattern like Redux can instead be used and makes things much simpler to reason about. We'll discover a diagram in the next part to visualize this.&lt;/p&gt;
&lt;h1&gt;
  
  
  Implementing the main data flow
&lt;/h1&gt;

&lt;p&gt;Before implementing all the "details", we'll think and sketch our main stream. Based on the requirements explained earlier, we know that the state of the microwave will change based on 4 different &lt;strong&gt;actions&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add some time (in our case either +10s or +60s)&lt;/li&gt;
&lt;li&gt;Start the microwave&lt;/li&gt;
&lt;li&gt;Stop the microwave&lt;/li&gt;
&lt;li&gt;Reset the microwave&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fbuilding-a-reactive-microwave-for-ryan-cavanaugh-with-rxjs%2Fassets%2Fredux-flow.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fbuilding-a-reactive-microwave-for-ryan-cavanaugh-with-rxjs%2Fassets%2Fredux-flow.png" title="Main data flow and state" alt="Main data flow and state"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's now transform the above diagram into some code.&lt;/p&gt;
&lt;h2&gt;
  
  
  Defining the actions
&lt;/h2&gt;

&lt;p&gt;We are now aware that we need to create 4 &lt;strong&gt;actions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Actions are simple objects with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A type &lt;em&gt;(unique string per action)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;A payload &lt;em&gt;(optional and can be anything)&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In a very simplified way, we could write them as such:&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;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;StartAction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Start&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="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;StopAction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Stop&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="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ResetAction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Reset&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="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;AddTimeAction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AddTimeMs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;timeMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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;But thanks to Typescript, we can improve that code by building on top of it to make it type safe to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create an action before dispatching it&lt;/li&gt;
&lt;li&gt;Make sure that in our "reducer" function we do not forget to deal with all of them&lt;/li&gt;
&lt;li&gt;Avoid to deal with strings and rather use enums
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// as the number of actions has a known length&lt;/span&gt;
&lt;span class="c1"&gt;// I prefer to use an enum to define all of them&lt;/span&gt;
&lt;span class="c1"&gt;// rather than just writing the type of an action&lt;/span&gt;
&lt;span class="c1"&gt;// as a string&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;EMicrowaveAction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;START&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Start&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;STOP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Stop&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;RESET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Reset&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ADD_TIME_MS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AddTimeMs&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="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;StartAction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EMicrowaveAction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;START&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;StopAction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EMicrowaveAction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STOP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ResetAction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EMicrowaveAction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RESET&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;AddTimeAction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EMicrowaveAction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ADD_TIME_MS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;timeMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// we can also create a union type&lt;/span&gt;
&lt;span class="c1"&gt;// (or a "one of" type) of all our actions&lt;/span&gt;
&lt;span class="c1"&gt;// this will be useful in our reducer later on&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveAction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;StartAction&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;StopAction&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;ResetAction&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;AddTimeAction&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// we don't **have to** use the namespace here&lt;/span&gt;
&lt;span class="c1"&gt;// but I personally like this approach as when&lt;/span&gt;
&lt;span class="c1"&gt;// you start having different parts in your&lt;/span&gt;
&lt;span class="c1"&gt;// store, you can use the namespace to clearly&lt;/span&gt;
&lt;span class="c1"&gt;// indicate which one is which, example from&lt;/span&gt;
&lt;span class="c1"&gt;// the previous schema:&lt;/span&gt;
&lt;span class="c1"&gt;// `UserActions`, `MessagesActions`, `DocumentsActions`, etc&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nx"&gt;Actions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// we then create a function for each action type&lt;/span&gt;
  &lt;span class="c1"&gt;// this allows us to simply call a well named function&lt;/span&gt;
  &lt;span class="c1"&gt;// instead of dispatching an object several times in our app&lt;/span&gt;
  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;StartAction&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EMicrowaveAction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;START&lt;/span&gt;&lt;span class="p"&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;const&lt;/span&gt; &lt;span class="nx"&gt;stop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;StopAction&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EMicrowaveAction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STOP&lt;/span&gt;&lt;span class="p"&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;const&lt;/span&gt; &lt;span class="nx"&gt;reset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;ResetAction&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EMicrowaveAction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RESET&lt;/span&gt;&lt;span class="p"&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;const&lt;/span&gt; &lt;span class="nx"&gt;addTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;AddTimeAction&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EMicrowaveAction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ADD_TIME_MS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;timeMs&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;p&gt;Good! We're now able to send actions 👏.&lt;br&gt;&lt;br&gt;
Let's move on to the part where we need to handle them.&lt;/p&gt;
&lt;h2&gt;
  
  
  Defining our reducer
&lt;/h2&gt;

&lt;p&gt;Before we define our reducer... What the fork is a reducer?!&lt;/p&gt;

&lt;p&gt;Let's take a quick look to our previous diagram:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fbuilding-a-reactive-microwave-for-ryan-cavanaugh-with-rxjs%2Fassets%2Freducer.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fbuilding-a-reactive-microwave-for-ryan-cavanaugh-with-rxjs%2Fassets%2Freducer.png" title="Reducer function" alt="Reducer function"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the picture above, the &lt;strong&gt;reducer&lt;/strong&gt; is the black square holding the microwave state. As you can notice, every time an action is being dispatched, the reducer will be called.&lt;/p&gt;

&lt;p&gt;It is a simple function which:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Takes 2 parameters

&lt;ul&gt;
&lt;li&gt;The current state&lt;/li&gt;
&lt;li&gt;The action which just got dispatched&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Returns a new state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Important note:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A reducer &lt;strong&gt;must be pure&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data must be immutable&lt;/strong&gt;
Never mutate data from the current state or the action&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It must not have any side effect&lt;/strong&gt;
You can't for example make HTTP calls within a reducer. Make them before dispatching an action, and once you've got the result pass it in the payload of the action&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For any input passed to the function we must be able to guess the output&lt;/strong&gt;
You can't for example get the current timestamp in a reducer. Instead, if you need the current timestamp get it before dispatching the action and pass it in the payload of the action&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The microwave state
&lt;/h2&gt;

&lt;p&gt;We said previously that our microwave will have 4 actions available to change its current state (add time/start/stop/reset). But can the microwave status be the same as all these actions? Is it a 1-1 relationship? No, it isn't. The add time action shouldn't change the current &lt;strong&gt;status&lt;/strong&gt; of the microwave.&lt;/p&gt;

&lt;p&gt;Lets define the &lt;code&gt;MicrowaveStatus&lt;/code&gt; for that purpose then:&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;export&lt;/span&gt; &lt;span class="kr"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveStatus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;STARTED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Started&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;STOPPED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Stopped&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;RESET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Reset&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;Now, we need to think about how to hold the internal state of the microwave. What data does our microwave need to work internally?&lt;/p&gt;

&lt;p&gt;Of course, it'll need the status we just created so we can start with:&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="c1"&gt;// internal state to the reducer&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveInternalState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveStatus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// ... todo&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It'll also need to keep track of how much time the user plans to use it (when adding time through the add time action):&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveInternalState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveStatus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;timePlannedMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// ... todo&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally, we need to keep track of how much time has been spent already with the microwave in the &lt;code&gt;STARTED&lt;/code&gt; status.&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveInternalState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveStatus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;timePlannedMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;onAndOffTimes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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;You may now think:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why is &lt;code&gt;onAndOffTimes&lt;/code&gt; an array of numbers instead of just the time elapsed in the &lt;code&gt;STARTED&lt;/code&gt; status?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Lets think a bit about how a microwave works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You enter some time using the buttons&lt;/li&gt;
&lt;li&gt;You press start&lt;/li&gt;
&lt;li&gt;The microwave is running&lt;/li&gt;
&lt;li&gt;You can pause/restart the program until you reach 0s left (or stop it before)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At no point in that workflow you press a button to keep the microwave running every second. Well, this is exactly the same for our actions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Actions represent how we want to interact with the state and every computation should be driven from the state downstream&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this case, we keep a record of the timestamps when the user toggle the microwave on and off. Later on, we'll see how to compute the elapsed time. In the meantime, we can still prepare the interface that will be consumed publicly when we subscribe to the microwave stream. It is pretty much the same except that instead of &lt;code&gt;onAndOffTimes: number[]&lt;/code&gt; we'll have &lt;code&gt;timeDoneMs: number&lt;/code&gt;.&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="c1"&gt;// exposed/computed state&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveStatus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;timePlannedMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;timeDoneMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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;Here's another diagram to visually represent what we're building:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fbuilding-a-reactive-microwave-for-ryan-cavanaugh-with-rxjs%2Fassets%2Freducer-and-selector.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fbuilding-a-reactive-microwave-for-ryan-cavanaugh-with-rxjs%2Fassets%2Freducer-and-selector.png" title="Reducer and selector" alt="Reducer and selector"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing the reducer function
&lt;/h2&gt;

&lt;p&gt;Now that we've understood the architecture we're trying to build and especially the role of the reducer function, we can start implementing it.&lt;/p&gt;

&lt;p&gt;If you refer to the previous diagram, the reducer is a (&lt;strong&gt;pure&lt;/strong&gt;) function which takes 2 parameters: The &lt;code&gt;MicrowaveInternalState&lt;/code&gt; and an &lt;code&gt;action&lt;/code&gt;. We'll see later on how to attach the current timestamp to each action (without having to pass it manually all the time). For now, we'll assume the current timestamp is passed within an object, next to the current action.&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;microwaveReducer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;microwave&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveInternalState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveInternalState&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;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;EMicrowaveAction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;START&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// todo: return the new `MicrowaveInternalState`&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;EMicrowaveAction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;STOP&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// todo: return the new `MicrowaveInternalState`&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;EMicrowaveAction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;RESET&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// todo: return the new `MicrowaveInternalState`&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;EMicrowaveAction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ADD_TIME_MS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// todo: return the new `MicrowaveInternalState`&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nf"&gt;unreachableCaseWrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;microwave&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;Before we start implementing each case, note the use of a &lt;code&gt;switch&lt;/code&gt; statement and the call in the &lt;code&gt;default&lt;/code&gt; of &lt;code&gt;unreachableCaseWrap&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As the &lt;code&gt;action.type&lt;/code&gt; is a union type, every time we handle one case and return a result (hence stopping the &lt;code&gt;switch&lt;/code&gt;), Typescript is smart enough to narrow down the next possible type. By having an &lt;code&gt;unreachableCaseWrap&lt;/code&gt; function to which we pass the &lt;code&gt;action.type&lt;/code&gt;, we can ensure that we don't forget to implement any type in our switch 🔥! Otherwise Typescript would throw an error at &lt;strong&gt;compile time&lt;/strong&gt;.&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;unreachableCaseWrap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;never&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By saying that &lt;code&gt;unreachableCaseWrap&lt;/code&gt; takes as an input a value of type &lt;code&gt;never&lt;/code&gt;, if within our &lt;code&gt;switch&lt;/code&gt; statement we're not handling all the different possible types, Typescript will notice that we're trying to pass a value which is not of type &lt;code&gt;never&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Cool! Now let's move on to implementing our reducer. Remember, we have to return a &lt;strong&gt;new&lt;/strong&gt; state, without mutating the previous one. We want this function to remain &lt;strong&gt;pure&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does this mean we've got to deep copy the whole state? Isn't that going to be really expensive?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nop 😁! And thanks to ES6 we can easily do this using the spread operator. Here's a tiny example:&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;obj1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;propA&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;propA1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Value A 1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;propA2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Value A 2&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;propB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;propB1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Value B 1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;propB2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Value B 2&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="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// displays:&lt;/span&gt;
&lt;span class="c1"&gt;// ---------&lt;/span&gt;
&lt;span class="c1"&gt;// {&lt;/span&gt;
&lt;span class="c1"&gt;//   propA: {&lt;/span&gt;
&lt;span class="c1"&gt;//     propA1: 'Value A 1',&lt;/span&gt;
&lt;span class="c1"&gt;//     propA2: 'Value A 2',&lt;/span&gt;
&lt;span class="c1"&gt;//   },&lt;/span&gt;
&lt;span class="c1"&gt;//   propB: {&lt;/span&gt;
&lt;span class="c1"&gt;//     propB1: 'Value B 1',&lt;/span&gt;
&lt;span class="c1"&gt;//     propB2: 'Value B 2',&lt;/span&gt;
&lt;span class="c1"&gt;//   }&lt;/span&gt;
&lt;span class="c1"&gt;// }&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;obj1Updated&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="nx"&gt;obj1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;propB&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="nx"&gt;obj1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;propB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;propB2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NEW VALUE&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="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// `obj1` has **not** been modified&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// displays:&lt;/span&gt;
&lt;span class="c1"&gt;// ---------&lt;/span&gt;
&lt;span class="c1"&gt;// {&lt;/span&gt;
&lt;span class="c1"&gt;//   propA: {&lt;/span&gt;
&lt;span class="c1"&gt;//     propA1: 'Value A 1',&lt;/span&gt;
&lt;span class="c1"&gt;//     propA2: 'Value A 2',&lt;/span&gt;
&lt;span class="c1"&gt;//   },&lt;/span&gt;
&lt;span class="c1"&gt;//   propB: {&lt;/span&gt;
&lt;span class="c1"&gt;//     propB1: 'Value B 1',&lt;/span&gt;
&lt;span class="c1"&gt;//     propB2: 'Value B 2',&lt;/span&gt;
&lt;span class="c1"&gt;//   }&lt;/span&gt;
&lt;span class="c1"&gt;// }&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj1Updated&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// displays:&lt;/span&gt;
&lt;span class="c1"&gt;// ---------&lt;/span&gt;
&lt;span class="c1"&gt;// {&lt;/span&gt;
&lt;span class="c1"&gt;//   propA: {&lt;/span&gt;
&lt;span class="c1"&gt;//     propA1: 'Value A 1',&lt;/span&gt;
&lt;span class="c1"&gt;//     propA2: 'Value A 2',&lt;/span&gt;
&lt;span class="c1"&gt;//   },&lt;/span&gt;
&lt;span class="c1"&gt;//   propB: {&lt;/span&gt;
&lt;span class="c1"&gt;//     propB1: 'Value B 1',&lt;/span&gt;
&lt;span class="c1"&gt;//     propB2: 'NEW VALUE',&lt;/span&gt;
&lt;span class="c1"&gt;//   }&lt;/span&gt;
&lt;span class="c1"&gt;// }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we can use the same syntax for arrays. Instead of using methods which mutates the array, like &lt;code&gt;push&lt;/code&gt; for example, we can do the following:&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;arr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// [1, 2, 3]&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;arrUpdated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// `arr` has **not** been modified&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// [1, 2, 3]&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arrUpdated&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// [1, 2, 3, 4]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we're not deeply copying our entire state, this kind of copy is as efficient as possible. We reuse all the objects that we're not modifying and instead of making a deep copy, we just pass their reference.&lt;/p&gt;

&lt;p&gt;Now that we know how to create an updated version of an object without mutating it, lets take a look to the full reducer:&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;microwaveReducer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;microwave&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveInternalState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveInternalState&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;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;EMicrowaveAction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;START&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;microwave&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STARTED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;onAndOffTimes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;microwave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onAndOffTimes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;EMicrowaveAction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;STOP&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;microwave&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STOPPED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;onAndOffTimes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="nx"&gt;microwave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STARTED&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;microwave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onAndOffTimes&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;microwave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onAndOffTimes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;EMicrowaveAction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;RESET&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;INITIAL_MICROWAVE_STATE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;EMicrowaveAction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ADD_TIME_MS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;microwave&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;timePlannedMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;microwave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timePlannedMs&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeMs&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="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nf"&gt;unreachableCaseWrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;microwave&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;Once again, our function is &lt;strong&gt;pure&lt;/strong&gt; 🙌. Easy to understand, not a single side effect, for any input we're able to expect a given output and easily testable. Fantastic!&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing the selector function
&lt;/h2&gt;

&lt;p&gt;As a reminder, here's how the selector should look like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fbuilding-a-reactive-microwave-for-ryan-cavanaugh-with-rxjs%2Fassets%2Fselector.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fblog-posts%2Fbuilding-a-reactive-microwave-for-ryan-cavanaugh-with-rxjs%2Fassets%2Fselector.png" title="Selector" alt="Selector"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just like a reducer, a selector must be a &lt;strong&gt;pure function&lt;/strong&gt;.&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;microwaveSelector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;microwave&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveInternalState&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveState&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;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;microwave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;RESET&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;timePlannedMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;microwave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timePlannedMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RESET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;timeDoneMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;STOPPED&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;timeDoneMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computeTimeDoneMs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;microwave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onAndOffTimes&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;microwave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timePlannedMs&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;microwave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timePlannedMs&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;timeDoneMs&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;timePlannedMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RESET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;timeDoneMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;timePlannedMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;microwave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timePlannedMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STOPPED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;timeDoneMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;timeDoneMs&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="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;STARTED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;timePlannedMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;microwave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timePlannedMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STARTED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;timeDoneMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;computeTimeDoneMs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;microwave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onAndOffTimes&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UnreachableCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;microwave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&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;p&gt;We don't really care about the &lt;code&gt;computeTimeDoneMs&lt;/code&gt;. It gives us how much time did the microwave spent running from the &lt;code&gt;onAndOffTimes&lt;/code&gt; array. As it's not what we want to focus on today, here's the code without further explanations:&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&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;arr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[][]&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reduce&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&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;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;array&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;computeTimeDoneMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onAndOffTimes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onAndOffTimes&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;timeElapsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;off&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;timeElapsed&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;off&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create the microwave state stream
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Build the MicrowaveInternalState stream
&lt;/h3&gt;

&lt;p&gt;We now have all the logic for our state and our selector. We can start working on our data flow using RxJs streams. For that, we'll start by creating a &lt;strong&gt;factory function&lt;/strong&gt; which for a given &lt;code&gt;action$&lt;/code&gt; observable, will return a &lt;code&gt;MicrowaveState&lt;/code&gt; observable.&lt;/p&gt;

&lt;p&gt;As a first step, we'll create the function and manage the &lt;code&gt;MicrowaveInternalState&lt;/code&gt; using our reducer:&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;INITIAL_MICROWAVE_STATE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveInternalState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;timePlannedMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;onAndOffTimes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RESET&lt;/span&gt;&lt;span class="p"&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;const&lt;/span&gt; &lt;span class="nx"&gt;createMicrowave&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MicrowaveAction&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;MicrowaveState&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;microwaveState&lt;/span&gt;&lt;span class="na"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MicrowaveInternalState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;action$&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;timestamp&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nf"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;microwaveReducer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;INITIAL_MICROWAVE_STATE&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;startWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;INITIAL_MICROWAVE_STATE&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// todo: use our selector to transform the `MicrowaveInternalState` into a `MicrowaveState`&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;In less than 5 lines, we've got a fully reactive approach to manage our internal state so far 🤯.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is one of the reasons why RxJs is powerful and worth learning. But as nice as this is, it's probably a lot to process already! Lets go through it together:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We get an &lt;code&gt;action$&lt;/code&gt; stream. Any time a new action is dispatched, we'll receive it here&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://rxjs.dev/api/operators/timestamp" rel="noopener noreferrer"&gt;&lt;code&gt;timestamp&lt;/code&gt; operator&lt;/a&gt; wraps a value into an object containing the value + the current timestamp&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://rxjs.dev/api/operators/scan" rel="noopener noreferrer"&gt;&lt;code&gt;scan&lt;/code&gt; operator&lt;/a&gt; is similar to the &lt;code&gt;reduce&lt;/code&gt; function available on iterable objects in Javascript. You provide a function (our &lt;code&gt;microwaveReducer&lt;/code&gt; in this case), which will get an accumulator (our &lt;code&gt;MicrowaveInternalState&lt;/code&gt;) and a value (our &lt;code&gt;action&lt;/code&gt;). From this, it should return a value which will be emitted downstream and which will also become the new value passed as the accumulator the next time the &lt;code&gt;scan&lt;/code&gt; runs. Finally, as the 2nd argument of the &lt;code&gt;scan&lt;/code&gt; operator, we provide an initial state (in our case, the &lt;code&gt;INITIAL_MICROWAVE_STATE&lt;/code&gt;). The &lt;code&gt;scan&lt;/code&gt; operator is &lt;strong&gt;really powerful&lt;/strong&gt; and let us have the state &lt;strong&gt;scoped to that function&lt;/strong&gt;. It's not created before and it is only possible to update it by sending a new value to the &lt;code&gt;scan&lt;/code&gt;. No one has access to a variable holding our state and likely to be mutated&lt;/li&gt;
&lt;li&gt;Last but not least, when we subscribe to the microwave we expect to receive an initial state. Before you start your microwave, it still exists, doesn't it? So right after the &lt;code&gt;scan&lt;/code&gt;, we emit the initial state of the microwave. Another possible way to achieve this would be to &lt;code&gt;startWith(Actions.reset())&lt;/code&gt; &lt;strong&gt;before&lt;/strong&gt; the &lt;code&gt;scan&lt;/code&gt; and then the &lt;code&gt;scan&lt;/code&gt; would be started with the &lt;code&gt;RESET&lt;/code&gt; action. But why run the whole reducer function when we know the initial value it's about to return?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Build the public MicrowaveState stream using our selector
&lt;/h3&gt;

&lt;p&gt;So far we know the current state of the microwave, how much time is left, and we've got an array with the timestamps of when it was toggled STARTED/STOPPED.&lt;/p&gt;

&lt;p&gt;How can we get an update every second to represent the state of the microwave while it's running (started)?&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;microwave$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MicrowaveState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;microwaveState$&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;switchMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;microwave&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;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;microwave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;RESET&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;STOPPED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;microwaveSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;microwave&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;STARTED&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;timer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&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;timestamp&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;timestamp&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="nf"&gt;microwaveSelector&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
              &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;microwave&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;onAndOffTimes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;microwave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onAndOffTimes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timestamp&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;takeWhile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeDoneMs&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timePlannedMs&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="nf"&gt;endWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MICROWAVE_RESET_STATE&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UnreachableCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;microwave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&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;p&gt;For &lt;code&gt;MicrowaveStatus.RESET&lt;/code&gt; and &lt;code&gt;MicrowaveStatus.STOPPED&lt;/code&gt;, we just pass the &lt;code&gt;MicrowaveInternalState&lt;/code&gt; to our selector which will transform it to a &lt;code&gt;MicrowaveState&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For the &lt;code&gt;MicrowaveStatus.STARTED&lt;/code&gt;, it's slightly different as we need to update the stream every second (for the countdown):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;timer(0, 1000)&lt;/code&gt;: Start the stream immediately and emit every seconds&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;timestamp&lt;/code&gt;: Get the current timestamp (which will be updated every second thanks to &lt;code&gt;timer&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;map&lt;/code&gt;: Use the &lt;code&gt;microwaveSelector&lt;/code&gt; (just like &lt;code&gt;MicrowaveStatus.RESET&lt;/code&gt; and &lt;code&gt;MicrowaveStatus.STOPPED&lt;/code&gt;) but instead of passing the internal state directly, we create a new object (immutability for the win!). Within that new object, we add the current timestamp into the &lt;code&gt;onAndOffTimes&lt;/code&gt; (which therefore will update the &lt;code&gt;timeDoneMs&lt;/code&gt; in the output) 🙌. The important thing to understand here is that thanks to immutability we never modify the original &lt;code&gt;onAndOffTimes&lt;/code&gt; so by adding the new timestamp in the array we don't accumulate them in the array. We take the initial one and add one. We take the initial one and add one. We take the initial one and add one. Etc...&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;takeWhile(x =&amp;gt; x.timeDoneMs &amp;lt; x.timePlannedMs)&lt;/code&gt;: As soon as the time done is equal or greater than the time planned, we stop that inner stream (no more update needed every second)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;endWith(MICROWAVE_RESET_STATE)&lt;/code&gt;: When the stream ends, we emit the reset state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that before that inner stream, we've got:&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="nx"&gt;microwaveState$&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;switchMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;microwave&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;// ...&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;So when &lt;code&gt;microwaveState$&lt;/code&gt; emits new value, we'll kill all that inner stream and start a new one, which is exactly what we want.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final version of the microwave factory function
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createMicrowave&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MicrowaveAction&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;Microwave&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;microwaveState&lt;/span&gt;&lt;span class="na"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ConnectableObservable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MicrowaveInternalState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;action$&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;timestamp&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nf"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;microwaveReducer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;INITIAL_MICROWAVE_STATE&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;startWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;INITIAL_MICROWAVE_STATE&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;publishReplay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&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;ConnectableObservable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MicrowaveInternalState&lt;/span&gt;&lt;span class="o"&gt;&amp;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;microwave&lt;/span&gt;&lt;span class="na"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MicrowaveState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;microwaveState$&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;switchMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;microwave&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;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;microwave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;RESET&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;STOPPED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;microwaveSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;microwave&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;MicrowaveStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;STARTED&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;timer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&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;timestamp&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;timestamp&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
              &lt;span class="nf"&gt;microwaveSelector&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;microwave&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;onAndOffTimes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;microwave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onAndOffTimes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timestamp&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;takeWhile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeDoneMs&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timePlannedMs&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nf"&gt;endWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MICROWAVE_RESET_STATE&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UnreachableCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;microwave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&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;shareReplay&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;bufferSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;refCount&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="c1"&gt;// we need to keep the state subscribed as if no one is listening&lt;/span&gt;
  &lt;span class="c1"&gt;// to it we should still be able to take actions into account&lt;/span&gt;
  &lt;span class="c1"&gt;// note: we don't unnecessarily subscribe to `microwave$` as this&lt;/span&gt;
  &lt;span class="c1"&gt;// does some computation derived from the state so if someone subscribes&lt;/span&gt;
  &lt;span class="c1"&gt;// later on, that stream would still be up to date!&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;microwaveStateSubscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;microwaveState$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;microwave$&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cleanUp&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;microwaveStateSubscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unsubscribe&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;p&gt;Notice the subtle changes above?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;publishReplay(1)&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;shareReplay({ bufferSize: 1, refCount: true })&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;microwaveState$.connect()&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cleanUp&lt;/code&gt;?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the last part 🥵. Hang tight!&lt;/p&gt;

&lt;p&gt;We have 2 stream to represent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The internal state: &lt;code&gt;microwaveState$&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The public state: &lt;code&gt;microwave$&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When someone calls the &lt;code&gt;createMicrowave&lt;/code&gt; factory function, they'll get a stream representing the microwave. But what if they start dispatching actions without listening to the microwave first? Nothing would be taken into account which is unfortunate.&lt;/p&gt;

&lt;p&gt;To fix this, we put &lt;code&gt;publishReplay(1)&lt;/code&gt; at the end of &lt;code&gt;microwaveState$&lt;/code&gt;. This operator is quite powerful and brings the following features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The "publish" side transforms the &lt;code&gt;Observable&lt;/code&gt; into a &lt;code&gt;ConnectableObservable&lt;/code&gt;. It means that we will have to &lt;strong&gt;connect&lt;/strong&gt; manually to the observable. The connect method will basically subscribe to it. This is why we need to return an object containing a &lt;code&gt;cleanUp&lt;/code&gt; which will &lt;code&gt;unsubscribe&lt;/code&gt; to it when needed&lt;/li&gt;
&lt;li&gt;The "replay" side (which needs an argument, here &lt;code&gt;1&lt;/code&gt;) means that if a value is emitted by that stream &lt;strong&gt;before&lt;/strong&gt; someone subscribe to it downstream, it'll keep the value and send it straight away to a late subscriber&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last one to understand is &lt;code&gt;shareReplay({ bufferSize: 1, refCount: true })&lt;/code&gt;. It's applied as the last operator of the &lt;code&gt;microwave$&lt;/code&gt; stream. When someone calls the &lt;code&gt;createMicrowave&lt;/code&gt; factory function and subscribe multiple times to the &lt;code&gt;microwave$&lt;/code&gt; stream, the &lt;code&gt;microwaveState$&lt;/code&gt; won't be re-triggered (as explained previously it's been shared), but for &lt;code&gt;microwave$&lt;/code&gt; we'd have the whole selector and observable chain for the started state running 1 time &lt;strong&gt;per subscriber&lt;/strong&gt;. When we create an instance of a microwave using the &lt;code&gt;createMicrowave&lt;/code&gt;, we should be able to subscribe multiple times to it without triggering that logic multiple times. Therefore, we use &lt;code&gt;shareReplay&lt;/code&gt;. We set the &lt;code&gt;bufferSize&lt;/code&gt; property to &lt;code&gt;1&lt;/code&gt; so that if someone subscribes later on, he'll get the last value straight away. We set the &lt;code&gt;refCount&lt;/code&gt; property to &lt;code&gt;true&lt;/code&gt; (which is very important), so that if the microwave is started but no one listen, the whole observable chain with &lt;code&gt;timer&lt;/code&gt;, &lt;code&gt;timestamp&lt;/code&gt;, &lt;code&gt;microwaveSelector&lt;/code&gt;, &lt;code&gt;takeWhile&lt;/code&gt;, &lt;code&gt;endWith&lt;/code&gt; will &lt;strong&gt;NOT&lt;/strong&gt; run. Only if there's at least one subscriber. And if more than one, they share the results 🔥.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;On one hand, working with observables and thinking reactively can be very challenging. There's a steep learning curve and the concept is very different from imperative programming.&lt;/p&gt;

&lt;p&gt;On the other hand, RxJs is very powerful and once we get used to it, it becomes easier to write complicated workflows.&lt;/p&gt;

&lt;p&gt;If you decide to use reactive programming, remember that using &lt;code&gt;subscribe&lt;/code&gt; is where the reactive programming ends.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1180316203937681410-672" src="https://platform.twitter.com/embed/Tweet.html?id=1180316203937681410"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1180316203937681410-672');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1180316203937681410&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;h1&gt;
  
  
  Found a typo?
&lt;/h1&gt;

&lt;p&gt;If you've found a typo, a sentence that could be improved or anything else that should be updated on this blog post, you can access it through a git repository and make a pull request. Instead of posting a comment, please go directly to &lt;a href="https://github.com/maxime1992/my-dev.to"&gt;https://github.com/maxime1992/my-dev.to&lt;/a&gt; and open a new pull request with your changes. If you're interested how I manage my dev.to posts through git and CI, &lt;a href="https://dev.to/maxime1992/manage-your-dev-to-blog-posts-from-a-git-repo-and-use-continuous-deployment-to-auto-publish-update-them-143j"&gt;read more here&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Follow me
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/maxime1992"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fdev-logo.png" title="Dev" alt="Dev"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fgithub-logo.png" title="Github" alt="Github"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://twitter.com/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Ftwitter-logo.png" title="Twitter" alt="Twitter"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.reddit.com/user/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Freddit-logo.png" title="Reddit" alt="Reddit"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.linkedin.com/in/maximerobert1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Flinkedin-logo.png" title="Linkedin" alt="Linkedin"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://stackoverflow.com/users/2398593/maxime1992" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmaxime1992%2Fmy-dev.to%2Fmaster%2Fshared-assets%2Fstackoverflow-logo.png" title="Stackoverflow" alt="Stackoverflow"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>rxjs</category>
      <category>typescript</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
