<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: John Carroll</title>
    <description>The latest articles on Forem by John Carroll (@johncarroll).</description>
    <link>https://forem.com/johncarroll</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%2F27796%2F667086cf-c174-450e-b96f-b063c3f15f7f.jpeg</url>
      <title>Forem: John Carroll</title>
      <link>https://forem.com/johncarroll</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/johncarroll"/>
    <language>en</language>
    <item>
      <title>Surprisingly simple method to increase Firestore performance &amp; reduce cost</title>
      <dc:creator>John Carroll</dc:creator>
      <pubDate>Thu, 27 May 2021 01:57:48 +0000</pubDate>
      <link>https://forem.com/johncarroll/surprisingly-simple-method-to-increase-firestore-performance-reduce-cost-2gcg</link>
      <guid>https://forem.com/johncarroll/surprisingly-simple-method-to-increase-firestore-performance-reduce-cost-2gcg</guid>
      <description>&lt;p&gt;&lt;em&gt;This post is intended for people already familiar with &lt;a href="https://firebase.google.com/products/firestore"&gt;Firebase's Firestore&lt;/a&gt; as well as &lt;a href="https://rxjs.dev/"&gt;RxJS observables&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Recently, I realized that I could increase performance and reduce the cost of Firebase's Firestore using a simple query cache.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Edit:&lt;/strong&gt; having used this in production for a while, I find this technique is mainly a performance improvement and does not have a large impact on cost (possibly because I've also enabled firestore persistence which already reduces cost).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;While Firebase's Firestore is pretty fast to begin with, because it doesn't support automatically joining data together (i.e. SQL joins) a common workaround is to use observables to do something like the following (example taken from &lt;a href="https://github.com/firebase/firebase-js-sdk/tree/master/packages/rxfire"&gt;rxFire docs&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;collectionData&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rxfire/firestore&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getDownloadURL&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rxfire/storage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;combineLatest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rxjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;switchMap&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rxjs/operators&lt;/span&gt;&lt;span class="dl"&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;firebase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initializeApp&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="cm"&gt;/* config */&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;citiesRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;firestore&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cities&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;collectionData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;citiesRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&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;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;cities&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;combineLatest&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;cities&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;c&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;ref&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/cities/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;c&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="s2"&gt;.png`&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;getDownloadURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ref&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;imageURL&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;imageURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;c&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="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="nx"&gt;cities&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;cities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;imageURL&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;In this example, a collection of cities is being loaded and then image data needs to be separately fetched for each city returned.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Using this method, queries can quickly slow down as one query turns into dozens of queries. In a small app I made for my nonprofit, all these queries added up over time (as new features were added) and all of a sudden you'd be waiting 5-30 seconds for certain pages to load. Any delay is especially annoying if you navigate back and forth between pages quickly.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I just loaded this data a second ago, why does it need to load everything again?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What I wanted to do was cache query data for a period of time so that it could be quickly reused if someone navigates back and forth between a few pages. However, without giving it much thought, it seemed like implementing such as cache would take time and add a fair amount of complexity. I tried using Firestore persistence with the hope that this would automatically deduplicate queries, cache data, and increase performance, but it didn't have as much of an impact as I'd hoped (it did reduce costs somewhat, but it also unexpectedly reduced performance).&lt;/p&gt;

&lt;p&gt;Turns out it was really easy to create a better cache.&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution
&lt;/h2&gt;

&lt;p&gt;I implemented a simple query cache that maintains a subscription to a query for some configurable amount of time, even after all observers have unsubscribed from the data. When a component executes a new query, instead of immediately calling Firestore, I check the query cache to see if the relevant query was already created. If it was, I reuse the existing query. Else, I create a new query and cache it for the future.&lt;/p&gt;

&lt;p&gt;The code:&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Subject&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rxjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;stringify&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fast-json-stable-stringify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;finalize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;shareReplay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;takeUntil&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rxjs/operators&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/** Amount of milliseconds to hold onto cached queries */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HOLD_CACHED_QUERIES_DURATION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&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="c1"&gt;// 3 minutes&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;QueryCacheService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&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="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;unknown&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;resolve&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;service&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="nx"&gt;method&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="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="nx"&gt;queryFactory&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;Observable&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="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;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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;query&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;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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="k"&gt;as&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;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="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;query&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;query&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;destroy$&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;Subject&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;subscriberCount&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="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NodeJS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Timeout&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="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;queryFactory&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;takeUntil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;destroy$&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nf"&gt;tapOnSubscribe&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;// since there is now a subscriber, don't cleanup the query&lt;/span&gt;
        &lt;span class="c1"&gt;// if we were previously planning on cleaning it up&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;timeout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;subscriberCount&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="nf"&gt;finalize&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;// triggers on unsubscribe&lt;/span&gt;
        &lt;span class="nx"&gt;subscriberCount&lt;/span&gt;&lt;span class="o"&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;subscriberCount&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="c1"&gt;// If there are no subscribers, hold onto any cached queries&lt;/span&gt;
          &lt;span class="c1"&gt;// for `HOLD_CACHED_QUERIES_DURATION` milliseconds and then&lt;/span&gt;
          &lt;span class="c1"&gt;// clean them up if there still aren't any new&lt;/span&gt;
          &lt;span class="c1"&gt;// subscribers&lt;/span&gt;
          &lt;span class="nx"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&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;destroy$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="nx"&gt;destroy$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;complete&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;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&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="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;HOLD_CACHED_QUERIES_DURATION&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;// Without this delay, very large queries are executed synchronously&lt;/span&gt;
      &lt;span class="c1"&gt;// which can introduce some pauses/jank in the UI. &lt;/span&gt;
      &lt;span class="c1"&gt;// Using the `async` scheduler keeps UI performance speedy. &lt;/span&gt;
      &lt;span class="c1"&gt;// I also tried the `asap` scheduler but it still had jank.&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;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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&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;query&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;query&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="cm"&gt;/** 
 * Triggers callback every time a new observer 
 * subscribes to this chain. 
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;tapOnSubscribe&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;callback&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="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;MonoTypeOperatorFunction&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="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;source&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;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;Observable&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;=&amp;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="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;callback&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;source&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;I can use this cache like so:&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;class&lt;/span&gt; &lt;span class="nc"&gt;ClientService&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="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;fstore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AngularFirestore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;queryCache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;QueryCacheService&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;getClient&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="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;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fstore&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;IClient&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;`clients/&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="s2"&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;valueChanges&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryCache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ClientService&lt;/span&gt;&lt;span class="dl"&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;getClient&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="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; 
      &lt;span class="nx"&gt;query&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;Now, when the &lt;code&gt;ClientService#getClient()&lt;/code&gt; method is called, the method arguments and identifiers are passed to the query cache service along with a query factory function. The query cache service uses the &lt;a href="https://github.com/epoberezkin/fast-json-stable-stringify"&gt;fast-json-stable-stringify&lt;/a&gt; library to stringify the query's identifying information and use this string as a key to cache the query's observable. Before caching the query, the observable is modified in the following ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;shareReplay(1)&lt;/code&gt; is added so that future subscribers get the most recent results immediately and also so that a subscription to the underlying Firestore data is maintained even after the last subscriber to this query unsubscribes.&lt;/li&gt;
&lt;li&gt;Subscribers to the query are tracked so that, after the last subscriber unsubscribes, a timer is set to automatically unsubscribe from the underlying Firestore data and clear the cache after a user defined set period of time (I'm currently using 3 minutes). &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;delay(0)&lt;/code&gt; is used to force subscribers to use the &lt;code&gt;asyncSchedular&lt;/code&gt;. I find this helps keep the UI snappy when loading a large dataset that has been cached (otherwise, the UI attempt to synchronously load the large data which can cause stutter/jank).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This cache could be further updated to allow configuring the &lt;code&gt;HOLD_CACHED_QUERIES_DURATION&lt;/code&gt; on a per-query basis.&lt;/p&gt;

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

&lt;p&gt;This simple cache greatly increases performance and potentially reduces costs if it prevents the same documents from being reloaded again and again in rapid succession. The one potential "gotcha" is if a query is built using &lt;code&gt;Date&lt;/code&gt; arguments. In this case, you need to be careful about using &lt;code&gt;new Date()&lt;/code&gt; as an argument to a query since this would change the cache key associated with the query on every call (basically, this would prevent the cache from ever being used). You can fix this issue by normalizing &lt;code&gt;Date&lt;/code&gt; creation (e.g. &lt;code&gt;startOfDay(new Date())&lt;/code&gt; using &lt;code&gt;date-fns&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Hope this is helpful.&lt;/p&gt;

</description>
      <category>firebase</category>
      <category>rxjs</category>
    </item>
    <item>
      <title>Awesome Forms with Solidjs</title>
      <dc:creator>John Carroll</dc:creator>
      <pubDate>Tue, 27 Apr 2021 18:56:15 +0000</pubDate>
      <link>https://forem.com/johncarroll/awesome-forms-with-solidjs-18gi</link>
      <guid>https://forem.com/johncarroll/awesome-forms-with-solidjs-18gi</guid>
      <description>&lt;p&gt;&lt;em&gt;Editors note 6/7/22: I overhauled this article in response to library improvements.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I recently started falling in love with &lt;a href="https://www.solidjs.com/"&gt;Solidjs&lt;/a&gt;, a javascript library that looks like React but is significantly faster and, dare I say, has a notably better API. Unlike React, Solidjs component functions are invoked only once when the component is initialized and then never again.&lt;/p&gt;

&lt;p&gt;I decided to take advantage of Solidjs' strengths, and build a 3.6kb min zipped library to aid with user input forms: &lt;a href="https://github.com/jorroll/solid-forms"&gt;solid-forms&lt;/a&gt;. Let's dive in and see what we can do (note, if you want an introduction to Solidjs, &lt;a href="https://dev.to/ryansolid/introducing-the-solidjs-ui-library-4mck"&gt;start here&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Let's create a simple &lt;code&gt;TextField&lt;/code&gt; component in typescript.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;IFormControl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createFormControl&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;solid-forms&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;const&lt;/span&gt; &lt;span class="nx"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Component&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;control&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IFormControl&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="na"&gt;label&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="nx"&gt;placeholder&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;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&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="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'input-label'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&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;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;oninput&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;props&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="nf"&gt;markDirty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="nx"&gt;props&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="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&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="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;onblur&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&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;props&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="nf"&gt;markTouched&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="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;placeholder&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&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;This component tracks whether it has been &lt;code&gt;touched&lt;/code&gt; by a user (notice &lt;code&gt;onblur&lt;/code&gt; callback) and whether it has been changed by a user (&lt;code&gt;oninput&lt;/code&gt;). When a user changes the value, we mark the control as &lt;code&gt;dirty&lt;/code&gt; to track that the value has been changed by the user. We also have the ability to set a label on the input as well as a placeholder. Pretty straightforward stuff.&lt;/p&gt;

&lt;p&gt;But text field's are &lt;em&gt;rarely&lt;/em&gt; used in isolation. We want to build a component to collect some address information. This will involve asking for a &lt;code&gt;Street&lt;/code&gt;, &lt;code&gt;City&lt;/code&gt;, &lt;code&gt;State&lt;/code&gt;, and &lt;code&gt;Postcode&lt;/code&gt;. Lets use our &lt;code&gt;TextField&lt;/code&gt; component to create our &lt;code&gt;AddressForm&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;withControl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createFormGroup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createFormControl&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;solid-forms&lt;/span&gt;&lt;span class="dl"&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;controlFactory&lt;/span&gt; &lt;span class="o"&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;createFormGroup&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;street&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;createFormControl&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;|&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="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;createFormControl&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;|&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="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;createFormControl&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;|&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="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;createFormControl&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;|&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="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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;AddressForm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;withControl&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;controlFactory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&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;controls&lt;/span&gt; &lt;span class="o"&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;props&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;controls&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;fieldset&lt;/span&gt; &lt;span class="na"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;is-valid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;props&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;isValid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;is-invalid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;props&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;isValid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;is-touched&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;props&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;isTouched&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;is-untouched&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;props&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;isTouched&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;is-dirty&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;props&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;isDirty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;is-clean&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;props&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;isDirty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TextField&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Street"&lt;/span&gt; &lt;span class="na"&gt;control&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;controls&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;street&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TextField&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"City"&lt;/span&gt; &lt;span class="na"&gt;control&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;controls&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TextField&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"State"&lt;/span&gt; &lt;span class="na"&gt;control&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;controls&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TextField&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Postcode"&lt;/span&gt; &lt;span class="na"&gt;control&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;controls&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;zip&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;fieldset&lt;/span&gt;&lt;span class="p"&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the address form is wrapped with &lt;code&gt;withControl()&lt;/code&gt;, a higher order component. This streamlines the process of creating reusable form components.&lt;/p&gt;

&lt;p&gt;We want our &lt;code&gt;AddressForm&lt;/code&gt; to use a &lt;code&gt;FormGroup&lt;/code&gt; control rather than the default &lt;code&gt;FormControl&lt;/code&gt; so we provide a &lt;code&gt;controlFactory&lt;/code&gt; function which initializes the control.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;controlFactory&lt;/span&gt; &lt;span class="o"&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;createFormGroup&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;street&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;createFormControl&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;|&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="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;createFormControl&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;|&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="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;createFormControl&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;|&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="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;createFormControl&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;|&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="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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;AddressForm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;withControl&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;controlFactory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&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;// continued...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All we needed to do to connect our AddressForm control to the &lt;code&gt;TextField's&lt;/code&gt; control was to use the &lt;code&gt;control={/* ... */}&lt;/code&gt; property to specify which FormControl on the parent should be connected with the child TextField.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;controls&lt;/span&gt; &lt;span class="o"&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;props&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;controls&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TextField&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Street"&lt;/span&gt; &lt;span class="na"&gt;control&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;controls&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;street&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TextField&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"City"&lt;/span&gt; &lt;span class="na"&gt;control&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;controls&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also set the component up to apply css classes based on if the &lt;code&gt;AddressForm&lt;/code&gt; is valid/invalid, edited/unedit, and touched/untouched.&lt;/p&gt;

&lt;p&gt;Say we want to hook our AddressForm component into a larger form. That's also easy!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// factory for initializing the `MyLargerForm` `FormGroup`&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;controlFactory&lt;/span&gt; &lt;span class="o"&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;createFormGroup&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;control&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AddressForm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;control&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// the form component itself&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;MyLargerForm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;withControl&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;controlFactory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&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;controls&lt;/span&gt; &lt;span class="o"&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;props&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;controls&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// because we can&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lastNameControl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createFormControl&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;|&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="kc"&gt;null&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;fieldset&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TextField&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"First name"&lt;/span&gt; &lt;span class="na"&gt;control&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;controls&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TextField&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Last name"&lt;/span&gt; &lt;span class="na"&gt;control&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;lastNameControl&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;fieldset&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AddressForm&lt;/span&gt; &lt;span class="na"&gt;control&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;controls&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And, with just a few steps, we have a very powerful, very composible set of form components. As changes happen to the &lt;code&gt;TextField&lt;/code&gt; components, those changes flow upwards and automatically update the parent &lt;code&gt;FormGroup&lt;/code&gt; components.&lt;/p&gt;

&lt;p&gt;You may have noticed that we did something new in the &lt;code&gt;controlFactory&lt;/code&gt; function.&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;controlFactory&lt;/span&gt; &lt;span class="o"&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;createFormGroup&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;control&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AddressForm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;control&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 could have defined this controlFactory function the same way we defined the others (i.e. using &lt;code&gt;createFormControl()&lt;/code&gt; and &lt;code&gt;createFormGroup()&lt;/code&gt;), but &lt;code&gt;withControl()&lt;/code&gt; helpfully adds the control's &lt;code&gt;controlFactory&lt;/code&gt; function to a special &lt;code&gt;control&lt;/code&gt; property on a component created using &lt;code&gt;withControl()&lt;/code&gt;. This DRYs up our code and allows easily creating FormControl's for any components you create &lt;code&gt;withControl()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So what if we want to listen to control changes in our &lt;code&gt;MyLargerForm&lt;/code&gt; component? Well that's easy. For example, to listen to when any part of the form is touched, we can simply observe the &lt;code&gt;isTouched&lt;/code&gt; property inside a Solidjs effect.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;createEffect&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;props&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;isTouched&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;// do stuff...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To listen to when the "firstName" control, specifically, is touched&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;createEffect&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;props&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;controls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isTouched&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;// do stuff...&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 a more complex, advanced example: if we want to listen for value changes, debounce the rate of changes, perform validation, and mark the &lt;code&gt;control&lt;/code&gt; as pending while we wait for validation to complete, we can do the following. Note, when we set errors on the &lt;code&gt;firstName&lt;/code&gt; control, that will result in the "First name" &lt;code&gt;TextField&lt;/code&gt;  being marked as invalid (score!).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;myCustomValidationService&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./my-validation-service&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;const&lt;/span&gt; &lt;span class="nx"&gt;MyLargerForm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;withControl&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="c1"&gt;// ...hiding the controlFactory boilerplate...&lt;/span&gt;
  &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&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;control&lt;/span&gt; &lt;span class="o"&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;props&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;controls&lt;/span&gt; &lt;span class="o"&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;control&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;controls&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// This is a simplified example. In reality, you'd want&lt;/span&gt;
    &lt;span class="c1"&gt;// to debounce user input and only run async&lt;/span&gt;
    &lt;span class="c1"&gt;// validation when it made sense rather than on every&lt;/span&gt;
    &lt;span class="c1"&gt;// change. There are multiple ways to accomplish this&lt;/span&gt;
    &lt;span class="c1"&gt;// but none of them are specific to Solid Forms.&lt;/span&gt;
    &lt;span class="nf"&gt;createEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &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;firstName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;controls&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;markPending&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;myCustomValidationService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;firstName&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setErrors&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;validationFailed&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="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setErrors&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="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;markPending&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&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="nf"&gt;onsubmit &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&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="nf"&gt;control&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;isPending&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;control&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;isValid&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;// do stuff...&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;onsubmit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onsubmit&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;fieldset&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TextField&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"First name"&lt;/span&gt; &lt;span class="na"&gt;control&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;controls&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TextField&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Last name"&lt;/span&gt; &lt;span class="na"&gt;control&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;controls&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;fieldset&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AddressForm&lt;/span&gt; &lt;span class="na"&gt;control&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;controls&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is really just scratching the surface of what you can do with &lt;code&gt;solid-forms&lt;/code&gt;. &lt;a href="https://github.com/jorroll/solid-forms"&gt;Check out the repo to read the documentation and learn more&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check out the repo
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/jorroll/solid-forms"&gt;https://github.com/jorroll/solid-forms&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>solidjs</category>
      <category>rxjs</category>
      <category>forms</category>
      <category>githunt</category>
    </item>
    <item>
      <title>Angular: How to save scroll position when navigating</title>
      <dc:creator>John Carroll</dc:creator>
      <pubDate>Sun, 20 Dec 2020 11:40:43 +0000</pubDate>
      <link>https://forem.com/johncarroll/angular-how-to-refresh-scroll-position-on-back-5e1b</link>
      <guid>https://forem.com/johncarroll/angular-how-to-refresh-scroll-position-on-back-5e1b</guid>
      <description>&lt;p&gt;If your app makes use of the Angular Router, you'd probably like for a user's scroll position to be remembered when they use the "back" button to navigate back to a page they were already on. In a standard "static" webpage, the browser does this for the user automatically. This doesn't happen automatically in an angular app however and there are a few reasons why.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Even for static sites, the browser will only refresh the scroll position of the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; element. In a single page app, it's quite likely that you'd like to refresh the scroll position of other elements. For example, if you're showing an admin dashboard, maybe there are a number of divs on the page that you'd like to independently refresh the scroll position for.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Before you can refresh the scroll position of an element, you need to wait for all the data that element needs to load. For example, if you have a &lt;code&gt;&amp;lt;my-list&amp;gt;&lt;/code&gt; component and you try to refresh the scroll position before the &lt;code&gt;&amp;lt;my-list&amp;gt;&lt;/code&gt; items have finished loading, nothing will happen because the &lt;code&gt;&amp;lt;my-list&amp;gt;&lt;/code&gt; component's &lt;code&gt;height&lt;/code&gt; in the DOM will not account for all of the items that haven't been loaded.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So how can we accomplish our goal? Well, you could try the Angular Router's &lt;a href="https://angular.io/api/common/ViewportScroller"&gt;built in scroll position refresh functionality&lt;/a&gt;, but you'll probably be disappointed to learn that it only works for the page's &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; element and it also requires you to use Angular Resolvers for all of your data. In practice, I don't find it to be useful. There's also very little documentation for it.&lt;/p&gt;

&lt;p&gt;Here's an alternative solution that's very flexible: if you're using the small &lt;a href="https://gitlab.com/service-work/is-loading#angular-isloading-scrollposition"&gt;&lt;code&gt;IsLoadingService&lt;/code&gt;&lt;/a&gt; to manage your app's loading state (and I highly recommend that you do), then you have a simple, centralized way of checking if something is loading. This sets us up to build a &lt;a href="https://gitlab.com/service-work/is-loading#angular-isloading-scrollposition"&gt;&lt;code&gt;ScrollPositionDirective&lt;/code&gt;&lt;/a&gt; (&lt;code&gt;@service-work/scroll-position&lt;/code&gt;) which automatically saves an elements scroll position on router navigation, and automatically refreshes an element's scroll position after all the pieces of an element have finished loading.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post builds off of &lt;a href="https://dev.to/johncarroll/angular-how-to-easily-display-loading-indicators-4359"&gt;Angular: How to easily display loading indicators&lt;/a&gt;. If you haven't read that post, I recommend you start there and then come back here.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now, let's look at some code! Here are a few examples which build off of each other. You'll see that most of these examples also use the &lt;code&gt;IsLoadingService&lt;/code&gt; from the &lt;code&gt;@service-work/is-loading&lt;/code&gt; package, which the &lt;code&gt;ScrollPositionDirective&lt;/code&gt; depends on.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Alternatively, here's a codesandbox demo: &lt;a href="https://codesandbox.io/s/isloadingservice-example-ujlgm?file=/src/app/app.component.ts"&gt;https://codesandbox.io/s/isloadingservice-example-ujlgm?file=/src/app/app.component.ts&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Refresh the scroll position for a synchronous element
&lt;/h2&gt;

&lt;p&gt;Let's start easy. We want to refresh the scroll position of an element that doesn't depend on any asynchronous data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-lorum-ipsum&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;div
      swScrollPosition='lorum-ipsum'
      style="height: 35px; overflow: auto;"
    &amp;gt;
      &amp;lt;p&amp;gt;
        Class aptent taciti sociosqu ad litora torquent 
        per conubia nostra, per inceptos himenaeos. In 
        ultricies mollis ante. Phasellus mattis ut turpis 
        in vehicula. Morbi orci libero, porta ac tincidunt a,
        hendrerit sit amet sem.
      &amp;lt;/p&amp;gt;
    &amp;lt;/div&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;class&lt;/span&gt; &lt;span class="nc"&gt;LorumIpsumComponent&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simply by applying the &lt;code&gt;[swScrollPosition]&lt;/code&gt; directive to the div element (seen here with the key &lt;code&gt;"lorum-ipsum"&lt;/code&gt;), Angular will now automatically remember and refresh this element's scroll position when you navigate away from, and back to, this component (admittedly, this example component doesn't have that much text so I'd imagine most of the time everything can fit in the viewport without needing a scrollbar).&lt;/p&gt;

&lt;h2&gt;
  
  
  Refresh the scroll position for an asynchronous element
&lt;/h2&gt;

&lt;p&gt;Let's look at a more realistic example, say we have a contact list component and we want to automatically refresh the scroll position when a user navigates back to this component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-user-list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;ul
      id="user-list"
      swScrollPosition='users-loading'
    &amp;gt;
      &amp;lt;li
        *ngFor='let user of users | async'
        [routerLink]="['/user', user.id, 'profile']"
      &amp;gt;
        {{ user.name }}
      &amp;lt;/li&amp;gt;
    &amp;lt;/ul&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;class&lt;/span&gt; &lt;span class="nc"&gt;UserListComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;users&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;IUser&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="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;isLoadingService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IsLoadingService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// assume UserService is a service you've created to fetch&lt;/span&gt;
    &lt;span class="c1"&gt;// user data&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;userService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserService&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;ngOnInit&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;users&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;isLoadingService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="c1"&gt;// assume `UserService#getUsers()` returns `Observable&amp;lt;IUser[]&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;userService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUsers&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users-loading&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similar to the previous example, the &lt;code&gt;UserListComponent&lt;/code&gt; will automatically refresh the &lt;code&gt;#user-list&lt;/code&gt; element's scroll position when a user navigates back to this component. Unlike in the previous example however, here the &lt;code&gt;ScrollPositionDirective&lt;/code&gt; will wait until the &lt;code&gt;"users-loading"&lt;/code&gt; key has finished loading (i.e. &lt;code&gt;swScrollPosition='users-loading'&lt;/code&gt;) before attempting to refresh the &lt;code&gt;#user-list&lt;/code&gt; element's scroll position.&lt;/p&gt;

&lt;h2&gt;
  
  
  Refresh the scroll position for an asynchronous element and show a loading indicator while data is loading
&lt;/h2&gt;

&lt;p&gt;Let's expand on the previous example. Say you want to show a loading indicator while the &lt;code&gt;#user-list&lt;/code&gt; element is loading. Here, we'll use the Angular Material &lt;a href="https://material.angular.io/components/progress-spinner/overview"&gt;&lt;code&gt;MatProgressSpinner&lt;/code&gt; component&lt;/a&gt; as our loading spinner, along with the &lt;code&gt;IsLoadingPipe&lt;/code&gt; (i.e. &lt;code&gt;swIsLoading&lt;/code&gt; pipe) from &lt;code&gt;@service-work/is-loading&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="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-user-list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;mat-spinner
      *ngIf="'users-loading' | swIsLoading | async; else showContent"
    &amp;gt;
    &amp;lt;/mat-spinner&amp;gt;

    &amp;lt;ng-template #showContent&amp;gt;
      &amp;lt;ul
        id="user-list"
        swScrollPosition='users-loading'
      &amp;gt;
        &amp;lt;li
          *ngFor='let user of users | async'
          [routerLink]="['/user', user.id, 'profile']"
        &amp;gt;
          {{ user.name }}
        &amp;lt;/li&amp;gt;
      &amp;lt;/ul&amp;gt;
    &amp;lt;/ng-template&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;class&lt;/span&gt; &lt;span class="nc"&gt;UserListComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;users&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;IUser&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="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;isLoadingService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IsLoadingService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;userService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserService&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;ngOnInit&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;users&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;isLoadingService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&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;userService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUsers&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users-loading&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What's happening?
&lt;/h2&gt;

&lt;p&gt;Behind the scenes, the &lt;code&gt;ScrollPositionDirective&lt;/code&gt; will subscribe to the appropriate &lt;code&gt;IsLoadingService&lt;/code&gt; loading state as specified by a string key you pass the directive. For example, if you setup the directive with &lt;code&gt;swScrollPosition='users-loading'&lt;/code&gt;, then the &lt;code&gt;ScrollPositionDirective&lt;/code&gt; will use the &lt;code&gt;IsLoadingService&lt;/code&gt; to subscribe to the &lt;code&gt;"users-loading"&lt;/code&gt; loading state and wait for loading to emit &lt;code&gt;false&lt;/code&gt;. When it does so, it will refresh any saved scroll position it has for that element.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Psyudo-code:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ScrollPositionDirective&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;ngAfterViewInit&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;isLoadingService&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&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="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="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;take&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="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="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="nf"&gt;refresh&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;The &lt;code&gt;ScrollPositionDirective&lt;/code&gt; will also subscribe to Angular Router navigation events. When the router emits a &lt;code&gt;ResolveEnd&lt;/code&gt; event, then the directive will grab the host element's current scroll position and save it with a key derived from the provided loading key and the current URL. For advanced usage, if there are parts of your application's URL that you want ignored by the &lt;code&gt;ScrollPositionDirective&lt;/code&gt; (e.g. specific query params), then you can provide a custom url serialization function to &lt;code&gt;ScrollPositionDirective&lt;/code&gt; by re-providing the &lt;code&gt;SW_SCROLL_POSITION_CONFIG&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Psyudo-code:&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="nf"&gt;getPositionKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userProvidedKey&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="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userProvidedKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;::`&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;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlSerializer&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;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&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;The loading key (e.g. &lt;code&gt;"users-loading"&lt;/code&gt;) is also how the &lt;code&gt;ScrollPositionDirective&lt;/code&gt; differentiates between different loading elements on the same page. And the URL is how the &lt;code&gt;ScrollPositionDirective&lt;/code&gt; differentiates between the same element on different pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;And that's pretty much it. There's some additional customization you can do for the &lt;code&gt;ScrollPositionDirective&lt;/code&gt;. For example, you can set an optional &lt;code&gt;swScrollPositionDelay&lt;/code&gt; which adds a delay (in milliseconds) before the scroll position is refreshed. You can also set &lt;code&gt;swScrollPositionSaveMode='OnDestroy'&lt;/code&gt; to have the &lt;code&gt;ScrollPositionDirective&lt;/code&gt; save its host's scroll position OnDestroy rather than OnNavigation. This is useful (and necessary) if the host component is inside of an &lt;code&gt;ngIf&lt;/code&gt; structural directive and is being shown / hidden by logic other than page navigation.&lt;/p&gt;

&lt;h3&gt;
  
  
  You can check it out at: &lt;a href="https://gitlab.com/service-work/is-loading"&gt;https://gitlab.com/service-work/is-loading&lt;/a&gt;
&lt;/h3&gt;

</description>
      <category>angular</category>
      <category>ux</category>
      <category>githunt</category>
    </item>
    <item>
      <title>A proposal to improve Angular’s ReactiveFormsModule</title>
      <dc:creator>John Carroll</dc:creator>
      <pubDate>Thu, 07 Nov 2019 09:25:05 +0000</pubDate>
      <link>https://forem.com/johncarroll/a-proposal-to-improve-angular-s-reactiveformsmodule-485d</link>
      <guid>https://forem.com/johncarroll/a-proposal-to-improve-angular-s-reactiveformsmodule-485d</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--U_11tagF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/500/1%2A38kPC9JCqfdfPMt_iuaKGA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--U_11tagF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/500/1%2A38kPC9JCqfdfPMt_iuaKGA.png" alt="" width="500" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This was originally published on &lt;a href="https://blog.angularindepth.com/a-proposal-to-improve-angulars-reactiveformsmodule-323594fe2d74"&gt;Angular In Depth&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the past, the AngularInDepth blog has included some very helpful articles showing how the ReactiveFormsModule in @angular/forms can make your life easier.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.angularindepth.com/unleash-the-power-of-forms-with-angulars-reactive-forms-d6be5918f408"&gt;Unleash the power 💪of Forms with Angular’s Reactive Forms&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.angularindepth.com/dive-into-reactive-forms-cfc9adbb4467"&gt;Dive into Reactive Forms&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.angularindepth.com/angular-nested-reactive-forms-using-cvas-b394ba2e5d0d"&gt;Angular: Nested Reactive Forms Using ControlValueAccessors(CVAs)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Today, we’re going to talk about some of the problems with the ReactiveFormsModule and discuss a proposal to fix many of these problems. The formal proposal can be found as an issue in the Angular repo &lt;a href="https://github.com/angular/angular/issues/31963"&gt;#31963&lt;/a&gt; (it &lt;em&gt;appears to be&lt;/em&gt; the fastest growing issue at the moment¹). The goal of this post is to encourage feedback from the community on improving the ReactiveFormsModule and fixing some of its longstanding issues.&lt;/p&gt;

&lt;p&gt;So you may be wondering, what issues are there with the ReactiveFormsModule? Some of the biggest issues are:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. The module is not strongly typed
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;See issues &lt;a href="https://github.com/angular/angular/issues/13721"&gt;#13721&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/27389"&gt;#27389&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/27665"&gt;#27665&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/25824"&gt;#25824&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/20040"&gt;#20040&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/17000"&gt;#17000&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/16999"&gt;#16999&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/16933"&gt;#16933&lt;/a&gt; relating to controls.&lt;/li&gt;
&lt;li&gt;See issues &lt;a href="https://github.com/angular/angular/issues/31801"&gt;#31801&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/19340"&gt;#19340&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/19329"&gt;#19329&lt;/a&gt; relating to ControlValueAccessor.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. It’s relatively complicated to *display* error messages, given how fundamental this task is.
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;See &lt;a href="https://github.com/angular/angular/issues/25824"&gt;#25824&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/24981"&gt;#24981&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/22319"&gt;#22319&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/21011"&gt;#21011&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/2240"&gt;#2240&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/9121"&gt;#9121&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/18114"&gt;#18114&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  3. It’s relatively complicated to *add* error messages, including interfacing with async services for validation (hence the need for different update strategies like “on blur" / “on submit").
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;See &lt;a href="https://github.com/angular/angular/issues/31105"&gt;#31105&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/29275"&gt;#29275&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/26683"&gt;#26683&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/23484"&gt;#23484&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/20371"&gt;#20371&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/17090"&gt;#17090&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/13920"&gt;#13920&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/9119"&gt;#9119&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/6895"&gt;#6895&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/19851"&gt;#19851&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/18871"&gt;#18871&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/10530"&gt;#10530&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/6170"&gt;#6170&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  4. Numerous annoyances with unfortunate API decisions.
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;You can’t bind a single form control to multiple inputs without ControlValueAccessor &lt;a href="https://github.com/angular/angular/issues/14451"&gt;#14451&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Can’t store arbitrary metadata on a control &lt;a href="https://github.com/angular/angular/issues/19686"&gt;#19686&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Calling reset() doesn't actually reset the control to its initial value &lt;a href="https://github.com/angular/angular/issues/20214"&gt;#20214&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/19747"&gt;#19747&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/15741"&gt;#15741&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/19251"&gt;#19251&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Must call markAsTouched() / markAsUntouched() instead of simply markTouched(boolean), which is more programmatically friendly &lt;a href="https://github.com/angular/angular/issues/23414"&gt;#23414&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/23336"&gt;#23336&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Creating custom form components is relatively complex &lt;a href="https://github.com/angular/angular/issues/12248"&gt;#12248&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;etc. &lt;a href="https://github.com/angular/angular/issues/11447"&gt;#11447&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/12715"&gt;#12715&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/10468"&gt;#10468&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/10195"&gt;#10195&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/31133"&gt;#31133&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  5. In addition to all the issues dealing with errors, the API does not offer low level programmatic control and can be frustratingly not extensible.
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;See issues &lt;a href="https://github.com/angular/angular/issues/3009"&gt;#3009&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/20230"&gt;#20230&lt;/a&gt; related to parsing/formatting user input&lt;/li&gt;
&lt;li&gt;See issues &lt;a href="https://github.com/angular/angular/issues/31046"&gt;#31046&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/24444"&gt;#24444&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/10887"&gt;#10887&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/30610"&gt;#30610&lt;/a&gt; relating to touched/dirty/etc flag changes&lt;/li&gt;
&lt;li&gt;See issues &lt;a href="https://github.com/angular/angular/issues/30486"&gt;#30486&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/31070"&gt;#31070&lt;/a&gt; &lt;a href="https://github.com/angular/angular/issues/21823"&gt;#21823&lt;/a&gt; relating to the lack of ng-submitted change tracking&lt;/li&gt;
&lt;li&gt;Ability to remove FormGroup control without emitting event &lt;a href="https://github.com/angular/angular/issues/29662"&gt;#29662&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Ability to subscribe to FormGroup form control additions / removals &lt;a href="https://github.com/angular/angular/issues/16756"&gt;#16756&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Ability to mark ControlValueAccessor as untouched &lt;a href="https://github.com/angular/angular/issues/27315"&gt;#27315&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Provide ControlValueAccessors for libraries other than @angular/forms &lt;a href="https://github.com/angular/angular/issues/27672"&gt;#27672&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fundamentally, the existing AbstractControl class does not offer the extensibility / ease of use that such an important object should have. It’s unlikely that any one API could solve everyone’s problems all of the time, but a well designed API solves most peoples problems the majority of the time and can be extended to solve problems of arbitrary complexity when needed.&lt;/p&gt;

&lt;p&gt;What follows is a proposal for a new AbstractControl API powered by a ControlEvent interface. In general, this proposal addresses issues 1, 3, 4, and 5, above. &lt;strong&gt;Importantly, this proposal is a completely community driven effort. The Angular team has not provided any feedback in regards to this proposal.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Angular issue associated with this proposal can be seen here: &lt;a href="https://github.com/angular/angular/issues/31963"&gt;https://github.com/angular/angular/issues/31963&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The github repo for this proposal can be seen here: &lt;a href="https://github.com/jorroll/reactive-forms-2-proposal"&gt;https://github.com/jorroll/reactive-forms-2-proposal&lt;/a&gt;. &lt;strong&gt;The repo includes working implementations of everything discussed here.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;A prototype module for the proposal has been published on npm at &lt;a href="https://www.npmjs.com/package/reactive-forms-module2-proposal"&gt;reactive-forms-module2-proposal&lt;/a&gt; &lt;em&gt;this is just suitable for experimentation!&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The github repo also contains &lt;a href="https://stackblitz.com/github/jorroll/reactive-forms-2-proposal"&gt;stackblitz examples&lt;/a&gt; of the proposed API in action. The &lt;a href="https://stackblitz.com/github/jorroll/reactive-forms-2-proposal"&gt;stackblitz demo&lt;/a&gt; also contains an example compatibility directive, letting the new AbstractControl be used with existing angular forms components (such as @angular/material components).&lt;/p&gt;

&lt;h3&gt;
  
  
  The proposed new AbstractControl
&lt;/h3&gt;

&lt;p&gt;The proposed AbstractControl class has a source: ControlSource&amp;lt;PartialControlEvent&amp;gt; property which is the source of truth for all operations on the AbstractControl. The ControlSource is just a modified rxjs Subject. Internally, output from source is piped to the events observable, which performs any necessary actions to determine the new AbstractControl state before emitting a new ControlEvent object describing any mutations which occurred. This means that subscribing to the events observable will get you all changes to the AbstractControl.&lt;/p&gt;

&lt;p&gt;With this relatively modest change, we can accomplish a whole host of API improvements. Let’s walk through some of them by example, before looking at the ControlEvent API itself.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Alternatively, you can scroll down and skip to the “_Diving into the ControlEvent API” section&lt;/em&gt;, below._&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Example 1
&lt;/h3&gt;

&lt;h4&gt;
  
  
  The new API is familiar for users of the old API
&lt;/h4&gt;

&lt;p&gt;It’s important that the new API be very familiar to users of the existing ReactiveFormsModule, and be 100% usable by folks who don't want to use observables.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  Example 2
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Subscribing to nested changes
&lt;/h4&gt;

&lt;p&gt;The new API allows us to subscribe to the changes of any property. When applied to ControlContainers such as FormGroup and FormArray, we can subscribe to nested child properties.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Importantly, in this example, if the address FormGroup is removed, then our subscription will emit undefined. If a new address FormGroup is added, then our subscription will emit the new value of the street FormControl.&lt;/p&gt;

&lt;p&gt;This also allows us to subscribe to controls changes of a FormGroup/ FormArray.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 3
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Linking one FormControl to another FormControl
&lt;/h4&gt;

&lt;p&gt;Here, by subscribing the source of controlB to the events of controlA, controlB will reflect all changes to controlA.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Multiple form controls can also be linked to each other, meaning that all events to one will be applied to the others. Because events are keyed to source ids, this does not cause an infinite loop.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  Example 4
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Dynamically transform a control’s value
&lt;/h4&gt;

&lt;p&gt;Here, a user is providing string date values and we want a control with javascript Date objects. We create two controls, one for holding the string values and the other for holding the Date values and we sync all changes between them. However, value changes from one to the other are transformed to be in the appropriate format.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  Example 5
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Dynamically parse user input
&lt;/h4&gt;

&lt;p&gt;Manually syncing changes between controls, as shown in Example 4, above, can be somewhat of a hassle. In most cases, we just want to parse the user input coming from an input element and sync the parsed values.&lt;/p&gt;

&lt;p&gt;To simplify this process, FormControlDirective/ FormControlNameDirective/etc accept optional "toControl", "toAccessor", and "accessorValidator" functions.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;In this example, we provide a stringToDate function which receives an input string and transforms it into a javascript Date, or null if the string isn't in the proper format. Similarly, we provide a dateToString function to sync our control's Date | null values back to the input element. We also provide an optional accessorValidator function to validate the input element's strings and provide helpful error messages to the user.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 6
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Validating the value of an AbstractControl via a service
&lt;/h4&gt;

&lt;p&gt;Here, a usernameControl is receiving text value from a user and we want to validate that input with an external service (e.g. "does the username already exist?").&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Some things to note in this example:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When a subscription to the usernameControl's value property emits, the control will already be marked pending .&lt;/li&gt;
&lt;li&gt;The API allows users to associate a call to markPending() with a specific key (in this case "usernameValidator"). This way, calling markPending(false) elsewhere (e.g. a different service validation call) will not prematurely mark &lt;em&gt;this&lt;/em&gt; service call as "no longer pending". The AbstractControl is pending so long as any key is true.&lt;/li&gt;
&lt;li&gt;Similarly, errors are stored associated with a source. In this case, the source is 'usernameValidator'. If this service adds an error, but another service later says there are no errors, that service will not accidentally overwrite this service's error. Importantly, the errors property combines all errors into one object.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Diving into the ControlEvent API
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Note: it’s important to emphasize that, for standard usage, developers don’t need to know about the existence of the&lt;/em&gt; &lt;em&gt;ControlEvent API. If you don't like observables, you can continue to simply use&lt;/em&gt; &lt;em&gt;setValue(),&lt;/em&gt; &lt;em&gt;patchValue(), etc without fear. For the purposes of this post however, lets look under the hood at what is going on!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;At the core of this AbstractControl proposal is a new ControlEvent API which controls all mutations (state changes) to the AbstractControl. It is powered by two properties on the AbstractControl: source and events.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;To change the state of an AbstractControl, you emit a new PartialControlEvent object from the source property. This object has the interface&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;When you call a method like AbstractControl#markTouched(), that method simply constructs the appropriate ControlEvent object for you and emits that object from control's ControlSource (which itself is just a modified rxjs Subject).&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Internally, the AbstractControl subscribes to output from the source property and pipes that output to a protected processEvent() method. After being processed, a new ControlEvent object containing any changes is emitted from the control's events property (so when a subscriber receives a ControlEvent from the events property, any changes have already been applied to the AbstractControl).&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;You’ll notice that only events which haven’t yet been processed by this AbstractControl are processed (i.e. !event.processed.includes(this.id)). This allows two AbstractControls to subscribe to each other's events without entering into an infinite loop (more on this later).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can check out the &lt;a href="https://github.com/jorroll/reactive-forms-2-proposal"&gt;github repo&lt;/a&gt; to see the full AbstractControl interface proposal, as well as working implementations of FormControl, FormGroup, FormArray, etc.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now that we know a bit more about the ControlEvent API, lets look at some examples it allows…&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 7
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Syncing one FormControl’s value with another
&lt;/h4&gt;

&lt;p&gt;Say we have two FormControl’s and we want them to have the same state. The new API provides a handy AbstractControl#replayState() method which returns an observable of the ControlEvent state changes which describe the current AbstractControl's state.&lt;/p&gt;

&lt;p&gt;If you subscribe one FormControl’s source to the replayState() of another form control, their values will be made equal.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The replayState() method also provides a flexible way of "saving" a control state and reapplying all, or parts of it, later.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  Example 8
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Customizing AbstractControl state changes
&lt;/h4&gt;

&lt;p&gt;Say you are changing a control’s value programmatically via a “service A”. Separately, you have another component, “component B”, watching the control’s value changes and reacting to them. For whatever reason, you want &lt;em&gt;component B&lt;/em&gt; to ignore value changes which have been triggered programmatically by &lt;em&gt;service A&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In the current ReactiveFormsModule, you can change a control's value and squelch the related observable emission by passing a "noEmit" option. Unfortunately, this will affect &lt;em&gt;everything&lt;/em&gt; watching the control's value changes. If we only want &lt;em&gt;componentB&lt;/em&gt; to ignore a values emission, we're out of luck.&lt;/p&gt;

&lt;p&gt;With this new API, we can accomplish our goal. Every method which mutates an AbstractControl’s state accepts a meta option to which you can pass an arbitrary object. If you subscribe directly to a control's events, then we can view any passed metadata.&lt;/p&gt;

&lt;p&gt;Here, the subscription in the ngOnInit() hook ignores changes with the myService: true meta property.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  Example 9
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Emitting “lifecycle hooks” from an AbstractControl
&lt;/h4&gt;

&lt;p&gt;Let’s use this proposal’s FormControlDirective implementation as an example (full code can be seen in the &lt;a href="https://github.com/jorroll/reactive-forms-2-proposal/blob/master/libs/reactive-forms-module2-proposal/src/lib/directives/form-control.directive.ts"&gt;github repo&lt;/a&gt;). Say you're creating a custom directive which exposes a public FormControl, and you wish to provide "lifecycle hooks" for subscribers of that FormControl.&lt;/p&gt;

&lt;p&gt;In the specific case of the FormControlDirective, I wanted the ability for a ControlValueAccessor connected to a FormControlDirective to be notified when the "input" control of the FormControlDirective changed.&lt;/p&gt;

&lt;p&gt;Admittedly, this is an advanced use case. But these are precisely the kinds of corner cases which the current ReactiveFormsModule handles poorly. In the case of our new API, we can simply emit a custom event from the control's source. The control won't actually do anything with the event itself, but will simply reemit it from the events observable. This allows anything subscribed to the events observable to see these custom events.&lt;/p&gt;

&lt;p&gt;In this example, a custom ControlAccessor might want to perform special setup when a new input control is connected to MyFormControlDirective.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  ControlValueAccessor
&lt;/h3&gt;

&lt;p&gt;This far, we’ve focused on changes to the AbstractControl API. But some of the problems with the ReactiveFormsModule stem from the ControlValueAccessor API. While the ControlEvent API presented thus far doesn't rely on any assumptions about the ControlValueAccessor API, and it will work just fine with the existing ControlValueAccessor interface, it also allows for a big improvement to the ControlValueAccessor API.&lt;/p&gt;

&lt;p&gt;At the risk of introducing too many new ideas at one time, lets look at how we can improve ControlValueAccessor using the new ControlEvent API...&lt;/p&gt;

&lt;p&gt;As a reminder, the existing ControlValueAccessor interface looks like&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The proposed ControlEvent API allows for a new ControlAccessor API which looks like:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;With this update, the control property of a directive implementing ControlAccessor contains an AbstractControl representing the form state of the directive (as a reminder, components are directives).&lt;/p&gt;

&lt;p&gt;This would have several advantages over the current ControlValueAccessor API:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Easier to implement
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;When the form is touched, mark the control as touched.&lt;/li&gt;
&lt;li&gt;When the form value is updated, setValue on the control.&lt;/li&gt;
&lt;li&gt;etc&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. Easier to conceptualize (admittedly subjective)
&lt;/h4&gt;

&lt;h4&gt;
  
  
  3. Allows a ControlAccessor to represent a FormGroup / FormArray / etc, rather than just a FormControl
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;A ControlAccessor can represent an address using a FormGroup.&lt;/li&gt;
&lt;li&gt;A ControlAccessor can represent people using a FormArray.&lt;/li&gt;
&lt;li&gt;etc&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  4. Very flexible
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;You can pass metadata tied to changes to the ControlAccessor via the meta option found on the new AbstractControl.&lt;/li&gt;
&lt;li&gt;You can create custom ControlEvents for a ControlAccessor.&lt;/li&gt;
&lt;li&gt;If appropriate, you can access the current form state of a ControlAccessor via a standard interface (and you can use the replayState() method to apply that state to another AbstractControl)&lt;/li&gt;
&lt;li&gt;If appropriate, a ControlAccessor could make use of a custom control object extending AbstractControl.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Example 10
&lt;/h3&gt;

&lt;h4&gt;
  
  
  A simple example using the *existing* ControlValueAccessor API
&lt;/h4&gt;

&lt;p&gt;As a refresher, here is a simple custom ControlValueAccessor implemented using the &lt;strong&gt;existing&lt;/strong&gt; interface:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  Example 11
&lt;/h3&gt;

&lt;h4&gt;
  
  
  A simple example using the *proposed* ControlAccessor API
&lt;/h4&gt;

&lt;p&gt;Here is the same component implemented using the proposed ControlAccessor interface:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;If we want to programmatically mark this ControlAccessor as touched, we can simple call this.control.markTouched(true). If we want to programmatically update the value, we can simply setValue(), etc.&lt;/p&gt;

&lt;p&gt;Lets look at a few more advanced examples of the benefits of the new ControlAccessor API:&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 12
&lt;/h3&gt;

&lt;h4&gt;
  
  
  An email address input with async validation
&lt;/h4&gt;

&lt;p&gt;Here, we create a custom form control component for an email address. Our custom component performs async validation of input email addresses using a userService. Similarly to &lt;a href="https://gist.github.com/jorroll/89ead71d52e2ad8d25fbf15c23be590d#example-6"&gt;Example 6&lt;/a&gt;, we mark the component as pending and debounce user input so that we don't make too many requests to our external service.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  Example 13
&lt;/h3&gt;

&lt;h4&gt;
  
  
  A form group control accessor
&lt;/h4&gt;

&lt;p&gt;Here, we create a “user form” component which encapsulates the input fields for our user form. We also make use of our custom email address input component from the previous example. This control accessor represents its value using a FormGroup, something which is not possible using the current ControlValueAccessor API.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;I’ll also note that, because this component is also a&lt;/em&gt; &lt;em&gt;ControlContainerAccessor, the use of&lt;/em&gt; &lt;em&gt;formControlName will pull directly from the&lt;/em&gt; &lt;em&gt;app-user-form component's&lt;/em&gt; &lt;em&gt;control property. I.e. in this case, we don't need to use a&lt;/em&gt; &lt;em&gt;[formGroup]='control' directive inside the component's template.&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  Example 14
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Nesting multiple form groups
&lt;/h4&gt;

&lt;p&gt;Here, we utilize our custom “user form” component (created in the previous example) as part of a signup form. If the user attempts to submit the form when it is invalid, we grab the first invalid control and focus it.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;While fixing the existing ReactiveFormsModule is a possibility, it would involve many breaking changes. As Renderer -&amp;gt; Renderer2 has shown, a more user friendly solution is to create a new ReactiveFormsModule2 module, deprecate the old module, and provide a compatibility layer to allow usage of the two side-by-side (including using a new FormControl with a component expecting an old ControlValueAccessor).&lt;/p&gt;

&lt;p&gt;There is also a lot more to this proposal than what was covered here.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To take a look at the code and the current state of the proposal, as well as view stackblitz examples, head on over to the repo: &lt;a href="https://github.com/jorroll/reactive-forms-2-proposal"&gt;https://github.com/jorroll/reactive-forms-2-proposal&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;To provide your support or disapproval for the proposal, head on over to its Angular issue: &lt;a href="https://github.com/angular/angular/issues/31963"&gt;https://github.com/angular/angular/issues/31963&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;To provide feedback, make Pull Requests / contributions, etc, head on over to the github repo: &lt;a href="https://github.com/jorroll/reactive-forms-2-proposal"&gt;https://github.com/jorroll/reactive-forms-2-proposal&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Things not covered: the validators API
&lt;/h3&gt;

&lt;p&gt;A lot of the issues with the current FormControl API are ultimately issues with the current ValidatorFn / ValidationErrors API.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. If a control is required, a [required] attribute is not automatically added to the appropriate element in the DOM.
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Similarly, other validators should also include DOM changes (e.g. a maxLength validator should add a [maxlength] attribute for accessibility, there are ARIA attributes which should be added for accessibility, etc).&lt;/li&gt;
&lt;li&gt;If you validate to make sure an input is a number, it’s appropriate to add a type="number" attribute on the underlying &amp;lt;input&amp;gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. Generating and displaying error messages is much harder than it should be, for such a fundamental part a Forms API.
&lt;/h4&gt;

&lt;p&gt;Ultimately, I see these as failings of the current ValidatorFn / ValidationErrors API, and should be addressed in a fix to that API. Any such fix should be included in any ReactiveFormsModule2 and can be incorporated into this AbstractControl API, but are currently out of scope for this particular proposal.&lt;/p&gt;

&lt;h3&gt;
  
  
  To give your support or disapproval to the proposal:
&lt;/h3&gt;

&lt;p&gt;head on over to &lt;a href="https://github.com/angular/angular/issues/31963"&gt;Angular issue #31963&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Footnotes
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;The “fastest growing issue” statement is based off the fact that, in 3 months, the issue has risen to the second page of the Angular repo’s issues when sorted by “thumbsup” reactions. It is the only issue on the first 4 pages to have been created in 2019.&lt;/li&gt;
&lt;/ol&gt;




</description>
      <category>angular</category>
      <category>javascript</category>
      <category>forms</category>
    </item>
    <item>
      <title>Advanced Typescript: dynamic return types without using generics</title>
      <dc:creator>John Carroll</dc:creator>
      <pubDate>Tue, 15 Oct 2019 09:21:29 +0000</pubDate>
      <link>https://forem.com/johncarroll/advanced-typescript-creating-a-generic-class-without-using-generics-2hem</link>
      <guid>https://forem.com/johncarroll/advanced-typescript-creating-a-generic-class-without-using-generics-2hem</guid>
      <description>&lt;p&gt;&lt;em&gt;This post is aimed at library authors&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;While creating the &lt;a href="https://gitlab.com/john.carroll.p/rschedule"&gt;rSchedule&lt;/a&gt; recurring date library, I ran into a problem. The library facilitates processing recurring events (e.g. "every Tuesday starting on 2019/10/10") and I created it to be date-library agnostic. Depending on what date library you are using, rSchedule's objects should return dates using the user's chosen date library. But how to accomplish this while maintaining proper typing?&lt;/p&gt;

&lt;p&gt;For example, if you use the &lt;code&gt;MomentDateAdapter&lt;/code&gt; with rSchedule, rSchedule objects should return &lt;code&gt;Moment&lt;/code&gt; dates. The obvious way to do this is via generics. This quickly becomes annoying though, as &lt;em&gt;every&lt;/em&gt; rSchedule type in a given application will be providing the same generic argument, over an over again. Want to create a new recurrence &lt;code&gt;Rule&lt;/code&gt;? You'll need to do so via &lt;code&gt;Rule&amp;lt;MomentDateAdapter&amp;gt;&lt;/code&gt;, etc.  I wondered if there was a better way...&lt;/p&gt;

&lt;p&gt;A much better approach would be if typescript realized you imported a particular date adapter, and then updated all objects to return the appropriate dates. Well, it turns out you &lt;em&gt;can&lt;/em&gt; do this.&lt;/p&gt;

&lt;p&gt;For example, if you use rSchedule after importing the &lt;code&gt;MomentDateAdapter&lt;/code&gt; (once), then &lt;code&gt;rule.occurrences()&lt;/code&gt; is typed as returning &lt;code&gt;MomentDateAdapter&lt;/code&gt; objects for the entire application. However, if you were to use rSchedule after importing the &lt;code&gt;LuxonDateAdapter&lt;/code&gt;, then &lt;code&gt;rule.occurrences()&lt;/code&gt; is typed as returning luxon &lt;code&gt;DateTime&lt;/code&gt; objects.&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;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rschedule/moment-date-adapter/setup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Rule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rschedule/core/generators&lt;/span&gt;&lt;span class="dl"&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;rule&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;Rule&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;dates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;occurrences&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toArray&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;date&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;date&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// dates is typed as `Moment[]`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;vs&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;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rschedule/luxon-date-adapter/setup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Rule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rschedule/core/generators&lt;/span&gt;&lt;span class="dl"&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;rule&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;Rule&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;dates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;occurrences&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toArray&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;date&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;date&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// dates is typed as `DateTime[]`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trick comes from the ability to turn a typescript union into a typescript intersection, combined with typescript &lt;a href="https://www.typescriptlang.org/docs/handbook/declaration-merging.html"&gt;declaration merging&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For example, given the following interface:&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;MyCustomDateTypeInterface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;std&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="nl"&gt;moment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Moment&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 produce the type &lt;code&gt;object &amp;amp; Moment&lt;/code&gt; via&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;// taken from https://stackoverflow.com/a/50375286/5490505&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;UnionToIntersection&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;U&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="nx"&gt;U&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&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="nf"&gt;extends &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;k&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;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;void&lt;/span&gt;&lt;span class="p"&gt;)&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;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;MyCustomDateType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;UnionToIntersection&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MyCustomDateTypeInterface&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;MyCustomDateTypeInterface&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;// equals `object &amp;amp; Moment`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From here, we can update &lt;code&gt;MyCustomDateType&lt;/code&gt; using declaration merging to add &lt;code&gt;key: value&lt;/code&gt; entries to &lt;code&gt;MyCustomDateTypeInterface&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, if &lt;code&gt;MyCustomDateTypeInterface&lt;/code&gt; started 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;MyCustomDateTypeInterface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;std&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We could update it to include &lt;code&gt;moment: Moment&lt;/code&gt; by declaring&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;MyCustomDateTypeInterface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;moment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Moment&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;At that point, &lt;code&gt;MyCustomDateType&lt;/code&gt; would update from being &lt;code&gt;object&lt;/code&gt; to &lt;code&gt;object &amp;amp; Moment&lt;/code&gt;! This is the black magic rSchedule uses to achieve generic status without using generics!&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>library</category>
    </item>
    <item>
      <title>Angular: How to easily display loading indicators</title>
      <dc:creator>John Carroll</dc:creator>
      <pubDate>Tue, 02 Jul 2019 21:16:30 +0000</pubDate>
      <link>https://forem.com/johncarroll/angular-how-to-easily-display-loading-indicators-4359</link>
      <guid>https://forem.com/johncarroll/angular-how-to-easily-display-loading-indicators-4359</guid>
      <description>&lt;p&gt;A common task for Angular applications is showing a loading indicator during page navigation, or showing a loading indicator while POSTing data to a server, or showing a loading indicator while &lt;code&gt;*something*&lt;/code&gt; is happening.&lt;/p&gt;

&lt;p&gt;I recently &lt;a href="https://dev.to/angular/loading-indication-in-angular-52b6"&gt;read a great post by Nils Mehlhorn&lt;/a&gt; talking about various strategies that you can use to display/manage loading indicators. It's a great read, but I don't think any of the options discussed are quite as nice as using the small &lt;a href="https://gitlab.com/service-work/is-loading"&gt;IsLoadingService&lt;/a&gt; I made for this task. Allow me to demonstrate using some examples which build off each other (some of the examples are taken from Nils' post).&lt;/p&gt;

&lt;h2&gt;
  
  
  Indicating that a page is loading
&lt;/h2&gt;

&lt;p&gt;Let's start with page loading. Many (most?) Angular application's will make use of the Angular Router to allow navigation between sections of an app. We want to display a loading indicator while navigation is pending.&lt;/p&gt;

&lt;p&gt;For our loading indicator example, we will use the &lt;a href="https://material.angular.io/components/progress-bar/overview#indeterminate"&gt;MatProgressBar component&lt;/a&gt; from the &lt;code&gt;@angular/material&lt;/code&gt; library, and display it along the top of the page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;mat-progress-bar
      *ngIf="isLoading | async"
      mode="indeterminate"
      color="warn"
      style="position: absolute; top: 0; z-index: 5000;"
    &amp;gt;
    &amp;lt;/mat-progress-bar&amp;gt;

    &amp;lt;router-outlet&amp;gt;&amp;lt;/router-outlet&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;class&lt;/span&gt; &lt;span class="nc"&gt;AppComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;isLoading&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;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;isLoadingService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IsLoadingService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Router&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;ngOnInit&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;isLoading&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;isLoadingService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="nf"&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;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;events&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="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;NavigationStart&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
            &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;NavigationEnd&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
            &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;NavigationCancel&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
            &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;NavigationError&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="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;// If it's the start of navigation, `add()` a loading indicator&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;event&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;NavigationStart&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;isLoadingService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&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;// Else navigation has ended, so `remove()` a loading indicator&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;isLoadingService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&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;In this example, we use the &lt;code&gt;IsLoadingService&lt;/code&gt; to get our loading state as an observable, save it in the class property &lt;code&gt;isLoading&lt;/code&gt;, and subscribe to it in our root component's template. When loading is &lt;code&gt;false&lt;/code&gt;, the loading indicator will disappear.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://gitlab.com/service-work/is-loading"&gt;Angular IsLoadingService&lt;/a&gt; is a small (less than 1kb minzipped) service for angular that helps us track whether "something" is loading. We can subscribe to loading state via &lt;code&gt;IsLoadingService#isLoading$()&lt;/code&gt; which returns an &lt;code&gt;Observable&amp;lt;boolean&amp;gt;&lt;/code&gt; value indicating if the state is loading or not.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To indicate that something has started loading, we can call &lt;code&gt;IsLoadingService#add()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;When something has stopped loading, we can call &lt;code&gt;IsLoadingService#remove()&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;IsLoadingService will emit &lt;code&gt;true&lt;/code&gt; from &lt;code&gt;isLoading$()&lt;/code&gt; so long as there are one or more things that are still loading.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because we want to display a loading bar during router navigation, we subscribe to angular &lt;code&gt;Router&lt;/code&gt; events. When a &lt;code&gt;NavigationStart&lt;/code&gt; event is emitted, we say that loading has started (via &lt;code&gt;add()&lt;/code&gt;). When &lt;code&gt;NavigationEnd || NavigationCancel ||NavigationError&lt;/code&gt; event is emitted, we say that loading has stopped (via &lt;code&gt;remove()&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;With this code, our app will now display a loading bar at the top of the page while navigating between routes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Note: if we called &lt;code&gt;IsLoadingService#add()&lt;/code&gt; with a &lt;code&gt;Subscription&lt;/code&gt;, &lt;code&gt;Promise&lt;/code&gt;, or &lt;code&gt;Observable&lt;/code&gt; value, we wouldn't need to call &lt;code&gt;remove()&lt;/code&gt;.

&lt;ul&gt;
&lt;li&gt;When passed a &lt;code&gt;Subscription&lt;/code&gt; or &lt;code&gt;Promise&lt;/code&gt;, &lt;code&gt;add()&lt;/code&gt; will automatically mark that the &lt;code&gt;Subcription&lt;/code&gt; or &lt;code&gt;Promise&lt;/code&gt; has stopped loading when it completes.&lt;/li&gt;
&lt;li&gt;In the case of an &lt;code&gt;Observable&lt;/code&gt; value, &lt;code&gt;add()&lt;/code&gt; will &lt;code&gt;take(1)&lt;/code&gt; from the observable and subscribe to it, noting that loading for that &lt;code&gt;Observable&lt;/code&gt; has stopped when it emits it's next value.&lt;/li&gt;
&lt;li&gt;More on this below...&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Waiting for data
&lt;/h2&gt;

&lt;p&gt;Having completed the task of adding a "page loading" indicator during router navigation, now we want to display a loading indicator when we asynchronously fetch a list of users from a service. We decide we want to trigger the same progress bar indicator that we just set up to indicate that a page is loading.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;list-component&amp;gt;
      &amp;lt;profile-component *ngFor="let user of users | async" [user]='user'&amp;gt;
      &amp;lt;/profile-component&amp;gt;
    &amp;lt;/list-component&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;class&lt;/span&gt; &lt;span class="nc"&gt;UserComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnInit&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;users&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;User&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="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;userService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;isLoadingService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IsLoadingService&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;ngOnInit&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;users&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;isLoadingService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&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;userService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAll&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;&lt;em&gt;Note how, in this example, there are no additional subscriptions or variables we need to manage.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When you call &lt;code&gt;IsLoadingService#add()&lt;/code&gt; with an &lt;code&gt;Observable&lt;/code&gt; (or &lt;code&gt;Promise&lt;/code&gt; or &lt;code&gt;Subscription&lt;/code&gt;) argument, that argument is returned.&lt;/p&gt;

&lt;p&gt;So this code...&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&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;isLoadingService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&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;userService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAll&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Is the same as this code, so far as &lt;code&gt;this.users&lt;/code&gt; is concerned.&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&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;userService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seperately (as mentioned before), when &lt;code&gt;IsLoadingService#add()&lt;/code&gt; is passed an &lt;code&gt;Observable&lt;/code&gt; value, IsLoadingService will &lt;code&gt;take(1)&lt;/code&gt; from the value and subscribe to the results. When this component is initialized, &lt;code&gt;this.isLoadingService.add( this.userService.getAll() )&lt;/code&gt; will be called triggering the "page loading" indicator we set up previously. When the observable returned from &lt;code&gt;this.userService.getAll()&lt;/code&gt; emits for the first time, IsLoadingService will know that this observable has stopped loading and update the "page loading" indicator as appropriate.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;this.userService.getAll()&lt;/code&gt; returned a promise (or subscription), we could also pass it to &lt;code&gt;this.isLoadingService.add()&lt;/code&gt; and achieve similar results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Submitting a form
&lt;/h2&gt;

&lt;p&gt;Next up, we want to let our users create a new &lt;code&gt;User&lt;/code&gt; by submitting a user creation form. When we do this, we'd like to disable the form's "submit" button and style it to indicate that the form is pending. We also decide that, in this case, we &lt;em&gt;do not&lt;/em&gt; want to display the same "page loading" progress bar as before.&lt;/p&gt;

&lt;p&gt;One way of accomplishing this task, is 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="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;form [formGroup]='userForm' novalidate&amp;gt;
      &amp;lt;mat-form-field&amp;gt;
        &amp;lt;mat-label&amp;gt; Your name &amp;lt;/mat-label&amp;gt;
        &amp;lt;input matInput formControlName='name' required /&amp;gt;
      &amp;lt;/mat-form-field&amp;gt;

      &amp;lt;button
        mat-button
        color='primary'
        [disabled]='userFormIsPending | async'
        (click)='submit()'
      &amp;gt;
        Submit
      &amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;
  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`
    .mat-button[disabled] {
      // button pending styles...
    }
  `&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;UserComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnInit&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;userForm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormGroup&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;userFormIsPending&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;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;userService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;isLoadingService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IsLoadingService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;fb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormBuilder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Router&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;ngOnInit&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;userForm&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;fb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&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="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Validators&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;required&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;userFormIsPending&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;isLoadingService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;create-user&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;async&lt;/span&gt; &lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userForm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invalid&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&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;isLoadingService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&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;usersService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createUser&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;userForm&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;toPromise&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;create-user&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&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;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;profile&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are some new concepts here. For one, instead of subscribing to "default" loading state, we're subscribing to the "create-user" loading state by passing the &lt;code&gt;key&lt;/code&gt; option to &lt;code&gt;IsLoadingService#isLoading$()&lt;/code&gt;. In this case, &lt;code&gt;userFormIsPending&lt;/code&gt; will ignore the page loading indicator state. It only cares about loading things added using &lt;code&gt;add({key: 'create-user'})&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next, when our &lt;code&gt;userForm&lt;/code&gt; is submitted, we pass the form value to &lt;code&gt;UsersService#createUser()&lt;/code&gt; which returns an observable. We transform that observable into a &lt;code&gt;Promise&lt;/code&gt;, and &lt;code&gt;await&lt;/code&gt; the result. We also pass this promise to &lt;code&gt;IsLoadingService#add()&lt;/code&gt; with the &lt;code&gt;key: 'create-user'&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If our mutation is a success, we navigate to the new user's profile page.&lt;/p&gt;

&lt;p&gt;In the component template, we subscribe to &lt;code&gt;userFormIsPending&lt;/code&gt; and, when a &lt;code&gt;submit()&lt;/code&gt; is pending, the "submit" button is automatically disabled.&lt;/p&gt;

&lt;p&gt;This example is pretty clean, but it still has this otherwise unnecessary &lt;code&gt;userFormIsPending&lt;/code&gt; property. We can improve things...&lt;/p&gt;

&lt;h3&gt;
  
  
  Simplifying form submission with &lt;a href="https://gitlab.com/service-work/is-loading#isloadingpipe"&gt;IsLoadingPipe&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;A quick way we can simplify the previous example is using the optional &lt;code&gt;IsLoadingPipe&lt;/code&gt; alongside &lt;code&gt;IsLoadingService&lt;/code&gt;. The &lt;code&gt;IsLoadingPipe&lt;/code&gt; simplifies the task of subscribing to loading state inside a component's template.&lt;/p&gt;

&lt;p&gt;The IsLoadingPipe is an angular pipe which recieves a &lt;code&gt;key&lt;/code&gt; argument and returns an &lt;code&gt;isLoading$({key})&lt;/code&gt; observable for that key. Importantly, it plays nice with Angular's change detection. Because the IsLoadingPipe returns an observable, you should use it with the Anguilar built-in &lt;code&gt;AsyncPipe&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="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;form [formGroup]='userForm' novalidate&amp;gt;
      &amp;lt;mat-form-field&amp;gt;
        &amp;lt;mat-label&amp;gt; Your name &amp;lt;/mat-label&amp;gt;
        &amp;lt;input matInput formControlName='name' required /&amp;gt;
      &amp;lt;/mat-form-field&amp;gt;

      &amp;lt;button
        mat-button
        color='primary'
        [disabled]='"create-user" | swIsLoading | async' &amp;lt;!-- change is here --&amp;gt;
        (click)='submit()'
      &amp;gt;
        Submit
      &amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;
  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`
    .mat-button[disabled] {
      // button pending styles...
    }
  `&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;UserComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnInit&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;userForm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormGroup&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="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;userService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;isLoadingService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IsLoadingService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;fb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormBuilder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Router&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;ngOnInit&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;userForm&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;fb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&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="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Validators&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;required&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;async&lt;/span&gt; &lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userForm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invalid&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&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;isLoadingService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&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;usersService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createUser&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;userForm&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;toPromise&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;create-user&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&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;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;profile&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Notice, we no longer need to have the &lt;code&gt;userFormIsPending&lt;/code&gt; property on our component.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;However, we can improve this example further. In addition to simply disabling the userForm "submit" button, we'd like to add an "is-loading" css class to the button during loading. We'd also like to add an animated css spinner to the button while it is loading.&lt;/p&gt;

&lt;h3&gt;
  
  
  Improving form submission with &lt;a href="https://gitlab.com/service-work/is-loading#isloadingdirective"&gt;IsLoadingDirective&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Using the optional &lt;code&gt;IsLoadingDirective&lt;/code&gt; alongside &lt;code&gt;IsLoadingService&lt;/code&gt;, we can improve our previous example. The &lt;code&gt;IsLoadingDirective&lt;/code&gt; allows us to easily style (and optionally disable) elements based on loading state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;form [formGroup]='userForm' novalidate&amp;gt;
      &amp;lt;mat-form-field&amp;gt;
        &amp;lt;mat-label&amp;gt; Your name &amp;lt;/mat-label&amp;gt;
        &amp;lt;input matInput formControlName='name' required /&amp;gt;
      &amp;lt;/mat-form-field&amp;gt;

      &amp;lt;button
        mat-button
        color='primary'
        swIsLoading='create-user' &amp;lt;!-- change is here --&amp;gt;
        (click)='submit()'
      &amp;gt;
        Submit
      &amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;
  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`
    .sw-is-loading {
      // button styles...
    }

    .sw-is-loading .sw-is-loading-spinner {
      // spinner styles
    }
  `&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;UserComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnInit&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;userForm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormGroup&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="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;userService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;isLoadingService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IsLoadingService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;fb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormBuilder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Router&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;ngOnInit&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;userForm&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;fb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&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="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Validators&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;required&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;async&lt;/span&gt; &lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userForm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invalid&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&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;isLoadingService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&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;usersService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createUser&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;userForm&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;toPromise&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;create-user&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&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;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;profile&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we apply the &lt;code&gt;IsLoadingDirective&lt;/code&gt; to our "submit" button via &lt;code&gt;swIsLoading='create-user'&lt;/code&gt;. We pass the &lt;code&gt;'create-user'&lt;/code&gt; key to the directive, telling it to subscribe to "create-user" loading state.&lt;/p&gt;

&lt;p&gt;When the "create-user" state is loading, the IsLoadingDirective will automatically disable the "submit" button and add a &lt;code&gt;sw-is-loading&lt;/code&gt; css class to the button. When loading stops, the button will be enabled and the &lt;code&gt;sw-is-loading&lt;/code&gt; css class will be removed.&lt;/p&gt;

&lt;p&gt;Because this is a button/anchor element, the IsLoadingDirective will also add a &lt;code&gt;sw-is-loading-spinner&lt;/code&gt; child element to the "submit" button.&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;sw-is-loading-spinner&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"sw-is-loading-spinner"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/sw-is-loading-spinner&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using css, you can add a spinner animation (&lt;a href="https://tobiasahlin.com/spinkit/"&gt;example here&lt;/a&gt;) to this element so that the button displays a pending animation during loading. All of these options are configurable. For example, if you don't want a spinner element added to buttons when they are loading, you can configure that globally by injecting new defaults or locally via &lt;code&gt;swIsLoadingSpinner=false&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Triggering multiple loading indicators at once
&lt;/h3&gt;

&lt;p&gt;As one last variation on the above, say we wanted to trigger &lt;em&gt;both&lt;/em&gt; the page loading indicator as well as the "create-user" indicator during form submission. You can accomplish this by passing an array of keys to &lt;code&gt;IsLoadingService#add()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In this case, you could update the previous example 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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnInit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userForm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invalid&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&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;isLoadingService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&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;usersService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createUser&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;userForm&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;toPromise&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&lt;/span&gt;&lt;span class="dl"&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;create-user&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="c1"&gt;// &amp;lt;-- change here&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&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;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;profile&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Calling &lt;code&gt;IsLoadingService#isLoading$()&lt;/code&gt; without a key argument is the same as calling it with &lt;code&gt;isLoading$({key: 'default'})&lt;/code&gt;, so you can trigger the "default" loading state by passing &lt;code&gt;"default"&lt;/code&gt; as a key.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;IsLoadingService has been created iteratively over the past year or so. At this point, I find that it greatly simplifies managing loading indicators in my apps.&lt;/p&gt;

&lt;h3&gt;
  
  
  You can check it out at: &lt;a href="https://gitlab.com/service-work/is-loading"&gt;https://gitlab.com/service-work/is-loading&lt;/a&gt;
&lt;/h3&gt;

</description>
      <category>angular</category>
      <category>ux</category>
      <category>githunt</category>
    </item>
    <item>
      <title>Poll: why does clicking a DEV comment link display the comment in isolation?</title>
      <dc:creator>John Carroll</dc:creator>
      <pubDate>Thu, 27 Jun 2019 14:34:36 +0000</pubDate>
      <link>https://forem.com/johncarroll/poll-why-does-clicking-a-dev-comment-link-display-the-comment-in-isolation-2k5n</link>
      <guid>https://forem.com/johncarroll/poll-why-does-clicking-a-dev-comment-link-display-the-comment-in-isolation-2k5n</guid>
      <description>&lt;p&gt;For those who aren't familiar, when you click on a comment link in DEV.to, you are taken to a page which displays only that comment in isolation (&lt;a href="https://dev.to/ben/comment/329n"&gt;example here&lt;/a&gt;). If you want more context, you can (must) click &lt;code&gt;VIEW POST&lt;/code&gt; or &lt;code&gt;VIEW FULL DISCUSSION&lt;/code&gt; links. Medium works similarly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Question: Do you like this UX?
&lt;/h2&gt;

&lt;p&gt;To me, a comment is inherently contextual.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is there ever a time when viewing a comment without context is desireable? When / why?&lt;/li&gt;
&lt;li&gt;Is there a technical reason for the current UX?

&lt;ul&gt;
&lt;li&gt;Github comment links are powered by anchor tags, so the browser simply displays the full page and scrolls to the relevant section (very intuitive).&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>discuss</category>
      <category>meta</category>
    </item>
    <item>
      <title>Angular Size Observer: a library for styling DOM elements based on their size in the browser.</title>
      <dc:creator>John Carroll</dc:creator>
      <pubDate>Wed, 12 Jun 2019 15:20:49 +0000</pubDate>
      <link>https://forem.com/johncarroll/angular-size-observer-a-library-for-styling-dom-elements-based-on-their-size-in-the-browser-2k9d</link>
      <guid>https://forem.com/johncarroll/angular-size-observer-a-library-for-styling-dom-elements-based-on-their-size-in-the-browser-2k9d</guid>
      <description>&lt;p&gt;&lt;a href="https://gitlab.com/service-work/size-observer"&gt;Angular Size Observer&lt;/a&gt; includes tools for Angular apps to monitor and react to the display size of elements in the browser. Think CSS media queries, except applying CSS based on DOM element size rather than browser screen size (tangentially related: see the &lt;a href="https://github.com/tomhodgins/element-queries-spec"&gt;CSS element query spec proposal&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The primary component of the library is a &lt;code&gt;SWObserveSize&lt;/code&gt; directive. At its most basic, simply apply the directive to a DOM element and the directive will automatically apply a special CSS class based on the element's display width, and a second CSS class based on the element's display height.&lt;/p&gt;

&lt;p&gt;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="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;div id="the-div" swObserveSize&amp;gt;
      &amp;lt;!-- div content... --&amp;gt;
    &amp;lt;/div&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;class&lt;/span&gt; &lt;span class="nc"&gt;AppComponent&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lets assume that the display size of &lt;code&gt;'the-div'&lt;/code&gt; is &lt;code&gt;1250&lt;/code&gt; px wide and &lt;code&gt;650&lt;/code&gt; px tall in the above example. SWObserveSize Directive will apply two CSS classes to &lt;code&gt;'the-div'&lt;/code&gt; element:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;'sw-container-width-xlarge'&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;'sw-container-height-small'&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the display size of &lt;code&gt;'the-div'&lt;/code&gt; changes in the browser, SWObserveSize will automatically update the CSS classes of &lt;code&gt;'the-div'&lt;/code&gt; as appropriate.&lt;/p&gt;

&lt;p&gt;By providing an optional &lt;code&gt;SW_SIZE_OBSERVER_CONFIG&lt;/code&gt; config token, you can customize the CSS classes that SWObserveSize applies to elements, along with the width/height breakpoints associated with these classes.&lt;/p&gt;

&lt;p&gt;The default config token which is automatically used if you do nothing:&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;defaultSizeObserverConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ISWSizeObserverConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;defaultWidthClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sw-container-width-xtiny&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;defaultHeightClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sw-container-height-xtiny&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;widthBreakpoints&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;Map&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sw-container-width-xlarge&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="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sw-container-width-large&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="mi"&gt;768&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sw-container-width-medium&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="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sw-container-width-small&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="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sw-container-width-xsmall&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="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sw-container-width-tiny&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;heightBreakpoints&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;Map&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sw-container-height-xlarge&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="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sw-container-height-large&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="mi"&gt;768&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sw-container-height-medium&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="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sw-container-height-small&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="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sw-container-height-xsmall&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="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sw-container-height-tiny&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This config translates to, "If an element's display width is..."&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;&amp;gt;= 1200&lt;/code&gt; px, apply the &lt;code&gt;'sw-container-width-xlarge'&lt;/code&gt; CSS class.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;gt;= 1024&lt;/code&gt; px, apply the &lt;code&gt;'sw-container-width-large'&lt;/code&gt; CSS class.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;gt;= 768&lt;/code&gt; px, apply the &lt;code&gt;'sw-container-width-medium'&lt;/code&gt; CSS class.&lt;/li&gt;
&lt;li&gt;etc ...&lt;/li&gt;
&lt;li&gt;Otherwise, apply the &lt;code&gt;'sw-container-width-xtiny'&lt;/code&gt; CSS class.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The same logic applies for the display element's height.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learn more
&lt;/h2&gt;

&lt;p&gt;You can find more information about the Angular Size Observer library, including the &lt;code&gt;SizeObserverService&lt;/code&gt; for more advanced usage, in its repo on GitLab: &lt;a href="https://gitlab.com/service-work/size-observer"&gt;https://gitlab.com/service-work/size-observer&lt;/a&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>githunt</category>
      <category>ux</category>
      <category>library</category>
    </item>
    <item>
      <title>How to share Firebase Authentication across subdomains</title>
      <dc:creator>John Carroll</dc:creator>
      <pubDate>Sat, 09 Mar 2019 01:24:53 +0000</pubDate>
      <link>https://forem.com/johncarroll/how-to-share-firebase-authentication-across-subdomains-1ka8</link>
      <guid>https://forem.com/johncarroll/how-to-share-firebase-authentication-across-subdomains-1ka8</guid>
      <description>&lt;p&gt;&lt;em&gt;This post is intended for people who are already familiar with Firebase, Firebase Authentication, and who are using Firebase in their web apps.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you use Firebase Authentication in your web apps, you may have run into the problem that Firebase only supports authentication with a single domain. This means that if your application experience is spread across multiple subdomains, your users must sign in to each subdomain separately. More problematically, your users must sign &lt;em&gt;out&lt;/em&gt; of each subdomain separately.&lt;/p&gt;

&lt;p&gt;If your applications share branding across subdomains, this could pose a security risk. It might be reasonable for a user to expect signing out of &lt;code&gt;app1.domain.com&lt;/code&gt; to also sign them out of &lt;code&gt;app2.domain.com&lt;/code&gt;. Many popular applications share signed in status across subdomains, e.g. Google itself.&lt;/p&gt;

&lt;p&gt;Having spent much longer then I intended to getting single-sign-in working across subdomains, I'm writing this post so that the next person hopefully has it easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  At a high level, this is our setup:
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;We have three applications at different domains.

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;accounts.domain.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;app1.domain.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;app2.domain.com&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;We have three Firebase Functions

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;...cloudfunctions.net/users-signin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;...cloudfunctions.net/users-checkAuthStatus&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;...cloudfunctions.net/users-signout&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In order to sign in:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Someone navigates to the &lt;code&gt;accounts.domain.com&lt;/code&gt; app&lt;/li&gt;
&lt;li&gt;They provide their authentication information&lt;/li&gt;
&lt;li&gt;That authentication information is sent to our &lt;code&gt;/users-signin&lt;/code&gt; cloud function which verifies the information and, if valid, sets a signed &lt;code&gt;__session&lt;/code&gt; cookie which contains the user's UID and returns a success indication to the client.&lt;/li&gt;
&lt;li&gt;On success, the client calls the &lt;code&gt;/users-checkAuthStatus&lt;/code&gt; cloud function which looks for the signed &lt;code&gt;__session&lt;/code&gt; cookie, extracts the user UID, and uses the UID and the &lt;code&gt;firebase-admin&lt;/code&gt; SDK to mint a custom auth token which it returns to the client.&lt;/li&gt;
&lt;li&gt;When the client receives this custom auth token, it uses it to sign in using the firebase javascript SDK.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When someone navigates to one of the other apps, say &lt;code&gt;app1.domain.com&lt;/code&gt;, the app first checks to see if the person is already signed in via Firebase Auth. If not, it calls the &lt;code&gt;/users-checkAuthStatus&lt;/code&gt; cloud function which looks for the signed &lt;code&gt;__session&lt;/code&gt; cookie and returns a custom auth token to the client if appropriate. The client then signs the user in using the custom auth token (if present).&lt;/p&gt;

&lt;p&gt;If a user for &lt;code&gt;app1.domain.com&lt;/code&gt; isn't signed in and wants to be, you send them over to &lt;code&gt;accounts.domain.com&lt;/code&gt; and then redirect them back to &lt;code&gt;app1.domain.com&lt;/code&gt; when sign in is complete.&lt;/p&gt;

&lt;p&gt;In order to sign out, a client clears the local auth state by calling &lt;code&gt;signOut()&lt;/code&gt; with the &lt;code&gt;firebase-js-sdk&lt;/code&gt; and also calls &lt;code&gt;...cloudfunctions.net/users-signout&lt;/code&gt;, which clears the &lt;code&gt;__session&lt;/code&gt; cookie. Additionally, the client needs to notify any other connected clients that the user has been signed out so that they can call &lt;code&gt;signOut()&lt;/code&gt; using the firebase-js-sdk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Actually making things work, with security.
&lt;/h2&gt;

&lt;p&gt;That's the high level overview, but in order to actually make it work, we need to deal with some stuff like cross-site-scripting, cookies, handling provider auth, etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  Signing in
&lt;/h3&gt;

&lt;p&gt;To start off, you need to decide how to verify authentication on the server.&lt;/p&gt;

&lt;p&gt;One possibility, is to authenticate someone on the &lt;code&gt;accounts.domain.com&lt;/code&gt; client normally (using Firebase Auth), and then send their idToken to the server where you use the admin SDK to verify the ID token, verify the &lt;code&gt;issuedAtTime&lt;/code&gt; associated with the ID token (e.g. make sure it was created in the last 5 minutes), and verify the provider associated with the ID token (e.g. make sure it wasn't created using a custom auth token).&lt;/p&gt;

&lt;p&gt;Another possibility, if someone is authenticating via a provider like Facebook or Twitter, is to authenticate them using that provider's SDK, retrieve the &lt;code&gt;authToken&lt;/code&gt;, and send the &lt;code&gt;authToken&lt;/code&gt; to the server where you follow the provider's instructions for verifying the token on the server.&lt;/p&gt;

&lt;p&gt;However you accomplish passing credentials to the server, if the server determines that authentication is valid, you need to set a &lt;code&gt;__session&lt;/code&gt; cookie, which is equal to the Firebase Authentication UID associated with the user, as well as set a cross-site-scripting cookie, which we'll use to protect against &lt;a href="https://en.wikipedia.org/wiki/Cross-site_scripting"&gt;cross-site-scripting attacks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;__session&lt;/code&gt; cookie should be &lt;code&gt;signed&lt;/code&gt;, &lt;code&gt;secure&lt;/code&gt; (meaning HTTPS only) and &lt;code&gt;httponly&lt;/code&gt; (meaning javascript can't access it). The cross-site-scripting cookie, we'll call it &lt;code&gt;csst&lt;/code&gt;, should be &lt;code&gt;secure&lt;/code&gt; but not &lt;code&gt;signed&lt;/code&gt; or &lt;code&gt;httponly&lt;/code&gt;. Instead, the &lt;code&gt;csst&lt;/code&gt; cookie should be created using the &lt;a href="https://github.com/auth0/node-jsonwebtoken"&gt;&lt;code&gt;jsonwebtoken&lt;/code&gt;&lt;/a&gt; library which will sign the token and record the token's subject (i.e. auth UID). The domain associated with both cookies should be your application's root domain (i.e. &lt;code&gt;domain.com&lt;/code&gt;). This ensures that the cookies are shared between subdomains.&lt;/p&gt;

&lt;p&gt;Browsers do not allow you to set cookies for another domain. This is a problem because, by default, your Firebase Functions are on a different domain from your app. You can follow &lt;a href="https://stackoverflow.com/a/51461847/5490505"&gt;these instructions&lt;/a&gt; to use Firebase Hosting &amp;amp; a custom domain for your Functions. Note also that using Firebase Hosting for your functions means that you are restricted to &lt;a href="https://firebase.google.com/docs/hosting/functions#using_cookies"&gt;only reading a &lt;code&gt;__session&lt;/code&gt; cookie on the server side&lt;/a&gt; (this won't impact our consumption of the &lt;code&gt;csst&lt;/code&gt; token). Even if you set up Firebase Functions to use your custom domain though, you might still have trouble during development: in my original setup, I was hosting my app locally but deploying Firebase Functions to a special development firebase project. This meant that the domain associated with the functions was not localhost (meaning that a function couldn't set a cookie that the client could see).&lt;/p&gt;

&lt;p&gt;There are two workarounds to this situation that I came up with.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Disable cross-site-scripting checks during development and remove the &lt;code&gt;domain&lt;/code&gt; specification from the &lt;code&gt;__session&lt;/code&gt; cookie. This works because the &lt;code&gt;__session&lt;/code&gt; cookie is only read by the Firebase Functions anyway, so it's OK if the &lt;code&gt;__session&lt;/code&gt; cookie isn't shared across subdomains (in this case, the domain associated with the &lt;code&gt;__session&lt;/code&gt; cookie will be your Firebase Functions Domain).&lt;/li&gt;
&lt;li&gt;Serve your functions locally during development. &lt;a href="https://firebase.google.com/docs/functions/local-emulator"&gt;Firebase has a instructions for using their local-emulator in the docs&lt;/a&gt;.

&lt;ul&gt;
&lt;li&gt;The local functions emulator now works with node 8+ &lt;del&gt;A problem with the local emulator is it only works for nodejs 6 (FYI, I found the current version of expressjs doesn't work in nodejs6).&lt;/del&gt;
&lt;/li&gt;
&lt;li&gt;This is no longer necessary as the local functions emulator now works with node 8+ &lt;del&gt;Another option is to build your own express app to host your functions during development. This is the route...&lt;/del&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In order to set cookies, you'll need to use an &lt;a href="https://firebase.google.com/docs/functions/http-events"&gt;&lt;code&gt;onRequest&lt;/code&gt; Firebase Function&lt;/a&gt; rather than an &lt;code&gt;onCall&lt;/code&gt; Firebase Function. You'll also need to handle &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS"&gt;CORS&lt;/a&gt; and all that jazz. I'll also call out that Google Chrome has a very unexpected quirk in that it &lt;em&gt;strips&lt;/em&gt; the &lt;code&gt;set-cookie&lt;/code&gt; header from the response if the &lt;code&gt;set-cookie&lt;/code&gt; header is for a different domain. I &lt;em&gt;hate&lt;/em&gt; this quirk. I spent hours thinking the cookie wasn't being set when, in fact, it was. See &lt;a href="https://stackoverflow.com/a/55010828/5490505"&gt;this S.O. issue&lt;/a&gt; for more information. Another FYI, when performing CORS requests you need to specify that the request is being made "with credentials" in order for the cookies to be sent. The server also needs to specify that credentials are allows on CORS requests for the credentials to be received.&lt;/p&gt;

&lt;h3&gt;
  
  
  Checking auth status
&lt;/h3&gt;

&lt;p&gt;Anyway, so after you set the &lt;code&gt;__session&lt;/code&gt; cookie and &lt;code&gt;csst&lt;/code&gt; cookies on the client. The client can now call the &lt;code&gt;/users-checkAuthStatus&lt;/code&gt; endpoint. When doing so, &lt;strong&gt;the client&lt;/strong&gt; will need to find the &lt;code&gt;csst&lt;/code&gt; cookie, extract its token, and set the token in a &lt;code&gt;Authorization: Bearer ${token}&lt;/code&gt; header for the request. When receiving the request, the &lt;code&gt;checkAuthStatus&lt;/code&gt; endpoint extracts the &lt;code&gt;csst&lt;/code&gt; token contained in the Authorization header, validates the signature on the token, and makes sure the token's subject matches the auth UID contained in the signed &lt;code&gt;__session&lt;/code&gt; cookie. Assuming everything is valid, you use the &lt;code&gt;firebase-admin&lt;/code&gt; SDK to mint a &lt;a href="https://firebase.google.com/docs/auth/web/custom-auth"&gt;custom auth token&lt;/a&gt; and send it to the client. If things are invalid, clear any old &lt;code&gt;__session&lt;/code&gt; / &lt;code&gt;csst&lt;/code&gt; cookies on the client.&lt;/p&gt;

&lt;p&gt;Finally, when the client receives one of these custom auth tokens, make sure the client signs in using &lt;a href="https://firebase.google.com/docs/auth/web/auth-state-persistence"&gt;&lt;code&gt;SESSION&lt;/code&gt; auth persistence&lt;/a&gt;. This means that the auth state will be persisted through page refreshes, but the moment that every tab associated with a domain is closed, the auth state will be cleared. Whenever an app is initialized, you'll need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check if the person is already signed in.&lt;/li&gt;
&lt;li&gt;If not, call the &lt;code&gt;/users-checkAuthStatus&lt;/code&gt; endpoint and, if you receive a custom auth token in response, use it to sign the user in.

&lt;ul&gt;
&lt;li&gt;If you receive nothing, you know the user isn't signed in.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Signing out
&lt;/h3&gt;

&lt;p&gt;When someone signs out, the client needs to call the &lt;code&gt;/users-signout&lt;/code&gt; endpoint, which will clear any &lt;code&gt;__session&lt;/code&gt; / &lt;code&gt;csst&lt;/code&gt; cookies on the client, as well as call the &lt;code&gt;signOut()&lt;/code&gt; method of the firebase-js-sdk. Additionally, you need to somehow ping any other apps which are open to let them know of the signout -- at which point they should call the &lt;code&gt;signOut()&lt;/code&gt; method of the firebase-js-sdk to sign themselves out. As a reminder, if an app is closed, the firebase-js-sdk's auth state has already been cleared.&lt;/p&gt;

&lt;p&gt;In order to ping the other open apps to tell them to signout of the firebase-js-sdk, I found the easiest method is to monitor the presence of the &lt;code&gt;csst&lt;/code&gt; cookie. If the &lt;code&gt;csst&lt;/code&gt; cookie disappears, you know that the person has signed out and your app should call the &lt;code&gt;signOut()&lt;/code&gt; method of the firebase sdk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Anyway, this wraps up my overview. Getting Firebase Authentication to work across subdomains is not super straightforward, but it is doable without &lt;em&gt;that&lt;/em&gt; much work. Unfortunately, you need to be familiar with a number of concepts such as CORS, cookies, JWTs, Firebase Authentication itself, etc.&lt;/p&gt;

&lt;p&gt;Good luck!&lt;/p&gt;

&lt;h2&gt;
  
  
  Edit (5/14/21)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;A few people have asked for examples (i.e. code) of a working setup. Obviously I can understand why this would be really helpful, but I don't have any plans to do this (i.e. I'm not willing to spend the time). If someone reading this puts together an example repo and pings me in a comment, I'll update this post with a link to your repo (and credit you) so that other people can benefit.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Another developer came up with a variation of this approach (which they feel is an improvement) and which you can read  about here, &lt;a href="https://dev.to/brianburton/cross-domain-firebase-authentication-a-simple-approach-337k"&gt;Cross-Domain Firebase Authentication: A Simple Approach&lt;/a&gt;. I haven't tested this approach at all, so I'm sharing it without endorsing it (but always good to have options, right?).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Unrelated, I recently discovered that you can (pretty easily) implement a simple query cache for Firebase Firestore which increases performance and may reduce costs. You can see an overview here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/johncarroll/surprisingly-simple-method-to-increase-firestore-performance-reduce-cost-2gcg"&gt;https://dev.to/johncarroll/surprisingly-simple-method-to-increase-firestore-performance-reduce-cost-2gcg&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>firebase</category>
    </item>
    <item>
      <title>Simple Angular "isLoading?" Service Library</title>
      <dc:creator>John Carroll</dc:creator>
      <pubDate>Sun, 30 Sep 2018 11:08:55 +0000</pubDate>
      <link>https://forem.com/johncarroll/simple-angular-isloading-service-33d0</link>
      <guid>https://forem.com/johncarroll/simple-angular-isloading-service-33d0</guid>
      <description>&lt;p&gt;&lt;a href="https://gitlab.com/service-work/is-loading" rel="noopener noreferrer"&gt;IsLoadingService&lt;/a&gt; is a simple angular service for tracking whether your app, or parts of it, are loading. By subscribing to its &lt;code&gt;isLoading$()&lt;/code&gt; method, you can easily know when to display loading indicators.&lt;/p&gt;

&lt;p&gt;At its most basic, you can import the service into your root component and use &lt;code&gt;ngIf&lt;/code&gt; + the &lt;code&gt;AsyncPipe&lt;/code&gt; to show a loading indicator during page navigation.&lt;/p&gt;

&lt;p&gt;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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;IsLoadingService&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@service-work/is-loading&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="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;mat-progress-bar
      *ngIf="isLoading | async"
      mode="indeterminate"
      color="warn"
      style="position: absolute; top: 0; z-index: 100;"
    &amp;gt;
    &amp;lt;/mat-progress-bar&amp;gt;

    &amp;lt;router-outlet&amp;gt;&amp;lt;/router-outlet&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;class&lt;/span&gt; &lt;span class="nc"&gt;AppComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;isLoading&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;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;isLoadingService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IsLoadingService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Router&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;ngOnInit&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;isLoading&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;isLoadingService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="nf"&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;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;events&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="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;NavigationStart&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
            &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;NavigationEnd&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
            &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;NavigationCancel&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
            &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;NavigationError&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="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;// if it's the start of navigation, `add()` a loading indicator&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;event&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;NavigationStart&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;isLoadingService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&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;// else navigation has ended, so `remove()` a loading indicator&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;isLoadingService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&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;If you want to manually indicate that something is loading, you can call the &lt;code&gt;loadingService.add()&lt;/code&gt; method, and then call the &lt;code&gt;loadingService.remove()&lt;/code&gt; method when loading has stopped.&lt;/p&gt;

&lt;p&gt;If you call &lt;code&gt;loadingService.add()&lt;/code&gt; multiple times (because multiple things are loading), &lt;code&gt;isLoading$()&lt;/code&gt; will remain true until you call &lt;code&gt;remove()&lt;/code&gt; an equal number of times.&lt;/p&gt;

&lt;p&gt;Internally, the IsLoadingService maintains an array of loading indicators. Whenever you call &lt;code&gt;add()&lt;/code&gt; it pushes an indicator onto the stack, and &lt;code&gt;remove()&lt;/code&gt; removes an indicator from the stack. &lt;code&gt;isLoading$()&lt;/code&gt; is true so long as there are any loading indicators on the stack.&lt;/p&gt;

&lt;p&gt;You can also pass a subscription or promise argument to &lt;code&gt;loadingService.add(subscription)&lt;/code&gt;. In this case, the loading service will push a loading indicator onto the stack, and then automatically remove it when the subscription closes / promise resolves (i.e. you don't need to manually call &lt;code&gt;remove()&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;For more advanced scenarios, you can call &lt;code&gt;add()&lt;/code&gt; with an options object containing a &lt;code&gt;key&lt;/code&gt; property. The key allows you to track the loading of different things seperately. Any truthy value can be used as a key. The key option for &lt;code&gt;add()&lt;/code&gt; is intended to be used in conjunction with &lt;code&gt;key&lt;/code&gt; option for &lt;code&gt;isLoading$()&lt;/code&gt; and &lt;code&gt;remove()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;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;class&lt;/span&gt; &lt;span class="nc"&gt;MyCustomComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnInit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;AfterViewInit&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="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;loadingService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IsLoadingService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;myCustomDataService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MyCustomDataService&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;ngOnInit&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;loadingService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MyCustomComponent&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;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;myCustomDataService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getData&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="c1"&gt;// Note, we don't need to call remove() when calling&lt;/span&gt;
    &lt;span class="c1"&gt;// add() with a subscription&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;loadingService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&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="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;getting-data&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;ngAfterViewInit&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;loadingService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MyCustomComponent&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;isDataLoading&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// the `isLoading()` method (without the `$`) returns a boolean&lt;/span&gt;
    &lt;span class="c1"&gt;// instead of an observable&lt;/span&gt;
    &lt;span class="k"&gt;return&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;loadingService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;getting-data&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Wrapping Up
&lt;/h3&gt;

&lt;p&gt;This service is very simple, but I've found myself often recreating it in different applications so I decided to package it up and publish it. It also helps get around a few Angular quirks I've run into, by debouncing and deduplicating &lt;code&gt;isLoading$&lt;/code&gt; emissions which can otherwise mess up Angular's change detection.&lt;/p&gt;

&lt;p&gt;Personally, I hook it up to a global loading indicator in my app's root component and let it automatically respond to router navigation. Additionally, when loading data from an API, I generally want to display that same app loading indicator, so I'll simply add the data fetch subscriptions with &lt;code&gt;loadingService.add(subOne)&lt;/code&gt; + &lt;code&gt;loadingService.add(subTwo)&lt;/code&gt;. Though if I wanted to display a different loading indicator for data fetching, I could use the optional &lt;code&gt;key&lt;/code&gt; argument when calling &lt;code&gt;add()&lt;/code&gt; and &lt;code&gt;isLoading$()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://gitlab.com/service-work/is-loading" rel="noopener noreferrer"&gt;You can check it out on GitLab&lt;/a&gt;
&lt;/h2&gt;

</description>
      <category>typescript</category>
      <category>angular</category>
      <category>showdev</category>
      <category>githunt</category>
    </item>
    <item>
      <title>rSchedule: a javascript recurring dates library</title>
      <dc:creator>John Carroll</dc:creator>
      <pubDate>Mon, 03 Sep 2018 15:29:31 +0000</pubDate>
      <link>https://forem.com/johncarroll/rschedule-a-javascript-recurring-dates-library-51c6</link>
      <guid>https://forem.com/johncarroll/rschedule-a-javascript-recurring-dates-library-51c6</guid>
      <description>&lt;p&gt;&lt;a href="https://gitlab.com/john.carroll.p/rschedule" rel="noopener noreferrer"&gt;rSchedule&lt;/a&gt; is a javascript library, written in typescript, for working with recurring dates and ICal recurrences. Rules can be imported / exported in &lt;a href="https://tools.ietf.org/html/rfc5545" rel="noopener noreferrer"&gt;iCalendar RFC 5545&lt;/a&gt; format, and Rule objects themselves adhere to the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators" rel="noopener noreferrer"&gt;javascript iterator protocol&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Example usage:&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;rule&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;Rule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;frequency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YEARLY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;byMonthOfYear&lt;/span&gt;&lt;span class="p"&gt;:&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;6&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;byDayOfWeek&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;SU&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MO&lt;/span&gt;&lt;span class="dl"&gt;'&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="na"&gt;start&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;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2010&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;7&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;let&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;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;occurrences&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toISOString&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="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;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;occurrences&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;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2010&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;take&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;()&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;date&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rSchedule makes use of a fairly simple &lt;code&gt;DateAdapter&lt;/code&gt; wrapper object which abstracts away from individual date library implementations, making rSchedule date library agnostic. If your chosen &lt;code&gt;DateAdapter&lt;/code&gt; supports time zones, rSchedule supports time zones.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;StandardDateAdapter&lt;/code&gt;, &lt;code&gt;LuxonDateAdapter&lt;/code&gt;, &lt;code&gt;MomentDateAdapter&lt;/code&gt;, and &lt;code&gt;MomentTZDateAdapter&lt;/code&gt; packages currently exists which provide a &lt;code&gt;DateAdapter&lt;/code&gt; complient wrapper for the standard javascript &lt;code&gt;Date&lt;/code&gt; object, as well as &lt;a href="https://momentjs.com" rel="noopener noreferrer"&gt;&lt;code&gt;moment&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://momentjs.com" rel="noopener noreferrer"&gt;&lt;code&gt;moment-timezone&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://moment.github.io/luxon/" rel="noopener noreferrer"&gt;luxon &lt;code&gt;DateTime&lt;/code&gt;&lt;/a&gt; objects. Additionally, it should be pretty easy for you to create your own &lt;code&gt;DateAdapter&lt;/code&gt; for your preferred library.&lt;/p&gt;

&lt;p&gt;rSchedule has been coded from scratch to facilitate the creation of complex recurring schedules and there's a lot packed in. &lt;a href="https://gitlab.com/john.carroll.p/rschedule" rel="noopener noreferrer"&gt;For a complete overview, check out the project on Gitlab&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Occurrence Stream Operators
&lt;/h3&gt;

&lt;p&gt;Without delving too deeply into the library, I want to call out one, very cool feature which it has: occurrence stream operators.&lt;/p&gt;

&lt;p&gt;Occurrence stream operators are inspired by rxjs pipe operators, and they allow you to combine and manipulate occurrence streams from different objects.&lt;/p&gt;

&lt;p&gt;An example from my own app:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I want to get the volunteer schedule for a particular volunteer. A volunteer schedule is the &lt;em&gt;intersection&lt;/em&gt; of the schedule a person signs up for and the occurrence schedule of the associated volunteer opportunity.&lt;/p&gt;

&lt;p&gt;Put another way, a volunteer opportunity has its own occurrence schedule. Lets say: every &lt;strong&gt;other&lt;/strong&gt; Tuesday as well as every Friday. Separately, someone might sign up to volunteer "every tuesday", as well as a few, specific, Fridays. The days that an individual is &lt;em&gt;actually&lt;/em&gt; going to volunteer though, are the intersection of these two schedules.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's how you could build this new schedule using rSchedule:&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;volunteerAvailability&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Schedule&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;opportunitySchedule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Calendar&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;volunteerSchedule&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;Calendar&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;add&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;volunteerAvailability&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nf"&gt;intersection&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;streams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;opportunitySchedule&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;Breaking this example down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;volunteerAvailability&lt;/code&gt; contains an array different schedules when a volunteer has indicated that they are available.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;opportunitySchedule&lt;/code&gt; contains a calendar describing when the volunteer opportunity actually occurs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We want to create a calendar containing the times when the volunteer is available to participate &lt;em&gt;in this volunteer opportunity&lt;/em&gt; (the &lt;code&gt;volunteerSchedule&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;volunteerSchedule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;

  &lt;span class="c1"&gt;// create a new calendar&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Calendar&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="c1"&gt;// add all times that the volunteer is available, in general&lt;/span&gt;
    &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;volunteerAvailability&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="c1"&gt;// filter to get only unique times&lt;/span&gt;
    &lt;span class="nf"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;

    &lt;span class="c1"&gt;// get the intersection of these times with the&lt;/span&gt;
    &lt;span class="c1"&gt;// volunteer opportunity's schedule&lt;/span&gt;
    &lt;span class="nf"&gt;intersection&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;streams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;opportunitySchedule&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;The resulting &lt;code&gt;volunteerSchedule&lt;/code&gt; can be iterated over using &lt;code&gt;volunteerSchedule.occurrences()&lt;/code&gt;. I can also easily group occurrences by month using &lt;code&gt;volunteerSchedule.collections({granularity: 'MONTHLY'})&lt;/code&gt; and iterate over the months.&lt;/p&gt;

&lt;p&gt;Considering each volunteer that signs up will have their own schedule, I can then combine each of &lt;em&gt;these schedules&lt;/em&gt; together to create a new calendar containing the dates which every person is scheduled to volunteer.&lt;/p&gt;

&lt;p&gt;For 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;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;volunteerSchedules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Calendar&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;scheduleOfAllVolunteers&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;Calendar&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;schedules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;volunteerSchedules&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I imagine most users will not need such powerful tools, and for them they can just use the provided &lt;code&gt;Schedule&lt;/code&gt; object which implements &lt;code&gt;RRULE&lt;/code&gt;, &lt;code&gt;EXRULE&lt;/code&gt;, &lt;code&gt;RDATE&lt;/code&gt;, and &lt;code&gt;EXDATE&lt;/code&gt; from the ICAL spec. &lt;/p&gt;

&lt;p&gt;For 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;schedule&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;Schedule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;rrules&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;frequency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WEEKLY&lt;/span&gt;&lt;span class="dl"&gt;'&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;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2012&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;end&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;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2012&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;31&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;frequency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DAILY&lt;/span&gt;&lt;span class="dl"&gt;'&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;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2011&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&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="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Holds anything I want&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="nx"&gt;schedule&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;occurrences&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;take&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;()&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;date&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  &lt;a href="https://gitlab.com/john.carroll.p/rschedule" rel="noopener noreferrer"&gt;To learn more, check out the rSchedule repo.&lt;/a&gt;
&lt;/h1&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>githunt</category>
      <category>library</category>
    </item>
  </channel>
</rss>
