<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[DCode - Learnings by Adithya Sreyaj]]></title><description><![CDATA[A full stack developer who loves working with Angular and Nodejs. Love to create new projects (but never finishes them). Apart from Angular, I have tried React,]]></description><link>https://sreyaj.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1618852864441/pnblpZoiS.png</url><title>DCode - Learnings by Adithya Sreyaj</title><link>https://sreyaj.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 16 Apr 2026 18:16:25 GMT</lastBuildDate><atom:link href="https://sreyaj.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Figma like input field in Angular using Directives]]></title><description><![CDATA[People who are familiar with Figma would have noticed that the input fields support dragging to increase or decrease values. Instead of having to click on the input field first and then type the number in, the dragging feature is really handy as you ...]]></description><link>https://sreyaj.dev/figma-like-input-field-in-angular-using-directives</link><guid isPermaLink="true">https://sreyaj.dev/figma-like-input-field-in-angular-using-directives</guid><category><![CDATA[Angular]]></category><category><![CDATA[TypeScript]]></category><dc:creator><![CDATA[Adithya Sreyaj]]></dc:creator><pubDate>Mon, 30 Dec 2024 16:23:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1735573366018/09fd4230-7dce-4609-8e22-2a6288d45663.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>People who are familiar with Figma would have noticed that the input fields support dragging to increase or decrease values. Instead of having to click on the input field first and then type the number in, the dragging feature is really handy as you can easily get the desired value by dragging.</p>
<p>We can build something like that using Angular directives. We’ll use all the latest features of Angular in this experiment.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735481574335/9e74a6f1-5a5f-4d87-92c4-2d1c4fd047e0.gif" alt class="image--center mx-auto" /></p>
<p>Let's see how we can build this.</p>
<p>We can actually do this in multiple ways. We are going to build this using directives. The way we are going to do this is by taking a very generic approach. This way, we can reuse the logic for things like resizing elements or sidebars, etc.</p>
<h2 id="heading-scrubber-directive-the-core-functionality">Scrubber Directive - the core functionality</h2>
<p>The main logic of the input can be extracted and encapsulated into a directive. The main objective is to listen to the mouse events and then translate the mouse movements into a usable value. To explain in a bit more detail:</p>
<ol>
<li><p>When the user clicks the mouse (<code>mousedown</code> event).</p>
</li>
<li><p>We start listening to the mouse movements (<code>mousemove</code> events) and use that info to translate it into usable values.</p>
</li>
<li><p>When the user releases the click, we stop the listener (<code>mouseup</code> event).</p>
</li>
</ol>
<p>We will use <code>rxjs</code> to simplify the logic a bit.</p>
<p>Here's how the pseudocode would look.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> mousedown$ = fromEvent&lt;MouseEvent&gt;(target, <span class="hljs-string">'mousedown'</span>);
<span class="hljs-keyword">const</span> mousemove$ = fromEvent&lt;MouseEvent&gt;(<span class="hljs-built_in">document</span>, <span class="hljs-string">'mousemove'</span>);
<span class="hljs-keyword">const</span> mouseup$ = fromEvent&lt;MouseEvent&gt;(<span class="hljs-built_in">document</span>, <span class="hljs-string">'mouseup'</span>);

<span class="hljs-keyword">let</span> startX = <span class="hljs-number">0</span>;
<span class="hljs-keyword">let</span> step = <span class="hljs-number">1</span>;

mousedown$
  .pipe(
     tap(<span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
       startX = event.clientX; <span class="hljs-comment">// Initial x co-ordinate where the mouse down happened</span>
    }),
    switchMap(<span class="hljs-function">() =&gt;</span> mousemove$.pipe(takeUntil(mouseup$))))
  .subscribe(<span class="hljs-function">(<span class="hljs-params">moveEvent</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> delta = startX - moveEvent.clientX;
    <span class="hljs-keyword">const</span> newValue = <span class="hljs-built_in">Math</span>.round(startValueAtTheTimeOfDrag + delta);
  });
</code></pre>
<p>Looking at the above code, it should be pretty clear what is happening. We basically save the initial <code>clientX</code> value, which is the position of the click on the X axis. Once we have that info, when the user moves the mouse, we can calculate the delta from the initial start position and the current X position.</p>
<p>We can further add more customizations like:</p>
<ol>
<li><p><strong>Sensitivity</strong> - drag distance to final value will be decided by sensitivity. Higher sensitivity values mean the final value will be big even if the movement is not that much.</p>
</li>
<li><p><strong>Step</strong> - sets the <em>stepping interval</em> when moving the mouse. If the step value is <code>1</code>, the final value is incremented/decremented in steps of <code>1</code>.</p>
</li>
<li><p><strong>Min</strong> - minimum value that will be emitted.</p>
</li>
<li><p><strong>Max</strong> - maximum value that will be emitted.</p>
</li>
</ol>
<p>Here’s how the final directive would look:</p>
<pre><code class="lang-typescript"><span class="hljs-meta">@Directive</span>({
  selector: <span class="hljs-string">"[scrubber]"</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> ScrubberDirective {
  <span class="hljs-keyword">public</span> <span class="hljs-keyword">readonly</span> scrubberTarget = input.required&lt;HTMLDivElement&gt;({
    alias: <span class="hljs-string">"scrubber"</span>,
  });

  <span class="hljs-keyword">public</span> <span class="hljs-keyword">readonly</span> step = model&lt;<span class="hljs-built_in">number</span>&gt;(<span class="hljs-number">1</span>);
  <span class="hljs-keyword">public</span> <span class="hljs-keyword">readonly</span> min = model&lt;<span class="hljs-built_in">number</span>&gt;(<span class="hljs-number">0</span>);
  <span class="hljs-keyword">public</span> <span class="hljs-keyword">readonly</span> max = model&lt;<span class="hljs-built_in">number</span>&gt;(<span class="hljs-number">100</span>);
  <span class="hljs-keyword">public</span> <span class="hljs-keyword">readonly</span> startValue = model(<span class="hljs-number">0</span>);
  <span class="hljs-keyword">public</span> <span class="hljs-keyword">readonly</span> sensitivity = model(<span class="hljs-number">0.1</span>);

  <span class="hljs-keyword">public</span> <span class="hljs-keyword">readonly</span> scrubbing = output&lt;<span class="hljs-built_in">number</span>&gt;();

  <span class="hljs-keyword">private</span> isDragging = signal(<span class="hljs-literal">false</span>);
  <span class="hljs-keyword">private</span> startX = signal(<span class="hljs-number">0</span>);
  <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> startValueAtTheTimeOfDrag = signal(<span class="hljs-number">0</span>);
  <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> destroyRef = inject(DestroyRef);
  <span class="hljs-keyword">private</span> subs?: Subscription;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) {
    effect(<span class="hljs-function">() =&gt;</span> {
      <span class="hljs-built_in">this</span>.subs?.unsubscribe();
      <span class="hljs-built_in">this</span>.subs = <span class="hljs-built_in">this</span>.setupMouseEventListener(<span class="hljs-built_in">this</span>.scrubberTarget());
    });

    <span class="hljs-built_in">this</span>.destroyRef.onDestroy(<span class="hljs-function">() =&gt;</span> {
      <span class="hljs-built_in">document</span>.body.classList.remove(<span class="hljs-string">'resizing'</span>);
      <span class="hljs-built_in">this</span>.subs?.unsubscribe();
    });
  }

  <span class="hljs-keyword">private</span> setupMouseEventListener(target: HTMLDivElement): Subscription {
    <span class="hljs-keyword">const</span> mousedown$ = fromEvent&lt;MouseEvent&gt;(target, <span class="hljs-string">"mousedown"</span>);
    <span class="hljs-keyword">const</span> mousemove$ = fromEvent&lt;MouseEvent&gt;(<span class="hljs-built_in">document</span>, <span class="hljs-string">"mousemove"</span>);
    <span class="hljs-keyword">const</span> mouseup$ = fromEvent&lt;MouseEvent&gt;(<span class="hljs-built_in">document</span>, <span class="hljs-string">"mouseup"</span>);

    <span class="hljs-keyword">return</span> mousedown$
      .pipe(
        tap(<span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
          <span class="hljs-built_in">this</span>.isDragging.set(<span class="hljs-literal">true</span>);
          <span class="hljs-built_in">this</span>.startX.set(event.clientX);
          <span class="hljs-built_in">this</span>.startValueAtTheTimeOfDrag.set(<span class="hljs-built_in">this</span>.startValue());
          <span class="hljs-built_in">document</span>.body.classList.add(<span class="hljs-string">"resizing"</span>);
        }),
        switchMap(<span class="hljs-function">() =&gt;</span>
          mousemove$.pipe(
            takeUntil(
              mouseup$.pipe(
                tap(<span class="hljs-function">() =&gt;</span> {
                  <span class="hljs-built_in">this</span>.isDragging.set(<span class="hljs-literal">false</span>);
                  <span class="hljs-built_in">document</span>.body.classList.remove(<span class="hljs-string">"resizing"</span>);
                })
              )
            )
          )
        )
      )
      .subscribe(<span class="hljs-function">(<span class="hljs-params">moveEvent</span>) =&gt;</span> {
        <span class="hljs-keyword">const</span> delta = moveEvent.clientX - <span class="hljs-built_in">this</span>.startX();
        <span class="hljs-keyword">const</span> deltaWithSensitivityCompensation = delta * <span class="hljs-built_in">this</span>.sensitivity();

        <span class="hljs-keyword">const</span> newValue =
          <span class="hljs-built_in">Math</span>.round(
            (<span class="hljs-built_in">this</span>.startValueAtTheTimeOfDrag() +
              deltaWithSensitivityCompensation) /
              <span class="hljs-built_in">this</span>.step()
          ) * <span class="hljs-built_in">this</span>.step();

        <span class="hljs-built_in">this</span>.emitChange(newValue);
        <span class="hljs-built_in">this</span>.startValue.set(newValue);
      });
  }

  <span class="hljs-keyword">private</span> emitChange(newValue: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">void</span> {
    <span class="hljs-keyword">const</span> clampedValue = <span class="hljs-built_in">Math</span>.min(<span class="hljs-built_in">Math</span>.max(newValue, <span class="hljs-built_in">this</span>.min()), <span class="hljs-built_in">this</span>.max());
    <span class="hljs-built_in">this</span>.scrubbing.emit(clampedValue);
  }
}
</code></pre>
<h2 id="heading-how-to-use-the-scrubber-directive">How to use the scrubber directive</h2>
<p>Now that we have the directive ready, let’s see how we can actually start using it.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> #<span class="hljs-attr">scrubberTarget</span> 
     [<span class="hljs-attr">scrubber</span>]=<span class="hljs-string">"scrubberTarget"</span>
     [<span class="hljs-attr">startValue</span>]=<span class="hljs-string">"this.roundedNumericValue()"</span>
     [<span class="hljs-attr">min</span>]=<span class="hljs-string">"0"</span>
     [<span class="hljs-attr">max</span>]=<span class="hljs-string">"100"</span>
     [<span class="hljs-attr">step</span>]=<span class="hljs-string">"2"</span>
     [<span class="hljs-attr">sensitivity</span>]=<span class="hljs-string">"0.2"</span>
     (<span class="hljs-attr">scrubbing</span>)=<span class="hljs-string">"this.updateValue($event)"</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>Currently, we have marked the <code>scrubberTarget</code> input as <code>input.required</code>, but we can actually make it optional and automatically use the <code>elementRef.nativeElement</code> of the host of the directive, and it would work the same. The <code>scrubberTarget</code> is exposed as an input in case you want to set a different element as the target.</p>
<p>We also add a class <code>resizing</code> to the body so that we can set the resize cursor correctly.</p>
<pre><code class="lang-scss"><span class="hljs-selector-class">.resizing</span> {
  <span class="hljs-attribute">cursor</span>: ew-resize;
  touch-action: none;
  -webkit-user-<span class="hljs-selector-tag">select</span>: none;
  user-<span class="hljs-selector-tag">select</span>: none;
}
</code></pre>
<p>We have used <code>effect</code> to start the listener, this would make sure that when in case target element changes, we set the listener on the new element.</p>
<h2 id="heading-see-it-in-action">See it in action</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735495813889/e0077ec7-b1dc-4cd4-a173-7dfd8dad3cd4.gif?width=450" alt class="image--center mx-auto" /></p>
<p>We have made a super simple Scrubber directive in Angular which can help us build input fields similar to what Figma has. Makes is super easy for user to interact with numeric inputs.</p>
<h3 id="heading-code-amp-demo">Code &amp; Demo</h3>
<p><a target="_blank" href="https://stackblitz.com/edit/figma-like-number-input-angular?file=src%2Fscrubber.directive.ts">https://stackblitz.com/edit/figma-like-number-input-angular?file=src%2Fscrubber.directive.ts</a></p>
<h2 id="heading-connect-with-me">Connect with me</h2>
<ul>
<li><p><a target="_blank" href="https://twitter.com/AdiSreyaj">Twitter</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/adisreyaj">Github</a></p>
</li>
<li><p><a target="_blank" href="https://www.linkedin.com/in/adithyasreyaj/">Linkedin</a></p>
</li>
</ul>
<p>Do add your thoughts in the comments section. Stay Safe ❤️</p>
<p><a target="_blank" href="https://www.buymeacoffee.com/adisreyaj"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639498527478/IA3aJ9R0J.png" alt="Buy me a pizza" /></a></p>
]]></content:encoded></item><item><title><![CDATA[Creating a reusable calendar component in Angular]]></title><description><![CDATA[Recently, I came across a tweet where someone posted a calendar component that was used for tracking different subscriptions. The tweet was trending because the design looks really good. I was working a project which could use a calendar component, s...]]></description><link>https://sreyaj.dev/creating-a-reusable-calendar-component-in-angular</link><guid isPermaLink="true">https://sreyaj.dev/creating-a-reusable-calendar-component-in-angular</guid><category><![CDATA[Angular]]></category><dc:creator><![CDATA[Adithya Sreyaj]]></dc:creator><pubDate>Fri, 04 Oct 2024 16:30:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1727969907438/dc42afa2-3010-41f1-a13a-667748c2b4af.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Recently, I came across a <a target="_blank" href="https://x.com/dana_daniellaa/status/1836014255138615529">tweet</a> where someone posted a calendar component that was used for tracking different subscriptions. The tweet was trending because the design looks really good. I was working a project which could use a calendar component, so I decided to give it a try.</p>
<p>So today we are going to see exactly how I created a reusable calendar component in Angular.</p>
<h2 id="heading-getting-started">Getting started</h2>
<p>We are going to be using the latest and greatest Angular version 18.0.0 (at the time of writing this blog). We are going to use standalone component, along with signals. One important dependency that we are going to add is <code>date-fns</code> library, which is a modern JavaScript date utility library. It provides the most comprehensive, yet simple toolset for working with dates.</p>
<p>Let's start by creating the component. I'm just going to add the bare minimum template code. You can get the full template code from the source code link at the end of this blog.</p>
<h3 id="heading-building-the-calendar-component">Building the calendar component</h3>
<pre><code class="lang-ts"><span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-calendar'</span>,
  standalone: <span class="hljs-literal">true</span>,
  template:<span class="hljs-string">`
    &lt;div&gt;
      &lt;header&gt;
         &lt;!---- Calendar month be displayed here --&gt;
         &lt;h1&gt;&lt;/h1&gt;
         &lt;div&gt;
            &lt;button&gt;Today&lt;/button&gt;
            &lt;button&gt;Previous&lt;/button&gt;
            &lt;button&gt;Next&lt;/button&gt;
         &lt;/div&gt;
      &lt;/header&gt;

      &lt;div&gt;

        &lt;!---- Calendar days be displayed here --&gt;
        &lt;header class="grid grid-cols-7"&gt;
          @for (item of this.dayNamesEnriched; track item.dayName) {
              &lt;div&gt;
                {{ item.dayName }}
              &lt;/div&gt;
          }
        &lt;/header&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  `</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> CalendarComponent {
  <span class="hljs-keyword">public</span> markers = input&lt;CalendarMarkerData[]&gt;([]);
  <span class="hljs-keyword">public</span> markerTpl = input&lt;TemplateRef&lt;{ $implicit: CalendarMarkerData[] }&gt;&gt;();

  <span class="hljs-keyword">readonly</span> #dayNames = [<span class="hljs-string">'Sun'</span>, <span class="hljs-string">'Mon'</span>, <span class="hljs-string">'Tue'</span>, <span class="hljs-string">'Wed'</span>, <span class="hljs-string">'Thu'</span>, <span class="hljs-string">'Fri'</span>, <span class="hljs-string">'Sat'</span>];
  <span class="hljs-keyword">protected</span> <span class="hljs-keyword">readonly</span> dayNamesEnriched = <span class="hljs-built_in">this</span>.#dayNames.map(<span class="hljs-function">(<span class="hljs-params">dayName</span>) =&gt;</span> ({
    dayName: dayName,
    isToday: dayName === format(startOfToday(), <span class="hljs-string">'eee'</span>),
  }));

  <span class="hljs-keyword">protected</span> currentDate = signal(startOfToday());
  <span class="hljs-keyword">protected</span> currentMonthWithYear = computed(<span class="hljs-function">() =&gt;</span>
    format(<span class="hljs-built_in">this</span>.currentDate(), <span class="hljs-string">'MMMM yyyy'</span>),
  );

  <span class="hljs-keyword">protected</span> <span class="hljs-keyword">readonly</span> startDateOfSelectedMonth = computed(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">return</span> startOfMonth(<span class="hljs-built_in">this</span>.currentDate());
  });
  <span class="hljs-keyword">protected</span> <span class="hljs-keyword">readonly</span> endDateOfSelectedMonth = computed(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">return</span> endOfMonth(<span class="hljs-built_in">this</span>.currentDate());
  });

  <span class="hljs-keyword">protected</span> <span class="hljs-keyword">readonly</span> days = computed(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">return</span> eachDayOfInterval({
      start: <span class="hljs-built_in">this</span>.startDateOfSelectedMonth(),
      end: <span class="hljs-built_in">this</span>.endDateOfSelectedMonth(),
    });
  });

  <span class="hljs-keyword">protected</span> <span class="hljs-keyword">readonly</span> daysEnriched = computed(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.days().map(<span class="hljs-function">(<span class="hljs-params">day, i</span>) =&gt;</span> {
      <span class="hljs-keyword">return</span> {
        day: day,
        isToday: isEqual(day, startOfToday()),
        colStartClass: i === <span class="hljs-number">0</span> ? <span class="hljs-built_in">this</span>.#COL_START_CLASSES[day.getDay()] : <span class="hljs-string">''</span>,
        markers: <span class="hljs-built_in">this</span>.#markersMap().get(<span class="hljs-built_in">this</span>.getMarkerMapKey(day)) || [],
      };
    });
  });

  <span class="hljs-keyword">readonly</span> #COL_START_CLASSES = [
    <span class="hljs-string">''</span>,
    <span class="hljs-string">'col-start-2'</span>,
    <span class="hljs-string">'col-start-3'</span>,
    <span class="hljs-string">'col-start-4'</span>,
    <span class="hljs-string">'col-start-5'</span>,
    <span class="hljs-string">'col-start-6'</span>,
    <span class="hljs-string">'col-start-7'</span>,
  ];
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> CalendarMarkerData&lt;Data = any&gt; {
  date: <span class="hljs-built_in">Date</span>;
  data: Data;
}
</code></pre>
<p>This is all the code that is required for the calendar component. We have a header section where we display the month and year, along with buttons to navigate to the previous and next month. We also have a section where we display the days of the week. We are using the <code>date-fns</code> library to get the days of the week.</p>
<h3 id="heading-adding-markers-to-the-calendar">Adding markers to the calendar</h3>
<p>Now that we have the basic calendar component, we can add markers to the calendar. Markers are used to show some data on a specific date. For example, if you have a subscription on a specific date, you can show a marker on that date. The marker data is passed to the calendar component as an input. We are using the <code>input</code> signal to pass the marker data to the calendar component.</p>
<p>The consumer has full control of how the markers look, making the calendar component highly customizable.</p>
<pre><code class="lang-ts"><span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-main'</span>,
  standalone: <span class="hljs-literal">true</span>,
  template: <span class="hljs-string">`
  &lt;div class="p-6"&gt;
    &lt;app-calendar [markers]="this.markers" [markerTpl]="markerTpl"&gt;
    &lt;/app-calendar&gt;
  &lt;/div&gt;

    &lt;ng-template #markerTpl let-data&gt;
      &lt;div class="flex items-center justify-center"&gt;
        &lt;ul class="flex flex-col gap-1"&gt;
          @for(marker of data; track marker){
            &lt;li class="flex bg-blue-100 text-center text-xs rounded-md px-2 py-1 justify-center items-center"&gt;{{marker.data.title}}&lt;/li&gt;
          }
        &lt;/ul&gt;
      &lt;/div&gt;
    &lt;/ng-template&gt;
  `</span>,
  imports: [CalendarComponent],
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> MainComponent {
  markers: CalendarMarkerData[] = [
    {
      date: addDays(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(), <span class="hljs-number">1</span>),
      data: {
        title: <span class="hljs-string">'Netflix'</span>,
      },
    },
    {
      date: addDays(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(), <span class="hljs-number">1</span>),
      data: {
        title: <span class="hljs-string">'Google One'</span>,
      },
    },
    {
      date: addDays(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(), <span class="hljs-number">5</span>),
      data: {
        title: <span class="hljs-string">'Youtube Premium'</span>,
      },
    },
  ];
}
</code></pre>
<p>We defined the marker template in the consumer and passed it to the calendar component. The calendar component will render the markers using the template provided by the consumer.</p>
<p>You can further style it to your needs, here’s some screenshots of how I did it:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727970299031/33307c03-7e6d-430d-b020-5ca7c83b4f33.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727970307976/3592cd96-723c-4e1c-9914-fdf220d3551b.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-demo">Demo</h2>
<p><strong>Stackblitz</strong>:</p>
<p><a target="_blank" href="https://stackblitz.com/edit/angular-calendar-with-date-fns?ctl=1&amp;embed=1&amp;file=src%2Fmain.ts&amp;view=preview">https://stackblitz.com/edit/angular-calendar-with-date-fns</a></p>
<h2 id="heading-connect-with-me">Connect with me</h2>
<ul>
<li><p><a target="_blank" href="https://twitter.com/AdiSreyaj">Twitter</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/adisreyaj">GitHub</a></p>
</li>
<li><p><a target="_blank" href="https://www.linkedin.com/in/adithyasreyaj/">LinkedIn</a></p>
</li>
<li><p><a target="_blank" href="https://adi.so">Portfolio</a></p>
</li>
</ul>
<p><a target="_blank" href="https://www.buymeacoffee.com/adisreyaj"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639498527478/IA3aJ9R0J.png" alt="Buy me a pizza" /></a></p>
]]></content:encoded></item><item><title><![CDATA[Deploy Node.js applications on a VPS using Coolify with Dockerfile]]></title><description><![CDATA[In the previous article, I shared how to set up Coolify and deploy a Node.js application on a VPS using Nixpacks. It is the easiest way to deploy applications using Coolify. We don't need any knowledge of Docker.
Read Here: Deploy Node.js application...]]></description><link>https://sreyaj.dev/deploy-nodejs-applications-on-a-vps-using-coolify-with-dockerfile</link><guid isPermaLink="true">https://sreyaj.dev/deploy-nodejs-applications-on-a-vps-using-coolify-with-dockerfile</guid><category><![CDATA[self-hosted]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[Docker]]></category><dc:creator><![CDATA[Adithya Sreyaj]]></dc:creator><pubDate>Tue, 23 Apr 2024 14:14:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1713602895834/024d6d38-4077-446c-aa3d-c99eb774eaff.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the previous article, I shared how to set up Coolify and deploy a Node.js application on a VPS using Nixpacks. It is the easiest way to deploy applications using Coolify. We don't need any knowledge of Docker.</p>
<p>Read Here: <a target="_blank" href="https://sreyaj.dev/deploy-nodejs-applications-on-a-vps-using-coolify">Deploy Node.js applications on a VPS using Coolify</a></p>
<p>In this article, we are going to see how to deploy a similar Node.js application using Dockerfile. This is a common way to deploy applications. We create a <strong>Dockerfile</strong>, which can build an image and the image can be deployed to any server.</p>
<h2 id="heading-deploy-using-dockerfile">Deploy using Dockerfile</h2>
<p>So, for this example, we are going to take another node application built using:</p>
<ul>
<li><p><a target="_blank" href="https://fastify.dev/">Fast and low overhead web framework, for Node.js | Fastify</a></p>
</li>
<li><p>TypeScript</p>
</li>
<li><p><a target="_blank" href="https://www.prisma.io/">Prisma | Simplify working and interacting with databases</a></p>
</li>
<li><p>PostgreSQL</p>
</li>
</ul>
<p>The difference here is that we have to create a Dockerfile for our application.</p>
<h3 id="heading-setting-up-the-postgresql-database">Setting up the PostgreSQL database</h3>
<p>Under <strong>Project</strong> &gt; <strong>Environment</strong> &gt; <strong>Resources</strong> &gt; + <strong>New</strong> &gt; <strong>Databases</strong>, you'll be able to see <strong>PostgreSQL</strong>. Start by adding a database into your project.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713453370833/8b2378cd-0898-41d0-881e-ea64c7e1c94e.png" alt class="image--center mx-auto" /></p>
<p>All the necessary information is prefilled, and we are good to go. Clicking on <strong>Start</strong> will spin up a docker container. Wait for the process to complete and once done, the status would say <strong>Running</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713601254029/645e2ee9-7e0a-4ca7-a5cc-575d0e8de616.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-deploying-the-node-application"><strong>Deploying The Node Application</strong></h3>
<p>For deploying our node application, we start by adding a new resource to your project by going to <strong>Project</strong> &gt; <strong>Environment</strong> &gt; <strong>Resources</strong> &gt; <strong>+ New &gt; Public Repository</strong></p>
<pre><code class="lang-http"><span class="hljs-attribute">https://github.com/adisreyaj/notes-api-fastify</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713600630812/e2e24f2b-7975-47f4-9910-481c813ebf03.png" alt class="image--center mx-auto" /></p>
<p>Make sure you have selected <strong>Dockerfile</strong> under <strong>Build Pack</strong> option.</p>
<p>Here's the <code>Dockerfile</code> of our sample app:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> node:<span class="hljs-number">21</span>-bullseye-slim

<span class="hljs-keyword">ENV</span> DATABASE_URL=${DATABASE_URL}
<span class="hljs-keyword">ENV</span> PORT=${PORT}

<span class="hljs-keyword">WORKDIR</span><span class="bash"> /</span>

<span class="hljs-keyword">COPY</span><span class="bash"> package*.json ./</span>
<span class="hljs-keyword">COPY</span><span class="bash"> . .</span>
<span class="hljs-keyword">ADD</span><span class="bash"> prisma .</span>

<span class="hljs-keyword">RUN</span><span class="bash"> npm ci</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm run build</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npx prisma migrate deploy</span>

<span class="hljs-keyword">EXPOSE</span> ${PORT}

<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"npm"</span>, <span class="hljs-string">"start"</span>]</span>
</code></pre>
<h4 id="heading-configure-environment-variables"><strong>Configure environment variables!</strong></h4>
<p>We need to configure the environment variables that are consumed by our application. Navigate the <strong>Environment Variables</strong> page and start adding the following variables.</p>
<p>Copy the <strong>Postgres URL (internal)</strong> from the database configuration page.</p>
<pre><code class="lang-markdown">postgres://postgres:VrKrSbxvicsQR45w0XTt@e34ds48g44o:5432/postgres
</code></pre>
<p>Add the following variables: <code>DATABASE_URL</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713601731119/fb8ce6a2-8395-429f-956f-dcb201f82ed8.png" alt class="image--center mx-auto" /></p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Make sure to mark <code>DATBASE_URL</code> as <strong>Build Variable</strong> as Prisma will run the migrations in the build phase so we need the variable to be accessible then.</div>
</div>

<p>The value provided in the <strong>Ports Exposes</strong> will be used by the application.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713601851624/432218a9-ad96-43df-976b-6e636c751db6.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-deploy-the-application"><strong>Deploy the application!</strong></h4>
<p>Once we have configured the env variables, we can go ahead a <strong>Deploy</strong> the application. Coolify shows the logs of the process, we can click on <strong>Show Debug Logs</strong> to see detailed logs.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713601166390/c22a9cd4-31c9-43f4-98cb-4dee61534b04.png" alt class="image--center mx-auto" /></p>
<p>Once the application is deployed successfully, we can view the logs of the application under the <strong>Logs</strong> tab.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713601938566/1f54f758-d248-4635-85d4-86aaec3cb628.png" alt class="image--center mx-auto" /></p>
<p>Feel free to reach out to me if you are having trouble in deploying using above mentioned steps. Happy to help.</p>
<h2 id="heading-troubleshoot">Troubleshoot</h2>
<h4 id="heading-prisma-migrate-failed">Prisma Migrate Failed</h4>
<p>If you are getting the following error:</p>
<pre><code class="lang-bash">ERROR: failed to solve: process <span class="hljs-string">"/bin/sh -c npx prisma migrate deploy"</span> did not complete successfully.
</code></pre>
<p>Make sure that you have provided the correct database URL and also <strong>Build Variable</strong> is checked.</p>
<h4 id="heading-health-check-fail">Health Check Fail</h4>
<p>Make sure the <strong>Health Checks</strong> configuration is correct. If you don't want to run health checks, make sure to turn it off.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713602589333/4cd160b3-61f4-4c95-8dc5-60b141acb5e6.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-connect-with-me">Connect with me</h2>
<ul>
<li><p><a target="_blank" href="https://twitter.com/AdiSreyaj">Twitter</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/adisreyaj">GitHub</a></p>
</li>
<li><p><a target="_blank" href="https://www.linkedin.com/in/adithyasreyaj/">LinkedIn</a></p>
</li>
<li><p><a target="_blank" href="https://adi.so">Portfolio</a></p>
</li>
</ul>
<p><a target="_blank" href="https://www.buymeacoffee.com/adisreyaj"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639498527478/IA3aJ9R0J.png" alt="Buy me a pizza" /></a></p>
]]></content:encoded></item><item><title><![CDATA[Deploy Node.js applications on a VPS using Coolify]]></title><description><![CDATA[If you are not a DevOps person, deploying applications is not going to be very easy if you are trying to do everything on your own. This is the primary reason we have a lot of managed offerings from companies like Heroku, Vercel, Fly etc.
Managed off...]]></description><link>https://sreyaj.dev/deploy-nodejs-applications-on-a-vps-using-coolify</link><guid isPermaLink="true">https://sreyaj.dev/deploy-nodejs-applications-on-a-vps-using-coolify</guid><category><![CDATA[Node.js]]></category><category><![CDATA[self-hosted]]></category><category><![CDATA[Docker]]></category><dc:creator><![CDATA[Adithya Sreyaj]]></dc:creator><pubDate>Fri, 19 Apr 2024 14:30:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1713458965480/92168e39-5571-4005-90d5-62f723d57535.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you are not a DevOps person, deploying applications is not going to be very easy if you are trying to do everything on your own. This is the primary reason we have a lot of managed offerings from companies like Heroku, Vercel, Fly etc.</p>
<p>Managed offerings make it extremely easy to deploy applications on the Internet. They eliminate the hassle for us, but this convenience comes at a cost. Recently there has been a lot of noise around the self-hosting vs managed hosting.</p>
<h3 id="heading-my-vps-configuration">My VPS configuration</h3>
<p>I've been hosting all my APIs for applications like Flagsy, Flare, Compito, etc on a VPS with the following configuration:</p>
<ol>
<li><p>1 vCPU</p>
</li>
<li><p>2GB RAM</p>
</li>
<li><p>40GB Storage</p>
</li>
<li><p>Ubuntu</p>
</li>
</ol>
<p>Worked well for my use case. The VPS cost me around $7 per month. I've now shifted to a new VPS from Oracle which provides a great config in their free tier.</p>
<ol>
<li><p>4 vCPU</p>
</li>
<li><p>24GB RAM</p>
</li>
<li><p>ARM Based</p>
</li>
<li><p>Ubuntu</p>
</li>
</ol>
<h2 id="heading-deployment-workflow-before-coolify">Deployment workflow before Coolify</h2>
<p>For all these years, I've been using GitHub Actions with a custom workflow for deploying applications to the server. The GitHub Action workflow is as follows:</p>
<ol>
<li><p>Build the Application.</p>
</li>
<li><p>Package the contents into an artifact (<code>.tar</code> file).</p>
</li>
<li><p>Send the artifact to the remote server using <code>rsync</code> (ref: <a target="_blank" href="https://www.digitalocean.com/community/tutorials/how-to-use-rsync-to-sync-local-and-remote-directories">How to use Rsync to Sync Local and Remote Directories</a><strong>)</strong></p>
</li>
<li><p>SSH into the remote server.</p>
</li>
<li><p>Extract the contents of the <code>.tar</code> file.</p>
</li>
<li><p>Install Dependencies.</p>
</li>
<li><p>Run the application using PM2 (ref: <a target="_blank" href="https://pm2.keymetrics.io/docs/usage/quick-start/">Process manager for Node.js</a>)</p>
</li>
</ol>
<h2 id="heading-deployment-using-coolify">Deployment using Coolify</h2>
<p>When I came across Coolify, I thought of giving it a try. I am aware of <a target="_blank" href="https://dokku.com">Dokku</a>, but I never really tried it because it doesn't have a UI. I work primarily as a UI developer, so having a nice UI to work with is a plus for me.</p>
<p>Coolify is an open source &amp; self-hostable Heroku / Netlify / Vercel alternative. Built by <a target="_blank" href="https://twitter.com/heyandras">Andras Bacsai (@heyandras)</a></p>
<p>It's surprisingly good. I did feel a bit lost when I first started trying it out. Eventually, everything fell into place, and I've moved all my previously hosted node applications to use Coolify instead.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713294558393/204318b2-3364-4e75-b415-c8a20e0d878b.png" alt="Different projects hosted on VPS using coolify" class="image--center mx-auto" /></p>
<h2 id="heading-getting-started-with-coolify">Getting started with Coolify</h2>
<p>To get started, you need a server, it can be a VPS, a Raspberry Pi, or any other server that you have SSH access to.</p>
<p>Coolify comes with a pretty neat one-click installation script which makes the installation super easy.</p>
<h3 id="heading-minimum-requirements-for-coolify"><strong>Minimum requirements for Coolify</strong></h3>
<ul>
<li><p>2 CPUs</p>
</li>
<li><p>2 GB memory</p>
</li>
<li><p>30+ GB of storage for the images.</p>
</li>
</ul>
<p>VPS offerings that I found to be good:</p>
<ul>
<li><p>Hetzner</p>
</li>
<li><p>OVH</p>
</li>
</ul>
<p>I've used both of these and didn't have any issues with them.</p>
<h2 id="heading-installing-coolify">Installing Coolify</h2>
<p>The first step is to SSH into your server.</p>
<pre><code class="lang-bash">ssh &lt;username&gt;@&lt;ip_address&gt;
</code></pre>
<p>Once you are inside the VPS. Enter the below command to install Coolify</p>
<pre><code class="lang-bash">curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
</code></pre>
<p>If you get an error saying: <code>Please run as root</code> using the command below:</p>
<pre><code class="lang-bash">curl -fsSL https://cdn.coollabs.io/coolify/install.sh | sudo bash
</code></pre>
<p>Once the installation is complete, you'll get a link to the Coolify dashboard. When you visit the link (Eg: <code>http://&lt;server_ip&gt;:8000</code>), you'll be asked to register yourself.</p>
<h3 id="heading-localhost-vs-remote-server">Localhost vs Remote Server</h3>
<p>On completion of registration, we will come to this screen where we need to choose whether we want everything to be on one server or not.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713296138167/2b9fedac-073e-4ccf-9f89-d0bf44381219.png" alt class="image--center mx-auto" /></p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">If you want to host Coolify on one server and deploy the applications on a different one, go with the <strong>Remote Server</strong> option. Else continue with <strong>Localhost</strong>.</div>
</div>

<p>I am using the Localhost mode. In the next step, server requirements are validated. Once validation is complete, we can get started by creating our first project.</p>
<h3 id="heading-resources">Resources</h3>
<p>We can host multiple resources in the server like <strong>Applications</strong>, <strong>Databases</strong> and <strong>Services</strong>. Each of these are called as a resource in Coolify.</p>
<p>Coolify supports deployment of applications from your private GitHub repo or any public repo. You can also deploy using Docker.</p>
<p>When it comes to databases, we have out of the box support for popular databases like <strong>PostgreSQL</strong>, <strong>MySQL</strong>, <strong>Mongo</strong>, <strong>Redis</strong> etc.</p>
<h2 id="heading-deploy-node-application-using-coolify">Deploy Node application using Coolify</h2>
<p>There are couple of ways in which we can deploy a node.js application. We will be looking at how to deploy a node application on Coolify using <strong>Nixpacks</strong>.</p>
<h3 id="heading-deploy-using-nixpacks">Deploy using Nixpacks</h3>
<p>This one will be the easiest way to deploy any node application using Coolify. The whole process is pretty straight forward.</p>
<blockquote>
<p>Nixpacks takes a source directory and produces an OCI compliant image that can be deployed anywhere.</p>
</blockquote>
<p>In simpler terms, <a target="_blank" href="https://nixpacks.com/docs">Nixpacks</a> will analyse the source folder and automatically create a <strong>Dockerfile</strong> for us. We don't have to know anything about docker.</p>
<p>We are going to be building a super simple Notes API using:</p>
<ul>
<li><p><a target="_blank" href="https://hono.dev">Hono - Ultrafast web framework for the Edges</a></p>
</li>
<li><p>TypeScript</p>
</li>
<li><p>MySQL</p>
</li>
</ul>
<h3 id="heading-setting-up-the-mysql-database">Setting up the MySQL database</h3>
<p>Under <strong>Project</strong> &gt; <strong>Environment</strong> &gt; <strong>Resources</strong> &gt; + <strong>New</strong> &gt; <strong>Databases</strong>, you'll be able to see <strong>MySQL</strong>. Start by adding a database into your project.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713445515481/97d10a66-34f5-445d-8e4b-a64b9fc90b8c.png" alt class="image--center mx-auto" /></p>
<p>All the necessary information is prefilled, and we are good to go. Clicking on <strong>Start</strong> will spin up a docker container. Wait for the process to complete and once done, the status would say <strong>Running</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713450791346/7929fedd-a64c-41e1-8d23-ab94c44c42a3.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-deploying-the-node-application">Deploying The Node Application</h3>
<p>For deploying our node application, we start by adding a new resource to your project by going to <strong>Project</strong> &gt; <strong>Environment</strong> &gt; <strong>Resources</strong> &gt; <strong>+ New &gt; Public Repository</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713451098836/17d9ad79-96d4-44f1-ba62-a066ba167e22.png" alt class="image--center mx-auto" /></p>
<p>Continue till we are asked to provide the URL of the repo:</p>
<pre><code class="lang-http"><span class="hljs-attribute">https://github.com/adisreyaj/notes-api</span>
</code></pre>
<p>Once Coolify loads the repo, we'll be asked to create a new application. We choose <strong>Nixpacks</strong> as the Build Pack and use <code>3000</code> for the port to run the app on.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713451482259/b0247c42-f4b0-453d-9330-4a0f2f11c168.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-configure-environment-variables">Configure environment variables!</h4>
<p>We need to configure the environment variables that are consumed by our application. Navigate the <strong>Environment Variables</strong> page and start adding the following variables.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🚨</div>
<div data-node-type="callout-text">If you are using <strong>Prisma</strong>, we need to expose the database to the internet. Some applications / libraries need to connect to the database during the build phase, to run migrations for example. Ref: <a target="_blank" href="https://coolify.io/docs/knowledge-base/faq#database">Frequently Asked Questions (FAQ) with Coolify</a></div>
</div>

<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713453665389/8ceb7f4f-5c87-4dec-a64c-666e90ea8615.png" alt class="image--center mx-auto" /></p>
<p>Copy the <strong>MySQL URL(Public)</strong> in case you are using <strong>Prisma</strong> or copy the <strong>MySQL URL(Internal)</strong> URL in other cases.</p>
<pre><code class="lang-markdown">mysql://mysql:BfwAmHtmTokbm70fUoFcsL7obNMPCFcW@vww348go4:3306/default
</code></pre>
<p>We can split the database URL into the below environment variables:</p>
<pre><code class="lang-bash">DATABASE_HOST=vww348go4
DATABASE_NAME=default
DATABASE_PASSWORD=BfwAmHtmTokbm70fUoFcsL7obNMPCFcW
DATABASE_PORT=3306
DATABASE_USER=mysql
PORT=3000
</code></pre>
<p>You can either add all of these variables at once using <strong>Developer View</strong> or create them individually.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713452045363/e019373a-2e6d-4b3b-b6ec-e49913fd3dbe.png" alt class="image--center mx-auto" /></p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">There is an option called <strong>Build Variable</strong> which will make the env available during build phase. For this particular example, we don't need to check the option. But for <strong>Prisma</strong> and other libraries, we need to make the environment variables accessible at build time.</div>
</div>

<h4 id="heading-deploy-the-application">Deploy the application!</h4>
<p>Once we have configured the env variables, we can go ahead a <strong>Deploy</strong> the application. Coolify shows the logs of the process, we can click on <strong>Show Debug Logs</strong> to see detailed logs.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713452431902/2d9f6970-8ecd-449d-bd9b-1c98bc4232be.png" alt class="image--center mx-auto" /></p>
<p>Once the application is deployed successfully, we can view the logs of the application under the <strong>Logs</strong> tab.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713452583052/401e1970-e6c6-42d5-9fe0-0518f9b71fc3.png" alt class="image--center mx-auto" /></p>
<p>You should be able to visit the domain provided and see the app live.</p>
<h3 id="heading-troubleshooting">Troubleshooting</h3>
<h4 id="heading-providing-custom-commands">Providing custom commands</h4>
<p>If you think that <strong>Nixpacks</strong> picked wrong commands for install, build, or start, you can customize it in the <strong>Configuration &gt; General</strong> section.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713453094419/523ce239-c945-4ebe-ae31-b08a4805b05a.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-database-not-reachable">Database not reachable</h4>
<p>If you see any errors related to database not reachable, make sure to double check the env variables and make sure they are correct.</p>
<p>Also, if you are using <strong>Prisma</strong> or other libraries which might access database at build time, make sure to mark the environment variables as <strong>Build Variable</strong>.</p>
<p>For <strong>Prisma</strong> with <strong>Nixpacks</strong>, we need to make the database publicly accessible over the internet.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713454544952/30776ee8-18e5-47fe-a448-24264c4eccbc.png" alt class="image--center mx-auto" /></p>
<p>Feel free to reach out to me if you are having trouble in deploying using above mentioned steps. Happy to help.</p>
<h2 id="heading-follow-up">Follow up</h2>
<p>If you want to deploy Node.js apps using Dockerfile instead, visit this blogpost:</p>
<p><a target="_blank" href="https://sreyaj.dev/deploy-nodejs-applications-on-a-vps-using-coolify-with-dockerfile">Deploy Node.js applications on a VPS using Coolify with Dockerfile (sreyaj.dev)</a></p>
<h2 id="heading-connect-with-me">Connect with me</h2>
<ul>
<li><p><a target="_blank" href="https://twitter.com/AdiSreyaj">Twitter</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/adisreyaj">GitHub</a></p>
</li>
<li><p><a target="_blank" href="https://www.linkedin.com/in/adithyasreyaj/">LinkedIn</a></p>
</li>
<li><p><a target="_blank" href="https://adi.so">Portfolio</a></p>
</li>
</ul>
<p><a target="_blank" href="https://www.buymeacoffee.com/adisreyaj"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639498527478/IA3aJ9R0J.png" alt="Buy me a pizza" /></a></p>
]]></content:encoded></item><item><title><![CDATA[Architecting A Highly Dynamic Card List In Angular]]></title><description><![CDATA[Let's look at one way to architect Angular's highly customizable and dynamic card list component. The goal is to make it easier to render different kinds of card lists using one single component.
Dynamic Card List Component
The component should be dy...]]></description><link>https://sreyaj.dev/architecting-a-highly-dynamic-card-list-in-angular</link><guid isPermaLink="true">https://sreyaj.dev/architecting-a-highly-dynamic-card-list-in-angular</guid><category><![CDATA[Angular]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[UI]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Adithya Sreyaj]]></dc:creator><pubDate>Sat, 14 Jan 2023 09:44:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1673688968762/4a1eb1e8-73a9-47cf-8767-92235368666a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Let's look at one way to architect Angular's highly customizable and dynamic card list component. The goal is to make it easier to render different kinds of card lists using one single component.</p>
<h2 id="heading-dynamic-card-list-component">Dynamic Card List Component</h2>
<p>The component should be dynamic enough to render different designs while maintaining a common structure. The consumer should be able to use the component and provide just the config and a data source, the rest is all gonna be taken care of by our dynamic card list component.</p>
<p><a target="_blank" href="https://examples.adi.so/examples/dynamic-card-list"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673551279830/6898ac5f-b772-4a38-8c6a-29cd260d8c32.png" alt class="image--center mx-auto" /></a></p>
<p>For table-like cards, if you want to render display different data in different designs; after a point, it becomes very difficult with dedicated card lists for each of the use cases. Having a single card list component that is dynamic enough to render a wide variety of designs would make things much easier.</p>
<h2 id="heading-features">Features</h2>
<ul>
<li><p>Highly customizable 🧱</p>
</li>
<li><p>Re-usable ♻️</p>
</li>
<li><p>Easy to use 🔌</p>
</li>
<li><p>Less boilerplate code to maintain 🛠️</p>
</li>
<li><p>Consistency in design 📐</p>
</li>
</ul>
<h2 id="heading-concepts">Concepts</h2>
<p>There are a couple of concepts that we use to build this out. We also make good use of Angular's strong Dependency Injection (DI) system. We'll see how we can provide custom components and dynamically create and attach them to the view. We use concepts like Dynamic Component Creation, Injectors, Injection Tokens, etc.</p>
<h3 id="heading-renderers">Renderers</h3>
<p>So if we consider the card like a table, then the columns are where we display the data. So each of these columns can display different types of information like text, images, buttons, etc. So we can have different renderers for each of these types. For example, we can have a text renderer that will render text, an image renderer that will render an image, etc.</p>
<h4 id="heading-core-renderers">Core Renderers</h4>
<p>We will have some core renderers which will be used by default. For example,</p>
<ul>
<li><p>Text Renderer</p>
</li>
<li><p>Badge Renderer</p>
</li>
</ul>
<p>We can identify some common use cases for renderers and create components that become part of Core Renderers.</p>
<h4 id="heading-custom-renderers">Custom Renderers</h4>
<p>If there are use cases that are not covered by the core renderers, then we can create custom renderers. For example, we need to display a user's name with the profile picture. So we can create a custom renderer for this use case. Look at the <a target="_blank" href="https://github.com/adisreyaj/sreyaj/blob/main/libs/examples/dynamic-card-list/src/lib/components/custom-renderers/name-with-avatar/name-with-avatar-column-renderer.component.ts">NameWithAvatarColumnRendererComponent</a></p>
<h3 id="heading-renderers-registry">Renderers Registry</h3>
<p>So we need a way to keep track of the renderers that we will create and use in the application. Users can create a lot of custom renderers according to their use cases. We use a <code>type</code> to identify the renderer. So all the renders that are created should have a unique type. We can use this type <code>type</code> associated with them.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673635390453/98bf8bd3-c814-441f-b69b-7001203222c2.png" alt class="image--center mx-auto" /></p>
<p>We use a <strong>Service</strong> as our registry (<code>ColumnRenderersRegistryService</code>). We expose a <code>Map</code> to store the type to renderer mapping. We expose two methods:</p>
<ul>
<li><p><code>registerRenderers</code> to register renderers</p>
</li>
<li><p><code>lookup</code> to lookup a renderer by type</p>
</li>
</ul>
<h3 id="heading-registering-renderers">Registering Renderers</h3>
<p>There are methods exposed to register default renderers and custom renderers. The default renderers are registered in the constructor of the <code>sreyaj-exp-card-list</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> registerDefaultRenderers = (): <span class="hljs-function"><span class="hljs-params">void</span> =&gt;</span> {
    registerCustomRenderers([
        TextColumnRendererComponent,
        BadgeColumnRendererComponent,
    ]);
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> registerCustomRenderers = (
    renderers: ListColumnRendererConstructor[]
): <span class="hljs-function"><span class="hljs-params">void</span> =&gt;</span> {
    <span class="hljs-keyword">const</span> rendererLookupService = inject(ColumnRenderersRegistryService);
    rendererLookupService.registerRenderers(renderers);
};
</code></pre>
<p>We inject the <code>ColumnRenderersRegistryService</code> and use it to register the renderers.</p>
<h3 id="heading-custom-decorator">Custom Decorator</h3>
<p>We create a custom decorator called <code>ListColumnRenderer</code> to add some metadata information about the renderer component like its <code>type</code> which is the unique identifier of the renderer in the registry.</p>
<p>We could totally do this without a decorator, but we would have to manually set the type on each renderer component. It would look like this:</p>
<pre><code class="lang-typescript"><span class="hljs-meta">@Component</span>()
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> TextColumnRendererComponent <span class="hljs-keyword">extends</span> ListColumnRendererBase {
  <span class="hljs-keyword">public</span> <span class="hljs-keyword">type</span> = CoreListColumnRendererType.Text;
}
</code></pre>
<p>With the decorator, we just remove this explicit variable assignment. Also, the decorator helps us easily identify renderer components from normal components.</p>
<p>Class decorator is a function that receives the constructor as the argument. Since we want to pass in some arguments to the decorator itself we use a decorator factory.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ListColumnRenderer</span>(<span class="hljs-params">
  metadata: ListColumnRendererMetadata
</span>) </span>{
  <span class="hljs-keyword">return</span> (<span class="hljs-keyword">constructor</span>: ListColumnRendererConstructor): void =&gt; {
    constructor.type = metadata.type;
  };
}
</code></pre>
<p>The default type of a <strong>Component</strong> is <code>Type</code>. For us, we have extra metadata so we say our components are of the type <code>ListColumnRendererConstructor</code> which is an extended <code>Type</code> with some additional properties like <code>type</code> (more can be added later).</p>
<pre><code class="lang-ts"><span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> ListColumnRendererConstructor <span class="hljs-keyword">extends</span> Type&lt;unknown&gt;,
    ListColumnRendererMetadata {}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> ListColumnRendererMetadata {
  <span class="hljs-keyword">type</span>: <span class="hljs-built_in">string</span>;
}
</code></pre>
<h2 id="heading-implementation">Implementation</h2>
<h3 id="heading-main-component">Main Component</h3>
<p>There is a main component <code>sreyaj-exp-card-list</code> which is the component the consumers will use. The component takes in two inputs:</p>
<ol>
<li><p><code>columnConfig</code> - An array of column config that is used to render each column.</p>
</li>
<li><p><code>dataSource</code> - The data source which drives the component.</p>
</li>
</ol>
<p>The column config is what drives the view part of the component. Here is how you define it:</p>
<pre><code class="lang-typescript">[
  {
    id: <span class="hljs-string">"name"</span>,
    display: CustomRenderers.NameWithAvatar,
    width: <span class="hljs-number">2</span>
  },
  {
    id: <span class="hljs-string">"email"</span>,
    display: CoreListColumnRendererType.Text,
    width: <span class="hljs-number">4</span>
  }
]
</code></pre>
<p>The <code>id</code> is what connects the view and the data. The data source should contain an object with the keys mentioned <code>id</code> in the column config.</p>
<p>The <code>display</code> is the type of renderer. It can be a core renderer or a custom renderer. This key will be used to look up the corresponding renderer to be used for that particular column.</p>
<p>Inside the main component, it just loops through the columns:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">sreyaj-exp-card-list-column-renderer</span> [<span class="hljs-attr">columnConfig</span>]=<span class="hljs-string">"column"</span> [<span class="hljs-attr">data</span>]=<span class="hljs-string">"item"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">sreyaj-exp-card-list-column-renderer</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>We pass the column information and the data for the column from the data source to a child component <code>sreyaj-exp-card-list-column-renderer</code> which takes care of dynamically creating and attaching the corresponding renderer to the view.</p>
<h3 id="heading-attaching-renderers-to-the-view">Attaching Renderers to the View</h3>
<p>This magic happens in the <code>sreyaj-exp-card-list-column-renderer</code> component. This is how we do it:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">if</span> (!<span class="hljs-built_in">this</span>.columnConfig || !<span class="hljs-built_in">this</span>.data) {
  <span class="hljs-keyword">return</span>;
}
<span class="hljs-comment">// Get the corresponding renderer for the display type</span>
<span class="hljs-keyword">const</span> renderer = <span class="hljs-built_in">this</span>.rendererLookupService.lookup(<span class="hljs-built_in">this</span>.columnConfig?.display);
<span class="hljs-keyword">if</span> (renderer) {
  <span class="hljs-comment">// Dynamically create the component and pass the data with the help of Injection Token</span>
  <span class="hljs-built_in">this</span>.vcr.createComponent(renderer, {
    injector: Injector.create({
      providers: [
        {
          provide: COLUMN_RENDERER_DATA,
          useValue: <span class="hljs-built_in">this</span>.data[<span class="hljs-built_in">this</span>.columnConfig.id]
        }
      ],
      parent: <span class="hljs-built_in">this</span>.parentInjector
    })
  });
}
</code></pre>
<p>If I were to breakdown what is happening inside the component:</p>
<ol>
<li><p>Get the renderer corresponding to the <code>type</code> passed in the <code>columnConfig</code>.</p>
</li>
<li><p>Use <code>ViewContainerRef</code> to dynamically create the component using the <code>createComponent</code> method.</p>
</li>
<li><p>To the <code>createComponent</code> method, we pass the renderer class along with an <code>injector</code> that holds the data.</p>
</li>
<li><p><code>COLUMN_RENDERER_DATA</code> injection token is provided the data for that particular column.</p>
</li>
</ol>
<h3 id="heading-inside-a-renderer">Inside a Renderer</h3>
<p>Let's look at <code>TextColumnRendererComponent</code> which is part of the Core Renderers.</p>
<pre><code class="lang-typescript"><span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">"sreyaj-exp-text-column-renderer"</span>,
  template: <span class="hljs-string">`
    &lt;div&gt;
      {{ data }}
      &lt;!-- Use the data here --&gt;
    &lt;/div&gt;
  `</span>,
  standalone: <span class="hljs-literal">true</span>
})
<span class="hljs-meta">@ListColumnRenderer</span>({
  <span class="hljs-keyword">type</span>: CoreListColumnRendererType.Text
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> TextColumnRendererComponent <span class="hljs-keyword">extends</span> ListColumnRendererBase {}
</code></pre>
<p>The <code>data</code> property contains the data passed from the injector. Wondering how we get it? For that, let's look at <code>ListColumnRendererBase</code> :</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">class</span> ListColumnRendererBase&lt;ColumnDataType = unknown&gt; {
  <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">readonly</span> <span class="hljs-keyword">type</span>: CoreListColumnRendererType;
  <span class="hljs-comment">// We inject the token here</span>
  <span class="hljs-keyword">public</span> <span class="hljs-keyword">readonly</span> data: ColumnDataType =
    inject&lt;ColumnDataType&gt;(COLUMN_RENDERER_DATA);
}
</code></pre>
<p>We can see that the <code>COLUMN_RENDERER_DATA</code> <strong>InjectionToken</strong> is injected inside the class and so the component gets the value passed while creating the component. If we don't use the new <code>inject</code> method, it would look something like this:</p>
<pre><code class="lang-typescript"><span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'sreyaj-exp-text-column-renderer'</span>,
  template: <span class="hljs-string">`
    &lt;div&gt;
      {{ data }}
    &lt;/div&gt;
  `</span>,
  standalone: <span class="hljs-literal">true</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> TextColumnRendererComponent {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-meta">@Inject</span>(COLUMN_RENDERER_DATA) <span class="hljs-keyword">public</span> data: unknown</span>){}
}
</code></pre>
<h3 id="heading-references">References</h3>
<p>The idea of this is taken from the OSS project called <a target="_blank" href="https://www.hypertrace.org?utm_source=sreyaj.dev">Hypertrace</a> which is an Open source distributed tracing platform from <a target="_blank" href="https://traceable.ai?utm_source=sreyaj.dev">Traceable</a> (The company I'm currently working in).</p>
<p>The project has a table implementation that does exactly this. I've just used the same concept to create a card list instead of a table (also use standalone components).</p>
<p>The table is far more capable as it even takes care of sorting, pagination, etc. You can look at the codebase to get an idea of how the <a target="_blank" href="https://github.com/hypertrace/hypertrace-ui/tree/main/projects/components/src/table">table</a> is implemented.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Title</td><td>Link</td></tr>
</thead>
<tbody>
<tr>
<td>Source Code</td><td><a target="_blank" href="https://github.com/adisreyaj/sreyaj/tree/main/libs/examples/dynamic-card-list">https://github.com/adisreyaj/sreyaj/tree/main/libs/examples/dynamic-card-list</a></td></tr>
<tr>
<td>Demo</td><td><a target="_blank" href="https://examples.adi.so/examples/dynamic-card-list">https://examples.adi.so/examples/dynamic-card-list</a></td></tr>
<tr>
<td>Hypertrace Repo</td><td><a target="_blank" href="https://github.com/hypertrace/hypertrace-ui">https://github.com/hypertrace/hypertrace-ui</a></td></tr>
</tbody>
</table>
</div><h2 id="heading-connect-with-me">Connect with me</h2>
<ul>
<li><a target="_blank" href="https://twitter.com/AdiSreyaj">Twitter</a></li>
<li><a target="_blank" href="https://github.com/adisreyaj">Github</a></li>
<li><a target="_blank" href="https://www.linkedin.com/in/adithyasreyaj/">Linkedin</a></li>
<li><a target="_blank" href="https://adi.so">Portfolio</a></li>
</ul>
<p>Have any thoughts or questions? shoot'em below in the comments.
Stay Safe ❤️</p>
<p><a target="_blank" href="https://www.buymeacoffee.com/adisreyaj"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639498527478/IA3aJ9R0J.png" alt="Buy me a pizza" /></a>ments. Stay Safe ❤️</p>
]]></content:encoded></item><item><title><![CDATA[Super Simple Select Implementation Using Angular CDK Selection Model]]></title><description><![CDATA[Building out Selection feature for tables/list can be challenging at times, because of a lot of corner case that needs to be handled. Here's how we can build one without writing much logic using the Angular CDK's SelectionModel utility.

Angular CDK ...]]></description><link>https://sreyaj.dev/select-implementation-using-angular-cdk-selection-model</link><guid isPermaLink="true">https://sreyaj.dev/select-implementation-using-angular-cdk-selection-model</guid><category><![CDATA[Angular]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Adithya Sreyaj]]></dc:creator><pubDate>Sat, 26 Nov 2022 14:29:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1669455772244/m4qoxkcuX.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Building out <strong>Selection</strong> feature for tables/list can be challenging at times, because of a lot of corner case that needs to be handled. Here's how we can build one without writing much logic using the Angular CDK's <strong>SelectionModel</strong> utility.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669440079217/WFsYu-CQ8.gif" alt="Multi Select Table" /></p>
<h2 id="heading-angular-cdk-selectionmodel">Angular CDK SelectionModel</h2>
<p><strong>Angular CDK</strong> comes with a lot of utilities and components that we can use to build our application. One such very useful utility is the <strong>SelectionModel</strong> utility that is part of the <strong>Collections</strong> utilities in Angular Material CDK.</p>
<blockquote>
<p>SelectionModel is a utility for powering the selection of one or more options from a list. This model is used in components such as the selection list, table selections, and chip lists.</p>
</blockquote>
<h2 id="heading-understanding-the-api">Understanding the API ℹ️</h2>
<p>The Selection Model class comes with a lot of handy methods that make it super easy to maintain the state of selections. Here's how we can use it:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> selectionModel = <span class="hljs-keyword">new</span> SelectionModel&lt;<span class="hljs-built_in">string</span>&gt;();
</code></pre>
<p>The class constructor takes in these arguments:</p>
<ol>
<li><code>multiple</code> - whether we can select multiple items at a time (multi-select)</li>
<li><code>initiallySelectedValues</code> - initial selection</li>
<li><code>emitChanges</code> - Whether to emit changes (<code>selectionModel.changed</code> property)</li>
<li><code>compareWith</code> - A method that can hold the logic for identifying if an item is selected.</li>
</ol>
<p>Usage with all the arguments provided:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> Movie {
  id: <span class="hljs-built_in">number</span>;
  name: <span class="hljs-built_in">string</span>;
  year: <span class="hljs-built_in">string</span>;
}
<span class="hljs-keyword">const</span> selectionModel = <span class="hljs-keyword">new</span> SelectionModel&lt;Movie&gt;(
  <span class="hljs-literal">true</span>, <span class="hljs-comment">// &lt;- multi-select</span>
  [], <span class="hljs-comment">// &lt;- Initial selections</span>
  <span class="hljs-literal">true</span>, <span class="hljs-comment">// &lt;- emit an event on selection change</span>
  (otherValue, value) =&gt; otherValue.id === value.id <span class="hljs-comment">// &lt;- compare method which checks the id of the movie</span>
);
</code></pre>
<p>We can provide a custom <code>compareWith</code> method to get if a checkbox is selected (<code>selectionModel.isSelected()</code> method). Here's how it's used internally:</p>
<pre><code class="lang-ts">  isSelected(value: T): <span class="hljs-built_in">boolean</span> {
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.compareWith) {
      <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> otherValue <span class="hljs-keyword">of</span> <span class="hljs-built_in">this</span>._selection) { <span class="hljs-comment">// this._selection is a Set()</span>
        <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.compareWith(otherValue, value)) {
          <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
        }
      }
      <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
    }
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>._selection.has(value);
  }
</code></pre>
<h2 id="heading-building-a-table-with-selection">Building a Table with Selection 🛠️</h2>
<p>Now let's use the selection model to build a table with ability to select a row and also have a global checkbox to <strong>Select All/Deselect All</strong>.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">section</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">table</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">thead</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">th</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"checkbox"</span> 
                  [<span class="hljs-attr">checked</span>]=<span class="hljs-string">"selectionModel.selected.length &gt; 0"</span> 
                  [<span class="hljs-attr">indeterminate</span>]=<span class="hljs-string">"selectionModel.selected.length &gt; 0 &amp;&amp;
                  selectionModel.selected.length &lt; tableData.length"</span> 
                  (<span class="hljs-attr">change</span>)=<span class="hljs-string">"onCheckAllChange($event)"</span> /&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">th</span>&gt;</span>Name<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">th</span>&gt;</span>Year<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">th</span>&gt;</span>Rating<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">thead</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">tbody</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">tr</span> *<span class="hljs-attr">ngFor</span>=<span class="hljs-string">"let row of tableData"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"checkbox"</span> 
                 [<span class="hljs-attr">checked</span>]=<span class="hljs-string">"selectionModel.isSelected(row.id)"</span> 
                 (<span class="hljs-attr">change</span>)=<span class="hljs-string">"onCheckRowChange(row)"</span> /&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>{{ row.name }}<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>{{ row.year | date : 'short' }}<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>{{ row.rating | number }}<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">tbody</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">table</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">footer</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"button"</span> (<span class="hljs-attr">click</span>)=<span class="hljs-string">"toggleSelectAll()"</span>&gt;</span>
      {{ isAllSelected ? 'Deselect All' : 'Select All' }}
    <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">footer</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
</code></pre>
<pre><code class="lang-ts"><span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'sreyaj-exp-cdk-selection'</span>,
  template: <span class="hljs-string">`
    &lt;!-- see code above &gt;
  `</span>,
  standalone: <span class="hljs-literal">true</span>,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [NgForOf, DatePipe, DecimalPipe],
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> CdkSelectionComponent {
    <span class="hljs-keyword">readonly</span> tableData: MockData[];
  <span class="hljs-keyword">readonly</span> selectionModel: SelectionModel&lt;<span class="hljs-built_in">number</span>&gt;;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) {
    <span class="hljs-built_in">this</span>.tableData = MOCK_TABLE_DATA;
    <span class="hljs-built_in">this</span>.selectionModel = <span class="hljs-keyword">new</span> SelectionModel&lt;<span class="hljs-built_in">number</span>&gt;(<span class="hljs-literal">true</span>, []);
  }

  get isAllSelected(): <span class="hljs-built_in">boolean</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.selectionModel.selected.length === <span class="hljs-built_in">this</span>.tableData.length;
  }

  <span class="hljs-keyword">public</span> onCheckAllChange(event: Event): <span class="hljs-built_in">void</span> {
    <span class="hljs-keyword">const</span> isChecked = (event.target <span class="hljs-keyword">as</span> HTMLInputElement).checked;
    <span class="hljs-keyword">if</span> (isChecked) {
      <span class="hljs-built_in">this</span>.selectAll();
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">this</span>.deselectAll();
    }
  }

  <span class="hljs-keyword">public</span> onCheckRowChange(row: MockData): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.selectionModel.toggle(row.id);
  }

  <span class="hljs-keyword">public</span> toggleSelectAll(): <span class="hljs-built_in">void</span> {
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.isAllSelected) {
      <span class="hljs-built_in">this</span>.deselectAll();
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">this</span>.selectAll();
    }
  }

  <span class="hljs-keyword">private</span> selectAll(): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.selectionModel.select(...this.tableData.map(<span class="hljs-function">(<span class="hljs-params">row</span>) =&gt;</span> row.id));
  }

  <span class="hljs-keyword">private</span> deselectAll(): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.selectionModel.clear();
  }
}
</code></pre>
<p>Here I've used the <code>id</code> property to maintain the selection state, but we can use the full data also for the selection.</p>
<h3 id="heading-all-selection-and-indeterminate-state">All selection and indeterminate state ✅</h3>
<p>The way we check for all selection/intermediate state is:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"checkbox"</span> 
      [<span class="hljs-attr">checked</span>]=<span class="hljs-string">"this.selectionModel.selected.length &gt; 0"</span> 
      [<span class="hljs-attr">indeterminate</span>]=<span class="hljs-string">"this.selectionModel.selected.length &gt; 0 &amp;&amp;
                  this.selectionModel.selected.length &lt; this.tableData.length"</span> 
      (<span class="hljs-attr">change</span>)=<span class="hljs-string">"this.onCheckAllChange($event)"</span> /&gt;</span>
</code></pre>
<p>We mark <code>checked</code> by checking if at least one item is selected (by looking at the length of the selected items):</p>
<pre><code class="lang-ts">[checked]=<span class="hljs-string">"this.selectionModel.selected.length &gt; 0"</span>
</code></pre>
<p>Next, we set the indeterminate state by checking if at least one is selected but not all.</p>
<pre><code class="lang-ts"> [indeterminate]=<span class="hljs-string">"this.selectionModel.selected.length &gt; 0 &amp;&amp;
                  this.selectionModel.selected.length &lt; this.tableData.length"</span>
</code></pre>
<h3 id="heading-selection-change-event">Selection change event ⚡️</h3>
<p>We can configure if the selection model should emit an event on change or not. This is specified using the <code>emitChange</code> argument in the constructor.</p>
<p>Here's what the event looks like:</p>
<pre><code class="lang-ts"><span class="hljs-comment">/**
* export interface SelectionChange&lt;T&gt; {
*  source: SelectionModel&lt;T&gt;;
*  added: T[];
*  removed: T[];
* }
* */</span>
selectionModel.changed.subscribe(<span class="hljs-function">(<span class="hljs-params">change: SelectionChange</span>) =&gt;</span> {
   <span class="hljs-built_in">console</span>.log(change); 
})
</code></pre>
<p>We'll be able to get the list of items <code>added</code> and <code>removed</code> and the model itself in case we want to do more when the selection changes.</p>
<p>The good thing about the selection model is that it's not coupled to any component (it's completely headless). It can be used with lists and other similar components. A small but really good utility that allows us to concentrate on the actual business logic instead of things like this.</p>
<h2 id="heading-code-and-demo">Code and Demo 🔥</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Title</td><td>Link</td></tr>
</thead>
<tbody>
<tr>
<td>Source Code</td><td>https://github.com/adisreyaj/sreyaj/blob/main/libs/examples/cdk-selection/src/lib/cdk-selection.component.ts</td></tr>
<tr>
<td>Demo</td><td>https://sreyaj.vercel.app/examples/cdk-selection</td></tr>
<tr>
<td>Angular CDK SelectionModel Docs</td><td>https://material.angular.io/cdk/collections/overview#selectionmodel</td></tr>
<tr>
<td>Angular CDK SelectionModel Code</td><td>https://github.com/angular/components/blob/main/src/cdk/collections/selection-model.ts</td></tr>
</tbody>
</table>
</div><h2 id="heading-connect-with-me">Connect with me</h2>
<ul>
<li><a target="_blank" href="https://twitter.com/AdiSreyaj">Twitter</a></li>
<li><a target="_blank" href="https://github.com/adisreyaj">Github</a></li>
<li><a target="_blank" href="https://www.linkedin.com/in/adithyasreyaj/">Linkedin</a></li>
<li><a target="_blank" href="https://adi.so">Portfolio</a></li>
</ul>
<p>Have any thoughts or questions? shoot'em below in the comments.
Stay Safe ❤️</p>
<p><a target="_blank" href="https://www.buymeacoffee.com/adisreyaj"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639498527478/IA3aJ9R0J.png" alt="Buy me a pizza" /></a></p>
]]></content:encoded></item><item><title><![CDATA[Presenting Show Off ✨ - Showcase your setup!]]></title><description><![CDATA[Show Off is a great place to showcase all the gadgets and software you use and share them with your friends or fans. Add affiliate links to the items and make it easy to earn when the user clicks through your collection.
Problem Statement
A lot of ti...]]></description><link>https://sreyaj.dev/show-off-showcase-your-setup-planetscale-hashnode-hackathon</link><guid isPermaLink="true">https://sreyaj.dev/show-off-showcase-your-setup-planetscale-hashnode-hackathon</guid><category><![CDATA[PlanetScale]]></category><category><![CDATA[PlanetScaleHackathon]]></category><category><![CDATA[Angular]]></category><category><![CDATA[nestjs]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Adithya Sreyaj]]></dc:creator><pubDate>Fri, 29 Jul 2022 16:30:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1658653559455/298i8LtCe.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Show Off is a great place to showcase all the gadgets and software you use and share them with your friends or fans. Add affiliate links to the items and make it easy to earn when the user clicks through your collection.</p>
<h2 id="heading-problem-statement">Problem Statement</h2>
<p>A lot of times, I stumble upon youtube videos or posts on Twitter and other social media where the person shares their setup and most of the comments below would be something like this:</p>
<ul>
<li>What IDE are you using?</li>
<li>Which monitor do you use?</li>
<li>What headphones/microphone is that in the background?</li>
<li>Are you using a mac?</li>
</ul>
<p>With show off, users can create a collection of what they have or use and share it with their friends and followers. This makes it easier for you and your followers and I bet you'll not have to reply to these comments anymore.</p>
<h2 id="heading-features">Features</h2>
<p>With show off, the primary idea is to make it super easy to share things with your followers. Also, the user interface is designed in such a way that it's functional and super easy to use as well. Copy the collection link and pin it to your Twitter profile or Github Readme or Instagram or Youtube or wherever you get asked about your stuff.</p>
<h4 id="heading-1-multiple-collections">1. Multiple Collections</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658590981637/CSnQ8HuUT.png" alt="my-collection.png" class="image--center mx-auto" /></p>
<p>You can create multiple collections for different use cases. So for example as a developer, you can have a collection to showcase your laptop, keyboard, monitor, etc in a list. Then create another list to showcase different software that you use.</p>
<h4 id="heading-2-built-for-developers-designers-and-content-creators">2. Built for developers, designers, and content creators</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658425465712/8VddhBut7.png" alt="items.png" class="image--center mx-auto" />
Preset for common categories tailored for developers, designers, and content creators. </p>
<h4 id="heading-3-share-andamp-embed">3. Share &amp; Embed</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658425085615/pJYKRNs6o.png" alt="embed-collection.png" class="image--center mx-auto" /></p>
<p>With the unique link that you get for your collection, you can use it to share on social media. There is also a way to embed the collection into your personal website or portfolio.</p>
<p>Clicking on share, will give two more options, one to copy the collection link or to directly share to Twitter.</p>
<h4 id="heading-4-private-collections">4. Private collections</h4>
<p>Ability to mark a collection as private and when you are ready to add items, you can open up the visibility to make it public.</p>
<h4 id="heading-5-like-and-comments">5. Like and Comments</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658424862214/iWWc6DrcY.png" alt="likes.png" class="image--center mx-auto" /></p>
<p>Users can comment and like the collection giving it more flare. Interact with your followers within the collection. Be in the spotlight if your collection gets more likes. </p>
<h4 id="heading-6-recommendation-meter">6. Recommendation Meter</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658424495828/kTcOtu4tZ.png" alt="recommendations.png" class="image--center mx-auto" /></p>
<p>Love or hate a product that you use personally? Let your followers know how much you would recommend it. With the recommendation meter, you can do just that. This gives them the idea of how good the gadget/software/item is.</p>
<h4 id="heading-7-affiliate-links">7. Affiliate Links</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658425286097/bZEII9z87.png" alt="links.png" class="image--center mx-auto" /></p>
<p>With each item, there is an option to add multiple links. Which includes affiliate links as well. Those who have affiliate accounts with a marketplace like Amazon, Flipkart, etc can add the affiliate links for the item.
This helps you earn additional income when someone purchases the item using your affiliate link.</p>
<p>You can always use a normal link type and add an affiliate link instead, but it's always a good practice to let the user know that the link you add is an affiliate link. If you choose the right type, they will be able to see that its affiliate link gives the user full disclosure.</p>
<h4 id="heading-other-features-in-the-pipeline">Other features in the pipeline</h4>
<ul>
<li>Custome item types</li>
<li>Image uploads where users can share their setup images and others</li>
</ul>
<h2 id="heading-tech-stack">Tech Stack</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658431586754/1gsYdBRYG.png" alt="badges.png" class="image--center mx-auto" /></p>
<h4 id="heading-front-end">Front-end</h4>
<p>The UI is built using the latest and greatest Angular v14. And the best thing is that all the components, directives, pipes, etc are standalone.</p>
<p>Standalone components were one of the most hyped features of v14 and I really enjoyed my time without modules in Angular. </p>
<p>Since the concept of standalone components is fairly new, I am pretty confident that this application will serve as an example for folks trying out standalone components as there aren't many open source angular applications at the time of writing this article.</p>
<p>As always styling is TaliwindCSS all the way. I have created a UI library called <strong>ZigZag</strong> that is built with TailwindCSS that I used in this project. I had a chance to migrate most of the components in zigzag to standalone as well.</p>
<h4 id="heading-back-end">Back-end</h4>
<p>My back-end framework of choice is NestJs as it goes very well with Angular. Especially when you work in an Nx monorepo. Things are so inline that you have very less context switching between the UI and Server code.</p>
<p>GraphQL is used which makes things even better. And for the database, Planetscale is used obviously paired with Prisma. More about it in a dedicated section as there are a few things I noticed when working with  Planetscale and Prisma that I would like to elaborate on.</p>
<p>Planetscale + Prisma = 🔥</p>
<h4 id="heading-deployment">Deployment</h4>
<p>My go-to for deploying the UI is Vercel. When it comes to the server, the repo has Github Actions integrated which automatically builds the server and sends the code to a remote Ubuntu instance.</p>
<h2 id="heading-in-depth-ui">In Depth: UI</h2>
<p>When it comes to the UI, as I have mentioned, all the components are standalone. With standalone components, we don't have to declare them in a <code>NgModule</code>. All the dependencies are imported inside the <code>@Component</code> itself. </p>
<pre><code class="lang-ts"><span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'show-off-login'</span>,
  template: <span class="hljs-string">``</span>,
  styles: [],
  standalone: <span class="hljs-literal">true</span>, <span class="hljs-comment">// &lt;-- Mark as standalone</span>
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [ButtonComponent, ...FORM_COMPONENTS], <span class="hljs-comment">// &lt;-- Import deps</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> LoginComponent {}
</code></pre>
<p>The main thing that I noticed when working with standalone components is that the amount of boilerplate has reduced considerably. Also, I feel that it's more convenient at times. I'm not against modules though.</p>
<p>Another thing that is a rather big change was that I don't have an <code>AppModule</code> for the application. With standalone components, Angular introduced a new method that can bootstrap the application using a standalone component.</p>
<pre><code class="lang-ts"><span class="hljs-comment">// main.ts</span>
bootstrapApplication(AppComponent, {
  providers: [
    {
      provide: AUTH_CONFIG,
      useValue: {
        authURL: <span class="hljs-string">`<span class="hljs-subst">${environment.apiURL}</span>/api/auth`</span>,
      },
    },
    {
      provide: CURRENT_USER,
      useFactory: <span class="hljs-function">(<span class="hljs-params">auth: AuthService</span>) =&gt;</span> auth.me(),
      deps: [AuthService],
    },
    importProvidersFrom(HttpClientModule), <span class="hljs-comment">// &lt;-- import from existing modules</span>
   ]
});
</code></pre>
<p>And a look at my <code>AppComponent</code></p>
<pre><code class="lang-ts"><span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'show-off-root'</span>,
  template: <span class="hljs-string">`&lt;router-outlet&gt;&lt;/router-outlet&gt;`</span>,
  standalone: <span class="hljs-literal">true</span>, <span class="hljs-comment">// &lt;-- standalone component</span>
  imports: [RouterModule],
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppComponent {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> authService: AuthService</span>) {
    authService.init().pipe(take(<span class="hljs-number">1</span>)).subscribe();
  }
}
</code></pre>
<p>Another change to note is the way we do lazy loading in routes.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> COLLECTION_ROUTES: Routes = [
  {
    path: <span class="hljs-string">''</span>,
    pathMatch: <span class="hljs-string">'full'</span>,
    title: <span class="hljs-string">'Home | Show Off'</span>, <span class="hljs-comment">// &lt;-- directly update title</span>
    data: {
      context: CollectionPageContext.Home,
      header: {
        text: <span class="hljs-string">'Home'</span>,
      },
    },
    loadComponent: <span class="hljs-function">() =&gt;</span> <span class="hljs-comment">// &lt;-- new `loadComponent` method</span>
      <span class="hljs-keyword">import</span>(<span class="hljs-string">'./components/collections.component'</span>).then(
        <span class="hljs-function">(<span class="hljs-params">m</span>) =&gt;</span> m.CollectionsComponent
      ),
  }
]
</code></pre>
<p>What are the major changes I noticed when working with Angular v14 (w/ standalone components).</p>
<p>Attaching lighthouse report run on the deployed app:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658607362292/Uk2MJGL_Q.png" alt="lighthouse.png" class="image--center mx-auto" /></p>
<h3 id="heading-embed-feature">Embed Feature</h3>
<p>One of the most striking features of Show Off is the embed feature where users can embed a collection into their website or portfolio by just copy-pasting the snippet generated from the application.</p>
<p>This was something that I haven't done before, so was really interesting to get this functionality working.
The below section is actually what an embedded collection would look like.</p>
<iframe width="100%" height="600px" src="https://show-off.adi.so/embed/cl5dx3okz0003aixcvomcbbcf"></iframe>

<p> It renders an <code>iframe</code> under the hood. You can inspect the below section and see that it's just an iframe:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">iframe</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"100%"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"600px"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://show-off.adi.so/embed/cl5dx3okz0003aixcvomcbbcf"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">iframe</span>&gt;</span>
</code></pre>
<p>Here how the embed script looks like:</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">showOff</span>(<span class="hljs-params">collectionId, opts</span>) </span>{
  <span class="hljs-keyword">if</span> (!collectionId) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'showOff: collectionId is required'</span>);
  }
  <span class="hljs-keyword">const</span> iframe = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'iframe'</span>);
  iframe.src = constructIframeSrc(collectionId, opts);
  iframe.width = opts?.width ?? <span class="hljs-string">'100%'</span>;
  iframe.height = opts?.height ?? <span class="hljs-string">'500px'</span>;
  iframe.frameBorder = <span class="hljs-string">'0'</span>;
  iframe.scrolling = <span class="hljs-string">'0'</span>;
  iframe.style.border = <span class="hljs-string">'none'</span>;
  <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'show-off-embed'</span>).appendChild(iframe);
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">constructIframeSrc</span>(<span class="hljs-params">collectionId, opts</span>) </span>{
  <span class="hljs-keyword">const</span> queryParams = {
    <span class="hljs-attr">showTitle</span>: opts?.showTitle ?? <span class="hljs-string">'true'</span>,
    <span class="hljs-attr">showDescription</span>: opts?.showDescription ?? <span class="hljs-string">'true'</span>,
    <span class="hljs-attr">showOwner</span>: opts?.showOwner ?? <span class="hljs-string">'true'</span>,
    <span class="hljs-attr">padding</span>: opts?.padding ?? <span class="hljs-string">'16'</span>,
  };
  <span class="hljs-keyword">let</span> baseUrl = <span class="hljs-keyword">new</span> URL(<span class="hljs-string">`https://show-off.adi.so/embed/<span class="hljs-subst">${collectionId}</span>`</span>);
  <span class="hljs-built_in">Object</span>.keys(queryParams).forEach(<span class="hljs-function">(<span class="hljs-params">key</span>) =&gt;</span> {
    baseUrl.searchParams.append(key, queryParams[key]);
  });

  <span class="hljs-keyword">return</span> baseUrl.href;
}
</code></pre>
<p>What the script does is that it creates an iframe and then loads the <code>/embed/&lt;collection_id&gt;</code> route.</p>
<h4 id="heading-embed-code-display">Embed Code Display</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658589571573/UAV2ueHuM.png" alt="embed-modal.png" class="image--center mx-auto" /></p>
<p>Embed code that is displayed on the application has syntax highlighting for the embed snippet. The syntax highlighting is powered by <a target="_blank" href="https://shiki.matsu.io/">Shiki</a></p>
<p>Here's what the code looks like in UI:</p>
<pre><code class="lang-ts">shiki.setCDN(<span class="hljs-string">'https://unpkg.com/shiki/'</span>); <span class="hljs-comment">// &lt;-- Don't miss this</span>
<span class="hljs-built_in">this</span>.highlighter = <span class="hljs-keyword">await</span> shiki.getHighlighter({
   theme: <span class="hljs-string">'material-palenight'</span>, <span class="hljs-comment">// &lt;-- Load a theme</span>
   langs: [<span class="hljs-string">'html'</span>], <span class="hljs-comment">// &lt;-- Load only HTML lang</span>
});
<span class="hljs-built_in">this</span>.code.nativeElement.innerHTML = <span class="hljs-built_in">this</span>.highlighter.codeToHtml(script ?? <span class="hljs-string">''</span>, {
        lang: <span class="hljs-string">'html'</span>,
})
</code></pre>
<h3 id="heading-zigzag-ui-library">Zigzag - UI library</h3>
<p>Zigzag is a very small UI component library that I built with Angular and TailwindCSS. It contains a few basic sets of components:</p>
<ul>
<li>Button</li>
<li>Modal</li>
<li>Dropdown</li>
<li>Tooltip</li>
<li>Form Components</li>
</ul>
<p>It's not packaged as an npm library yet, so I use Git Submodule to load the repo into the main project repo.</p>
<p>More about zigzag and how to use it here: </p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/adisreyaj/zigzag">https://github.com/adisreyaj/zigzag</a></div>
<h2 id="heading-in-depth-back-end">In Depth: Back-end</h2>
<p>NestJs is a back-end framework for building server-side applications. It plays very well with Angular as it shares a lot of concepts. And everything inside an Nx monorepo makes it even better.</p>
<p> I've been using GraphQL lately and totally love it. Mainly because I get instant documentation of my queries and mutations. Setting up GraphQL is a bit of a pain, but once you set it up, things are easy. NestJs has support for GraphQL using the official plugins.</p>
<p>NestJs supports both schema-first and code-first approaches. I've used the Schema first approach here.
For querying and testing everything, Apollo Sandbox provides a great interface. You'll be able to see the schema and all the queries and mutations and test them with ease.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658598955730/OMjNhuVIY_.png" alt="apollo-sandbox.png" class="image--center mx-auto" /></p>
<p>You can enable Apollo sandbox in NestJs by making these changes:</p>
<pre><code class="lang-ts"><span class="hljs-comment">// app.module.ts</span>
<span class="hljs-keyword">import</span> { ApolloServerPluginLandingPageLocalDefault } <span class="hljs-keyword">from</span> <span class="hljs-string">'apollo-server-core'</span>;

<span class="hljs-meta">@Module</span>({
  imports: [
    GraphQLModule.forRootAsync&lt;ApolloDriverConfig&gt;({
      driver: ApolloDriver,
      useFactory: <span class="hljs-function">(<span class="hljs-params">configService: ConfigService</span>) =&gt;</span> {
        <span class="hljs-keyword">const</span> isProd = configService.get(<span class="hljs-string">'NODE_ENV'</span>) === <span class="hljs-string">'production'</span>;
        <span class="hljs-keyword">return</span> {
         ...
          playground: <span class="hljs-literal">false</span>, <span class="hljs-comment">// &lt;-- default playground disabled</span>
          plugins: [
            ApolloServerPluginLandingPageLocalDefault({ <span class="hljs-comment">// &lt;-- use apollo sandbox plugin</span>
              embed: <span class="hljs-literal">true</span>,
            }),
          ],
        };
      },
    }),
  ],
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppModule {}
</code></pre>
<p>Once this is done, I faced some issues because of the Content Security Policies as I was using <a target="_blank" href="https://github.com/helmetjs/helmet">helmet</a>, so I had to update them in the <a target="_blank" href="https://github.com/adisreyaj/show-off/blob/23a72803651c60945b03f648a766a5adeaef6b96/apps/api/src/main.ts#L24">main.ts</a> file:</p>
<pre><code class="lang-ts"><span class="hljs-comment">// main.ts</span>
  <span class="hljs-keyword">const</span> devContentSecurityPolicy: ContentSecurityPolicyOptions = {
    directives: {
      manifestSrc: [<span class="hljs-string">"'self'"</span>, <span class="hljs-string">'*.cdn.apollographql.com'</span>],
      scriptSrc: [<span class="hljs-string">"'self'"</span>, <span class="hljs-string">"'unsafe-inline'"</span>, <span class="hljs-string">'*.cdn.apollographql.com'</span>],
      frameSrc: [<span class="hljs-string">"'self'"</span>, <span class="hljs-string">'*.embed.apollographql.com'</span>],
      imgSrc: [<span class="hljs-string">"'self'"</span>, <span class="hljs-string">'data:'</span>, <span class="hljs-string">'*.cdn.apollographql.com'</span>],
    },
  };

  app.use(
    helmet({
      <span class="hljs-comment">// when undefined it will load the default option: https://github.com/graphql/graphql-playground/issues/1283#issuecomment-723705276</span>
      contentSecurityPolicy: isProd ? <span class="hljs-literal">undefined</span> : devContentSecurityPolicy,
      crossOriginEmbedderPolicy: <span class="hljs-literal">false</span>,
    })
  );
</code></pre>
<p>Authentication is done using Google and Github OAuth right now. Username &amp; password based auth is not set up right now. Authentication flow is taken care of by Passport.</p>
<p>Auth-related code can be found: https://github.com/adisreyaj/show-off/tree/main/libs/api/auth</p>
<p>Here's what the entities look like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658591380508/WwwOcnTal.png" alt="erd.png" /></p>
<h2 id="heading-in-depth-database">In Depth: Database</h2>
<p>For databases, my primary choice is always Postgres or MySQL mainly because I love Prisma so much. So my go-to for ORM is Prisma.  PlanetScale is a MySQL-compatible database platform. I've been following PlanetScale for a long time and was really impressed with what they do.</p>
<p>Databases are always difficult to architect, scaling, backups, etc are always crucial for any application. I used to set up Postgres or MySQL in a docker container on my Ubuntu instance and pray to god that nothing goes wrong.</p>
<p>With Planetscale you leave everything related to a database like scaling, downtimes, backups, etc. One of the most interesting features that you get is database branching 🤯</p>
<p>I have to admit that I really liked how you can test out schema changes to your table in a separate branch. And once you are comfortable with the changes, you can merge them into the main branch. </p>
<p>I've used the <a target="_blank" href="https://www.prisma.io/docs/concepts/components/prisma-client/full-text-search">Prisma Full-text Search</a> for searching with collections.</p>
<p>To set it up we make the following changes in <code>schema.prisma</code> file:</p>
<pre><code>generator client {
  provider        = <span class="hljs-string">"prisma-client-js"</span>
  <span class="hljs-comment">// add `fullTextSearch` and  `fullTextIndex`</span>
  previewFeatures = [<span class="hljs-string">"referentialIntegrity"</span>, <span class="hljs-string">"fullTextSearch"</span>, <span class="hljs-string">"fullTextIndex"</span>]
}

datasource db {
  provider             = <span class="hljs-string">"mysql"</span>
  url                  = env(<span class="hljs-string">"DATABASE_URL"</span>)
  referentialIntegrity = <span class="hljs-string">"prisma"</span>
}

model Collection {
  id          String    <span class="hljs-meta">@id</span> <span class="hljs-meta">@default(cuid()</span>)
  name        String
  description String
  user        User      <span class="hljs-meta">@relation(fields: [userId], references: [id])</span>
  userId      String
  likes       Like[]
  shares      Share[]
  comments    Comment[]
  items       Item[]
  published   <span class="hljs-built_in">Boolean</span>   <span class="hljs-meta">@default(true)</span>
  <span class="hljs-keyword">private</span>     <span class="hljs-built_in">Boolean</span>   <span class="hljs-meta">@default(false)</span>
  deleted     <span class="hljs-built_in">Boolean</span>   <span class="hljs-meta">@default(false)</span>
  createdAt   DateTime  <span class="hljs-meta">@default(now()</span>)
  updatedAt   DateTime  <span class="hljs-meta">@updatedAt</span>

  @<span class="hljs-meta">@index([userId])</span>
  @<span class="hljs-meta">@fulltext([name])</span> <span class="hljs-comment">// &lt;-- add index</span>
}
</code></pre><h3 id="heading-prisma-x-planetscale">Prisma x PlanetScale</h3>
<p>PlanetScale and Prisma work very well. I still remember the time when people started asking to support Prisma and PlanetScale finally did it. They made it really easy to use it with Prisma.</p>
<h4 id="heading-1-create-an-account-on-planetscale">1. Create an account on PlanetScale.</h4>
<p>They provide a free account to start with generous limits.</p>
<h4 id="heading-2-create-a-new-database">2. Create a new Database</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658552039293/zSay-mu6D.png" alt="create-db.png" class="image--center mx-auto" /></p>
<p>You can create a database from the web console or using their CLI which is a nice little tool to have. You can setup PlanetScale CLI by referring to the docs here: https://planetscale.com/docs/concepts/planetscale-environment-setup</p>
<p>Once you have the CLI you need to authenticate:</p>
<pre><code class="lang-sh">pscale auth login
</code></pre>
<p>and then create a database</p>
<pre><code class="lang-sh">pscale database create &lt;name&gt;
</code></pre>
<p><strong>Note: </strong> When selecting the location, make sure to select a region near to where your server is hosted. In my case, I did a mistake in one of my previous projects where I selected a region very far from where my server was hosted. This caused latency issues as it takes time for the communication between the server and the DB itself.</p>
<h4 id="heading-3-connecting-to-database">3. Connecting to Database</h4>
<p>PlanetScale gives a list of options that you can use to connect to the database. In our case, we need to connect with Prisma, so we select <em>Prisma</em> from the dropdown. This gives us the connection string, that we can use with Prisma.</p>
<p>We can then add the <code>DATABASE_URL</code> in the <code>.env</code> file:</p>
<pre><code class="lang-sh">DATABASE_URL=mysql://&lt;username&gt;:&lt;password&gt;@&lt;subdomain&gt;.psdb.cloud/show-off?sslaccept=strict
</code></pre>
<p>You can set this environment variable for when the server is deployed. For local workflow, the easiest way to set up the connection is using the PlanetScale CLI.</p>
<pre><code class="lang-sh">pscale connect &lt;databast-name&gt; &lt;branch-name&gt; --port 3309
</code></pre>
<p>This will create a tunnel to your localhost, so you can use the following connection string:</p>
<pre><code class="lang-sh">DATABASE_URL=mysql://localhost:3309/&lt;db-name&gt;
</code></pre>
<p><strong>Note</strong>: The <code>--port 3309</code> is not required, by default it will run on <code>3306</code></p>
<h4 id="heading-4-prisma-configuration">4. Prisma configuration</h4>
<p>There is a slight change in the way we configure Prisma when using PlanetScale:</p>
<pre><code>generator client {
  provider        <span class="hljs-operator">=</span> <span class="hljs-string">"prisma-client-js"</span>
  previewFeatures <span class="hljs-operator">=</span> [<span class="hljs-string">"referentialIntegrity"</span>]
}

datasource db {
  provider             <span class="hljs-operator">=</span> <span class="hljs-string">"mysql"</span>
  url                  <span class="hljs-operator">=</span> env(<span class="hljs-string">"DATABASE_URL"</span>)
  referentialIntegrity <span class="hljs-operator">=</span> <span class="hljs-string">"prisma"</span>
}
</code></pre><p>The main thing to note here is that we add the <code>referentialIntegrity</code> under preview features and also set the <code>referentialIntegrity</code> type to <code>prisma</code></p>
<p>Here's the official documentation on <a target="_blank" href="https://planetscale.com/docs/tutorials/prisma-quickstart">how to get started with PlanetScale and Prisma</a></p>
<p>Read more about <a target="_blank" href="https://www.prisma.io/docs/guides/database/using-prisma-with-planetscale#how-to-enable-emulation-of-referential-integrity">referential integrity</a>.</p>
<p>Note: With PlanetScale, the way we handle migrations is different.
Prisma has a <code>prisma migrate</code> command that does the schema diff and generates the corresponding migration scripts. But with PlanetScale, the way we do this is different.</p>
<h4 id="heading-migrations-and-planetscale">Migrations and PlanetScale</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658553663358/hzfJ9cg8j.png" alt="branching.png" class="image--center mx-auto" /></p>
<p>When we make a change to the schema, we start by creating a development branch in PlanetScale:</p>
<pre><code class="lang-sh">pscale branch create &lt;db-name&gt; &lt;branch-name&gt;
</code></pre>
<p>Once the branch is connected, go ahead and close the connection and connect to the new branch that we created:</p>
<pre><code class="lang-sh">pscale connect &lt;databast-name&gt; &lt;branch-name&gt; --port 3309
</code></pre>
<p><strong>Note:</strong> You can also create a branch from the web console. Once the branch is created you'll have to copy the connection string and use that instead of the <code>main</code> branch connection string in the <code>.env</code> file. This is why I would recommend using the PlanetScale CLI as you don't have to keep on updating your <code>.env</code> file with a new connection string when working on big changes to the schema. What you do is just stop the connection to the main branch and instead connect to the new branch we created.</p>
<p>Once changes are made we push the changes to the DB using the following command:</p>
<pre><code class="lang-sh">prisma db push
</code></pre>
<p>This pushes the latest changes to the dev branch. When you are ready, we can create a <em>Deploy Request</em> to the production branch.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658553737437/csrUoKY4_.png" alt="deploy-req.png" class="image--center mx-auto" /></p>
<p>In the deploy request, we'll be able to see the schema changes. Once we are happy with the changes, the changes can be merged into the prod branch.</p>
<p>This is a really cool feature that gives you more power to prototype and makes changes to the schema with confidence. They also provide Rollback functionality as well which gives you peace of mind.</p>
<h2 id="heading-in-depth-deployment">In Depth: Deployment</h2>
<p>Deployment of the UI is very straightforward. Just push the code to git and it gets deployed to Vercel. So nothing to do there. Here's what the deployment setting in Vercel looks like for those who wonder.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658592258355/bnTxSfT_U.png" alt="vercel.png" class="image--center mx-auto" /></p>
<p>When it comes to back-end deployment, Here's how things are done.</p>
<ol>
<li>Code pushed to GitHub.</li>
<li>Github Actions run the build script.</li>
<li>Copy the <code>.graphql</code> files to the <code>dist</code> directory.</li>
<li>Dist artifacts are then packaged into a <code>.tar.gz</code> file.</li>
<li>The <code>.tar.gz</code> file is sent to a remote server using <code>rsync</code></li>
<li>SSH into the remote server</li>
<li>The packaged bundle is extracted into a folder.</li>
<li>Install all the dependencies</li>
<li>PM2 is used to run the server</li>
</ol>
<p>The server port is then pointed to a subdomain using Nginx. I always refer to my article on <a target="_blank" href="https://sreyaj.dev/self-host-plausible-ubuntu-with-ssl-for-free#heading-6-setting-up-the-domain">setting up Nginx and port forwarding to a subdomain with SSL</a>.</p>
<p>You can see that the API calls for the app are made to <code>https://show-off-api.adi.so</code></p>
<p>Monitoring the server is easy, thanks to PM2:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658597954913/fTl-04N-c.png" alt="pm2.png" class="image--center mx-auto" /></p>
<h2 id="heading-coding-practices">Coding Practices</h2>
<ul>
<li><p>✅ <strong>Linting</strong>: <strong>ESLint</strong> </p>
</li>
<li><p>✅ <strong>Code formatting</strong>: <strong>Prettier</strong></p>
</li>
<li><p>✅ And commit messages are following <strong>Conventional Commits</strong> enforced using <strong>Husky</strong></p>
</li>
<li><p>✅ <strong>Code Quality</strong>: <strong>SonarCloud</strong> <a target="_blank" href="https://sonarcloud.io/summary/overall?id=adisreyaj_show-off">See Project</a></p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658659928312/JXr588z7q.png" alt="sonar.png" class="image--center mx-auto" /></p>
<ul>
<li>✅ <strong>Code Vulnerabilities</strong>: <strong>Snyk</strong>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658776054896/SxGRINKRj.png" alt="snyk-vul.png" /></li>
</ul>
<h2 id="heading-running-locally">Running Locally 💻</h2>
<h4 id="heading-1-clone-the-repo">#1. Clone the repo</h4>
<pre><code class="lang-sh">git <span class="hljs-built_in">clone</span> https://github.com/adisreyaj/show-off.git
</code></pre>
<h4 id="heading-2-initialize-the-submodule-zigzag">#2. Initialize the submodule (zigzag)</h4>
<pre><code class="lang-sh">git submodule update --init
</code></pre>
<h4 id="heading-3-install-the-dependencies">#3.  Install the dependencies</h4>
<pre><code class="lang-sh">npm install
</code></pre>
<h4 id="heading-4-install-peer-deps-for-zigzag">#4. Install Peer deps for zigzag</h4>
<pre><code class="lang-sh">npm i @floating-ui/dom
</code></pre>
<h4 id="heading-5-setup-the-environment-variables">#5. Setup the environment variables</h4>
<p>Set up all the required environment variables required for the back-end:</p>
<pre><code><span class="hljs-attr">DATABASE_URL</span>=mysql://localhost:<span class="hljs-number">3309</span>/show-<span class="hljs-literal">off</span>

<span class="hljs-attr">NODE_ENV</span>=development
<span class="hljs-attr">FRONT_END_CALLBACK_URL</span>=http://localhost:<span class="hljs-number">4200</span>/auth/callback

<span class="hljs-comment"># JWT sign secret</span>
<span class="hljs-attr">JWT_SECRET</span>=very_very_strong_secret_key
<span class="hljs-attr">JWT_EXPIRY</span>=<span class="hljs-string">"3d"</span>
<span class="hljs-attr">COOKIE_SECRET</span>=very_very_strong_secret_session_key

<span class="hljs-comment"># Google OAuth Details</span>
<span class="hljs-attr">GOOGLE_CLIENT_ID</span>=xxxxx.googleusercontent.com
<span class="hljs-attr">GOOGLE_CLIENT_SECRET</span>=xxxxxxx
<span class="hljs-attr">GOOGLE_CALLBACK_URI</span>=http://localhost:<span class="hljs-number">3333</span>/api/auth/google/callback

<span class="hljs-comment"># Github OAuth Details</span>
<span class="hljs-attr">GITHUB_CLIENT_ID</span>=Iv1.xxxxxxxxxx
<span class="hljs-attr">GITHUB_CLIENT_SECRET</span>=xxxxxx
<span class="hljs-attr">GITHUB_CALLBACK_URI</span>=http://localhost:<span class="hljs-number">3333</span>/api/auth/github/callback
</code></pre><h4 id="heading-6-set-up-the-database">#6. Set up the Database</h4>
<p>Run the command to populate the DB with tables:</p>
<pre><code class="lang-sh">npm run prisma:push
</code></pre>
<h4 id="heading-7-start-up-the-ui-and-back-end">#7. Start up the UI and Back-end</h4>
<p>For UI:</p>
<pre><code class="lang-sh">npm start show-off
</code></pre>
<p>For Back-end</p>
<pre><code class="lang-sh">npm start api
</code></pre>
<p>UI: <code>http://localhost:4200</code> &amp; GQL: <code>http://localhost:3333</code></p>
<h2 id="heading-links-and-references">Links and References 🔗</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Title</td><td>Link</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td>Angular</td><td>https://angular.io/</td><td>Front-end framework</td></tr>
<tr>
<td>NestJs</td><td>https://docs.nestjs.com/</td><td>Back-end framework based on NodeJs</td></tr>
<tr>
<td>PlanetScale</td><td>https://www.planetscale.com/</td><td>MySQL Compatible DB</td></tr>
<tr>
<td>Prisma</td><td>https://www.prisma.io/</td><td>Node.js and TypeScript ORM</td></tr>
<tr>
<td>Tailwind CSS</td><td>https://tailwindcss.com/</td><td>Utility first CSS framework</td></tr>
<tr>
<td>Nx</td><td>https://nx.dev/#getting-started</td><td>Build system with monorepo support</td></tr>
<tr>
<td>PM2</td><td>https://app.pm2.io/</td><td>Advanced, production process manager for Node.JS</td></tr>
</tbody>
</table>
</div><h2 id="heading-code-and-demo">Code and Demo 💫</h2>
<p><strong>Link</strong>: https://show-off.adi.so</p>
<p><strong>Github</strong>: https://github.com/adisreyaj/show-off</p>
<h2 id="heading-connect-with-me">Connect with me</h2>
<ul>
<li><a target="_blank" href="https://twitter.com/AdiSreyaj">Twitter</a></li>
<li><a target="_blank" href="https://github.com/adisreyaj">Github</a></li>
<li><a target="_blank" href="https://www.linkedin.com/in/adithyasreyaj/">Linkedin</a></li>
<li><a target="_blank" href="https://adi.so">Portfolio</a></li>
</ul>
<p>Do add your thoughts or if you have any questions, shoot'em below in the comments.
Stay Safe ❤️</p>
<p><a target="_blank" href="https://www.buymeacoffee.com/adisreyaj"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639498527478/IA3aJ9R0J.png" alt="Buy me a pizza" /></a></p>
]]></content:encoded></item><item><title><![CDATA[From Working In A Tech Support Role To Becoming A Full-stack Developer]]></title><description><![CDATA[Starting as a tech support associate to becoming a full-stack web developer, this is my journey into tech. I currently work with technologies/frameworks like Angular, NodeJs & TypeScript.
Learning to code has changed my life. I'm really proud of myse...]]></description><link>https://sreyaj.dev/my-journey-into-tech</link><guid isPermaLink="true">https://sreyaj.dev/my-journey-into-tech</guid><category><![CDATA[Career]]></category><category><![CDATA[Angular]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Learning Journey]]></category><dc:creator><![CDATA[Adithya Sreyaj]]></dc:creator><pubDate>Wed, 27 Apr 2022 15:05:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/szrJ3wjzOMg/upload/v1650221442726/DNLwDqzCZ.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Starting as a tech support associate to becoming a full-stack web developer, this is my journey into tech. I currently work with technologies/frameworks like Angular, NodeJs &amp; TypeScript.</p>
<p>Learning to code has changed my life. I'm really proud of myself and the state I'm in right now. I feel empowered. And being a self-taught developer, I can say that it can change your life too.</p>
<h2 id="heading-studies">Studies</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1650988591592/MigCeTpRd.jpeg" alt="Education" />
<em>Photo by <a target="_blank" href="https://unsplash.com/@flipboo?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Philippe Bout</a></em></p>
<p><strong>Timeline</strong>: 2012 - 2016</p>
<p>I did my Engineering in Electronics and Communications. It was in my 3rd year, I really got interested in robotics. There was a workshop on building things with Arduino which triggered something in me. </p>
<p>I got myself an Arduino and started learning to code. I created multiple small projects like Smartphone controlled robocar, and a full home automation system with the support of gestures and voice control. Went on to win a few prizes at the college and national levels.</p>
<p>The thing I noticed is that, even while I was working on all these projects, it was the software side of it that excited me and I knew that I would be better off working with code. </p>
<p>I along with a few of my friends applied for startup incubation at our University to create a company called MakeLabs. We used to build projects for other students and also did R&amp;D on so many interesting ideas. </p>
<h2 id="heading-change-of-plans">Change of Plans</h2>
<p><strong>Timeline</strong>: 2016</p>
<p>After I did my bachelor's, I decided to go for my master's. It was not my plan, my family persuaded me into making this decision. So when I say family, it's not always my mom and dad, but other relatives because mom and dad had always been supportive.</p>
<p>I had two small offers as part of the Campus placements which I rejected because of the Master's plan.</p>
<p>Things went wrong just before a week of my Master's classes started. A bunch of us (Roll numbers 1 to 14) got a back paper in the 7th semester. I still wonder why all 14 of us failed at the same time.</p>
<p>But since I had grace marks as part of the entrepreneurship program. But luckily, my Master's admission got canceled due to the back-paper even though I cleared it thanks to the grace marks.</p>
<p>It was a relief for me to know that I won't be doing something I didn't like. And I think one of the best things happened to me because I really love what I do now.</p>
<h2 id="heading-getting-into-tech-support">Getting into tech support</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1650988765512/dGv3XCm4H.jpeg" alt="Tech Support" />
<em>Photo by <a target="_blank" href="https://unsplash.com/@machec?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Petr Macháček</a></em></p>
<p><strong>Timeline</strong>: 2017</p>
<p>So now since life took a different turn, I was put in a situation with no job and not knowing what to do next.</p>
<p>I struggled for nearly a year to get a job in the software domain. It was very hard during that time to get calls for interviews and most of them really didn't hire freshers. For most of the jobs that I interviewed for I got a really low package. </p>
<p>I came to Bangalore and started looking for a software developer role. I also wanted to do something till I could find a job. That is when one day, one of my friends was going for an interview for a tech support role. I just tagged along. I who went with him just for giving him company ended up giving an interview and getting the job as well.</p>
<p>The job wasn't exciting and I knew that its not for me. But I had to work so that I can pay my bills without having to trouble my parents.</p>
<h3 id="heading-blogging-about-computers-and-smartphones">Blogging about computers and smartphones</h3>
<p>The one thing that I did during that time is writing blogs, mainly about computer tips and tricks.
You can see that latest iteration of the blog here at <a target="_blank" href="https://sreyaj.com">sreyaj.com</a>. Before that I use to own other domains.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1650987867090/65bkDU75U.png" alt="Sreyaj.com Blog" /></p>
<p>I wrote a lot of detailed articles about things related to computers that I encountered or I found will be useful to others. I even had AdSense enabled after some time and used to get very small amounts as ad revenue. </p>
<p>I used WordPress for the blogs and also learned a little bit of PhP, just enough to be able to tweak themes and create small applications at that time. </p>
<p>I could really use some money to pay for rent and food. I ended up working there for almost 6 months. I used to work on my blog and also learn HTML, and CSS to make the blog look good. WordPress had limitations in what can be done. </p>
<p>We are limited by the design of the theme that we use. I am really picky about the design of the blog and that's what pushed me to learn HTML, CSS, and JavaScript.</p>
<p>While working in the Tech support role, I spent a lot of time doing UI changes to my blog. I spent more time learning how HTML, CSS, and JavaScript, all to make my blog look better.</p>
<p>I also did some logo designing and website development for my friends and family at that time. I really enjoyed creating designs. </p>
<p>I later started creating small web applications using PhP like Instagram Photo and Video downloader and a few others which could be a nice addition to the blog.</p>
<p>I was pretty successful in creating a blog that generated a good amount of views and so made a few dollars from it. But the interest in writing articles related to computer tips and tricks slowly faded as I got my first job as a Software Developer.</p>
<h2 id="heading-first-job-as-a-software-engineer">First job as a Software Engineer</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1650988906721/pKn6oJlGi.jpeg" alt="Software Career" />
<em>Photo by <a target="_blank" href="https://unsplash.com/@johnschno?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">John Schnobrich</a></em></p>
<p><strong>Timeline</strong>: Nov 2018</p>
<p>While being in the tech support role, I was pulled into the job completely and I thought I'll be stuck there If I don't do something. That's when I started to apply for software jobs, mainly web development. Got a couple of offers, but the pay was way lesser than what I got from the tech support job.</p>
<p>I was pretty confident that once I get a chance, I can prove myself and go from there. But getting that initial break was a real struggle for me.</p>
<p>One fine afternoon, I got a call from an HR who asked me If I could go in for an interview. I was having an off that day and so I decided to go. Went there and gave the interview. I was able to show my blogs and a few smaller projects that I built during that time.</p>
<p>The company asked me to go to their office for 2 days where they would evaluate me and then offer a job. I was given the task of redoing the company website using Angular. That is the first time I came to know about Angular and what Single Page Applications are.</p>
<p>I was able to complete the task given by the company before the given time and was offered a job with less salary than what I was getting in my tech support role. I decided to just take the job eventually. The pay was just around 12,000 INR or ~150 USD per month.</p>
<p>I undervalued myself because my confidence level went down because of all that happened after college and I was jobless for more than a year.</p>
<h3 id="heading-angular-and-me">Angular and Me</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1650997633936/h3WE5G4Az.png" alt="Angular" /></p>
<p>Angular version 7 just got released at the time I joined for my first-ever software role.</p>
<p>Angular opened up a whole lot of opportunities for me. I never knew frameworks like these existed and you can build impactful products with them. I realized how powerful the whole web ecosystem was. I was just working with WordPress and had a narrow vision of the web before. But that got changed when I was introduced to Angular.</p>
<p>I started reading the docs and also started going through the Udemy course by Max. I started off with the Udemy course at first, but the pace didn't work well for me. I switched to the official documentation and relied more on blogs.</p>
<p>If someone has told me about Angular or the exciting world of web applications before, I would've been in a far better place by now.</p>
<p>I started out with Angular 7 in the company and I was the only front-end developer. I learned everything on my own. Self-learning helped me in many ways. Whenever I come across a new pattern or feature, I try to read more about it and also practice it in a sandbox. I believe in learning by doing.</p>
<p>I was able to pick up things faster and I started working on NodeJS as well after a short while. Since it's a startup and when you don't have a lot of people, you do a lot of things. I really liked having that flexibility and control.</p>
<p>Also having a manager who really knows all the best practices when it comes to architecting software really came as a blessing. I was able to learn a lot from him. I'm grateful for having a mentor like him during the start itself.</p>
<h3 id="heading-learn-to-say-no">Learn to say NO</h3>
<p>Worked really hard for almost 2 years. I spent a lot of time at the office working really hard and learning as much as I can. Even spent almost 12 hours in the office for a month straight. I didn't complain as I was learning something every day. I realize it's a mistake that I did because I didn't know to say "No".</p>
<p>Saying <strong>NO</strong> was and is very hard for me. I am still trying to do this. It's really important for someone to say no to things at times. Otherwise, you'll just end up doing more work without much to gain. </p>
<p>I really am grateful to my first company for believing in me and giving me that opportunity. I was able to work on multiple projects and also lay the foundation for many projects which really helped me learn a lot.</p>
<p>From there I focused on getting more and more knowledge in Angular and NodeJs. I started feeling more confident about what I can do. I started experimenting and working on small side projects to further solidify my learnings.</p>
<p>Another thing that I loved doing is sharing my knowledge with my peers as well. Whenever I come across something interesting, I ping them and let them know about it.</p>
<h2 id="heading-being-part-of-the-community">Being part of the community</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651065140508/Hg11D387q.jpeg" alt="Community" />
<em>Photo by <a target="_blank" href="https://unsplash.com/@john_cameron?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">John Cameron</a></em></p>
<p>The one thing that I really enjoyed is sharing knowledge and that is what brought me into a lot of communities. Twitter was really one of the best places for sharing and networking with a lot of developers. </p>
<p>Twitter had a lot of awesome Angular developers who share good stuff all the time. I followed any Angular developer I could on Twitter at that time. Once I really got a good grasp of Angular and Web development in general, I shifted to writing blogs focusing on web development mainly using Angular. </p>
<p>I got a chance to sync with a lot of great people on Twitter like <a target="_blank" href="https://twitter.com/SantoshYadavDev">Santosh</a>, <a target="_blank" href="https://twitter.com/Nartc1410">Chau</a>, <a target="_blank" href="https://twitter.com/tuantrungvo">Trung</a>, <a target="_blank" href="https://twitter.com/beeman_nl">Bram</a>, and many more.</p>
<p>Another community that I wanted to give a shout-out to is <a target="_blank" href="https://www.angularnation.net/">Angular Nation</a> which is a really nice community of people who loves Angular. I didn't know that such a community existed before I got an invitation from <a target="_blank" href="https://twitter.com/connieleung404">Connie</a> to join Angular Nation.</p>
<p>I met a lot of cool folks there and also had a chance to interact with <a target="_blank" href="https://twitter.com/bonnster75">Bonnie Brennan</a>. She gave me a lot of support and motivation. I went on to give multiple talks on different channels in Angular Nation. There are a lot of people that I interacted with in Angular Nation like Sander, Stephen, Rob, Ankita, Valentine, Oyemade, Debmallya, etc.</p>
<p>I am also thankful to all of my supporters on <a target="_blank" href="https://www.buymeacoffee.com/adisreyaj">Buymeacoffee</a> for their support.</p>
<h3 id="heading-blogs-on-web-development">Blogs on web development</h3>
<p>Covid really changed a lot of things for me. Working from home gave me a lot of time that I can use to invest in learning more advanced topics and also blog about my learnings. This definitely has been a turning point for me. </p>
<p>I put my old blog into maintenance mode and started writing blogs related to web development.
You can find my blogs here:</p>
<ul>
<li><a target="_blank" href="https://sreyaj.dev/">sreyaj.dev</a></li>
<li><a target="_blank" href="https://dev.to/adisreyaj">dev.to</a></li>
</ul>
<p>I realized that I learned and researched more while I write blog posts. I was not just helping others by sharing these articles but was helping myself by researching a lot about topics I write about. So If you are someone who likes to learn and if you want to learn better, I would suggest documenting them as blog posts.</p>
<p>I mostly consider my blogs as documentation that I can go back and refer to just in case. I became a contributor to the <a target="_blank" href="https://dev.to/angular">Official Angular Org</a> on <strong>dev.to</strong> as well, Thanks to <a target="_blank" href="https://twitter.com/manekinekko">Wassim Chegham</a>.</p>
<h2 id="heading-projects-andamp-wins">Projects &amp; Wins</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651065364743/-MEqjPfUeP.jpeg" alt="Projects" />
<em>Photo by <a target="_blank" href="https://unsplash.com/@seanlimm?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Sean Lim</a></em></p>
<p>I also worked on a bunch of side projects, most of them built using Angular and a few with React. When it comes to the back-end frameworks, I started out with ExpressJS and then shifted to NestJs for its similarities with Angular.</p>
<p>Being able to write the back-end and front-end means, I don't want to rely on others If I had an idea in mind. I can execute it on my own. </p>
<p>I've participated in multiple hackathons during Covid times and won two of them.</p>
<ul>
<li><a target="_blank" href="https://townhall.hashnode.com/auth0-hashnode-hackathon-winners">Auth0 x Hashnode Hackthon</a></li>
<li><a target="_blank" href="https://townhall.hashnode.com/netlify-x-hashnode-hackathon-winners">Netlify x Hashnode Hackthon</a></li>
</ul>
<p>Hackathons came with cash prizes and swags and on top of that people started recognizing my work which gave a confidence boost and motivation to do more.</p>
<p>I've also built up my portfolio during this time. You can check it out here: <a target="_blank" href="https://adi.so">adi.so</a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1650100132037/ojrBfHHaO.png" alt="image.png" /></p>
<p>If you want to have a look at the code, here is the link to the repo:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/adisreyaj/portfolio">https://github.com/adisreyaj/portfolio</a></div>
<p>Check out some of my projects:</p>
<ul>
<li><a target="_blank" href="https://github.com/adisreyaj/flare">Flare</a></li>
<li><a target="_blank" href="https://github.com/adisreyaj/compito">Compito</a></li>
<li><a target="_blank" href="https://github.com/adisreyaj/cartella-web">Cartella</a></li>
<li><a target="_blank" href="https://cardify.adi.so/">Cardify</a></li>
<li><a target="_blank" href="https://github.com/adisreyaj/md-resume">Md Resume</a></li>
<li><a target="_blank" href="https://github.com/adisreyaj">more...</a></li>
</ul>
<h2 id="heading-fast-forward-to-2022">Fast forward to 2022</h2>
<p>I have started my Angular journey from v7 and now we are at v13. It's been a really good journey so far. The amount of learnings that I accumulated is so much and I'm still learning. That's a really interesting thing about the software domain, you need to be up to date with what's happening. And that is something that I really like about the whole software development field.</p>
<p>Fast forward to 2022, I now work as a Software Engineer at <a target="_blank" href="https://www.traceable.ai?utm_source=sreyaj.dev">Traceable</a>. I work with a really good team of experienced engineers whom I can learn from. I really got to know how culture makes a really big difference. It's not always about the money. Finding a job where you have peace of mind is really important.</p>
<h2 id="heading-tech-stack">Tech Stack</h2>
<p>I primarily work with Angular in my day-to-day job. Since I am working as a UI developer, I don't have to work with back-end technologies or databases in general. But since I do work on side projects and do like having control over both front-end and back-end, I keep myself up to date with back-end and database technologies as well.</p>
<p>I really like TypeScript and use it in every project that I start. So whether it's back-end or front-end, TS is my go-to.</p>
<h3 id="heading-front-end-technologies">Front-end technologies</h3>
<p>Apart from Angular, I've learned React. Haven't really gotten a chance to learn Vue or other newer frameworks. I like Angular more because it's opinionated and is more comfortable working with it.</p>
<p><img src="https://cardify.vercel.app/api/badges?border=false&amp;borderColor=%23ddd&amp;borderWidth=2&amp;iconColor=&amp;icons=angular%2Creact%2Cnextdotjs%2Ctailwindcss&amp;preset=default&amp;shadow=true&amp;width=100" alt="Tech Stack" /></p>
<p>I'm not saying React is bad, I enjoy working in React as well. Especially when working on smaller projects, React makes it really easy to get-go. One thing I didn't like much in React was how we have to set up routing. But with Next.js, I don't have any complaints about routing anymore.</p>
<p>I really like working with Next.js because it has a really good Dx.</p>
<p>For styling, I used SCSS a lot, and lately, it's Tailwind CSS all the way. I just love it so much. I can write styes and iterate over them very fast.</p>
<p>For state management in Angular, I use NGXS and Ngrx Component store.</p>
<h3 id="heading-back-end-technologies">Back-end technologies</h3>
<p>Since I only know JavaScript, Node.js is the way to go. I started off with Express.js and then later started using Nest.js because of its similarities with Angular. There is no context switching required when writing the back-end as both Angular and Nest.js shares a lot of concepts.</p>
<p><img src="https://cardify.vercel.app/api/badges?border=false&amp;borderColor=%23ddd&amp;borderWidth=2&amp;iconColor=&amp;icons=nestjs%2Cprisma%2Cpostgresql%2Cmysql%2Cmongodb%2Cgraphql&amp;preset=default&amp;shadow=true&amp;width=100" alt="Tech Stack" /></p>
<p>I am leaning towards learning Fastify these days for its simplicity. </p>
<p>The majority of my back-ends are all Restful APIs, but recently started using GraphQL and really love how good it is. I'll probably stick with GQL for my future projects.</p>
<p>When it comes to databases, I prefer MongoDB, Postgres, or MySQL. Since I am not that well versed with SQL, I always use an ORM like Prisma which has served me well so far. For Mongo, the obvious choice is mongoose for me. It just works.</p>
<p>For deployments, I use a simple Ubuntu based instance from OVH and run a container in it, connect to domains using Nginx and Let's Encrypt for SSL and that's pretty much it.</p>
<p>I use Nx for all of my projects as it makes it so easier to work on both the front-end and back-end with ease. They take care of all the tooling while we can concentrate on getting the things done.</p>
<h2 id="heading-going-forward">Going forward</h2>
<p>I aspire of being a GDE in Angular at some point in time. I want to continue writing more blogs and sharing my knowledge.</p>
<p>I am also thankful to a whole bunch of people from the Angular community that helped me reach where I am today.</p>
<p>Finally special thanks to all who have supported me on <a target="_blank" href="https://www.buymeacoffee.com/adisreyaj">buymeacoffee</a>:</p>
<ul>
<li><a target="_blank" href="https://twitter.com/saurabh47g">Saurabh Gangamwar</a></li>
<li><a target="_blank" href="https://twitter.com/amalgta">Amal GTA</a></li>
<li><a target="_blank" href="https://twitter.com/haroonansari1">Haroon Ansari</a></li>
<li><a target="_blank" href="https://twitter.com/SantoshYadavDev">Santosh Yadav</a></li>
<li><a target="_blank" href="https://twitter.com/sampsonorson">Sampson Orson Jackson</a></li>
<li><a target="_blank" href="https://twitter.com/JosephRajp">Joseph Raj</a></li>
<li><a target="_blank" href="https://twitter.com/beeman_nl">Bram Borggreve</a></li>
<li><a target="_blank" href="https://twitter.com/hashnode">Hashnode</a></li>
</ul>
<p>Feel free to reach out to me if you want to chat about something or if you just want to say hi.</p>
<h2 id="heading-connect-with-me">Connect with me</h2>
<ul>
<li><a target="_blank" href="https://twitter.com/AdiSreyaj">Twitter</a></li>
<li><a target="_blank" href="https://github.com/adisreyaj">Github</a></li>
<li><a target="_blank" href="https://www.linkedin.com/in/adithyasreyaj/">Linkedin</a></li>
</ul>
<p><a target="_blank" href="https://www.buymeacoffee.com/adisreyaj"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639498527478/IA3aJ9R0J.png" alt="Buy me a pizza" /></a></p>
]]></content:encoded></item><item><title><![CDATA[Launching Flare ✨ - The twitter for developers we all have been wanting!]]></title><description><![CDATA[Flare is my take on a social network for developers. It's built entirely around the needs and interests of software developers. Flare is my entry for the Netlify x Hashnode Hackathon 🔥
TL;DR: Flare is a new kind of social networking site made especi...]]></description><link>https://sreyaj.dev/launching-flare-the-twitter-for-developers-we-all-have-been-wanting</link><guid isPermaLink="true">https://sreyaj.dev/launching-flare-the-twitter-for-developers-we-all-have-been-wanting</guid><category><![CDATA[Netlify]]></category><category><![CDATA[NetlifyHackathon]]></category><category><![CDATA[Angular]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Adithya Sreyaj]]></dc:creator><pubDate>Sat, 26 Feb 2022 18:42:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1645807562084/mw6URTMvu.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Flare is my take on a social network for developers. It's built entirely around the needs and interests of software developers. Flare is my entry for the Netlify x Hashnode Hackathon 🔥</p>
<p>TL;DR: Flare is a new kind of social networking site made especially for software developers. Twitter is a really great place where developers hang out and share insightful tweets. But there is something that is lacking there.</p>
<p>That's where Flare would fit in. It has more options for the developers to share their knowledge with the community. Like a dedicated code snippet editor, a terminal command editor, etc. No need to convert your code into a screenshot to share it. Just share it as is so that people can copy the code.</p>
<h2 id="heading-what-is-flare">What is Flare?</h2>
<p>Flare is a dedicated social networking platform for developers to share and learn about new things. With a primary focus of helping the creators share better content to their uses and making it easier for the community to benefit from these contents, Flare has a lot to offer.</p>
<h2 id="heading-features">Features ✨</h2>
<p>Here are some features that I planned for Flare. The ones implemented right now are marked. The fundamental idea is to support writing small posts within Flare. So the concept of blocks came. 
Blocks are used to create a flare, you can have text, code, images, etc in a single flare. Each of these smaller items that make up a flare is called a block.</p>
<ul>
<li>Markdown for creating flares</li>
<li>Share Code snippets ✅</li>
<li>Share terminal scripts ✅</li>
<li>Share Images ✅</li>
<li>Polls ◻️</li>
<li>Attach Github Repos</li>
<li>Share tech stack used</li>
<li>Share libraries used</li>
<li>Reorder blocks</li>
<li>A code sandbox within a flare</li>
<li>Comments ✅</li>
<li>Bookmarks ✅</li>
<li>Show Spotify last played songs ✅</li>
<li>Tips and Sponsors</li>
<li>Header Image promotions ✅</li>
<li>Kudos ✅</li>
<li>Connect Hashnode blog ✅</li>
</ul>
<p>Now that the base setup is done, all the other features could be done. The only thing needed is time. So for the sake of this hackathon, I need to focus on what's doable within a few weeks.</p>
<hr />
<h2 id="heading-design">Design 🎨</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645792340598/4QOxAlBSs.png" alt="design.png" /></p>
<p>Since it's all about the community and growing with the community, I thought of having a plant as the logo for flare. The developer community is a great place to share and learn and grow. That is what Flare is wanting to be.</p>
<hr />
<h2 id="heading-tech-stack">Tech Stack 🛠️</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645792902591/TGqx1ik3A.png" alt="flare-tech-stack.png" /></p>
<p>Flare uses a lot of cool open-source frameworks and tools. Here is a non-exhaustive list of the main ones.</p>
<h3 id="heading-front-end">Front-end</h3>
<p>The client application for flare is built using <strong>Angular</strong> and is styled using <strong>Tailwind</strong> CSS. There is no complicated state management at the moment, just relying on GraphQL caching. The common components are coming from a small UI library I'm building which is based on Tailwind.</p>
<p><a target="_blank" href="https://apollo-angular.com/docs/">Apollo GrapQL</a> is used for all things GQL in the UI.</p>
<h3 id="heading-back-end">Back-end</h3>
<p>Most of the exciting stuff happens in the back-end. The most exciting one for me is <strong>GraphQL</strong>. It's the first time I am building a GraphQL backend. The backend framework of choice for me is <strong>NestJs</strong>. It provides support for a lot of different patterns in a very well-structured manner.</p>
<p>For the NestJs - GraphQL implementation, <a target="_blank" href="https://docs.nestjs.com/graphql/quick-start#schema-first">Schema First</a> approach is followed.</p>
<p>For database, there is MySQL as the primary data store driven using Prisma. Then there is Redis for the queue and a few other purposes. </p>
<p>The whole project is a monorepo managed by Nx.</p>
<h3 id="heading-deployment">Deployment</h3>
<p>For the front-end, it's deployed on <strong><a target="_blank" href="https://www.netlify.com/">Netlify</a></strong>. Again there isn't anything special that you have to do. Just put the code in git and connect it with Netlify. Baam, you get a CICD pipeline ready for your UI.</p>
<p>When it comes to the back-end things are not that easy. I used <strong>Github Actions</strong> to build and deploy the back-end code to a remote VPS running Ubuntu. The code is built and zipped and sent to the remote server. The code once is received by the remote server, unzips it, and starts the server.</p>
<p><strong>PM2</strong> is used to manage the server, detect failures and automatically restart if required.
The APIs are then port forwarded through Nginx to a domain that is used by the UI.</p>
<p>Ref: <a target="_blank" href="https://github.com/adisreyaj/flare/blob/main/.github/workflows/main.yml">Deployment yaml file</a></p>
<hr />
<h2 id="heading-flare-deep-dive">Flare Deep Dive 🔍</h2>
<p>Now let's dive deeper into how Flare is actually built and what are the features it currently has to offer.</p>
<h3 id="heading-composing-a-flare">Composing a Flare</h3>
<p>The flare below contains multiple text blocks, an image block, a terminal script block, and a code snippet block. It shows how the posted flare is later rendered.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645797734454/5rjCWVABM.png" alt="Flare" /></p>
<p>The composer component (left side) looks very simple but is the most complex part of the whole application. There are a couple of interesting things happening around how the flare is composed. Will share them below.</p>
<h4 id="heading-technical-details">Technical Details</h4>
<p>The text field is not markdown right now, but I want to at least highlight the automatically link the URLs and also highlight the hashtags. Had to play around a bit till I could come up with something that works.
Here's how the component is build:</p>
<pre><code class="lang-ts"><span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'flare-block-text-input'</span>,
  template: <span class="hljs-string">` &lt;div
    class="relative mt-[1px] flex flex-1 overflow-y-auto rounded-md bg-slate-100 ring-primary focus-within:ring-2"
    style="min-height: 100px; max-height: 200px;"
  &gt;
    &lt;div
      #editor
      class="absolute top-0 left-0 z-10 h-full w-full border-slate-100 font-medium text-transparent caret-slate-800"
    &gt;&lt;/div&gt;
    &lt;div
      class="flare-block-text-input-rendered pointer-events-none z-20 h-full w-full border-slate-100 p-4 font-medium"
      [innerHTML]="content"
    &gt;&lt;/div&gt;
  &lt;/div&gt;`</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> FlareBlockTextInputComponent {}
</code></pre>
<p>So it's basically two divs overlapping each other. The bottom div is a rich text editor which spits out the HTML of the text. The text is then passed through <a target="_blank" href="https://github.com/gregjacobs/Autolinker.js">autolinker</a> which converts the URLs to anchor tags.</p>
<p>The text is then regex'd to find and highlight the hashtags. The final HTML is then sanitized and passed to the upper div.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> linkified = <span class="hljs-built_in">this</span>.convertLinksToAnchorTags(html);
<span class="hljs-keyword">const</span> hashtagified = extractHashTags(linkified).content;
<span class="hljs-built_in">this</span>.content =
  <span class="hljs-built_in">this</span>.sanitizer.sanitize(SecurityContext.HTML, hashtagified) ?? <span class="hljs-string">''</span>;
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645802274618/M6reHadtz.png" alt="Rich Text Editor" /></p>
<p>The bottom editor text is <code>transparent</code> so only the content in the top div will be visible. This way we get the caret and select functionality working.</p>
<hr />
<h3 id="heading-feed">Feed</h3>
<p>The home page is the feed page where you can see all the flares from the people you follow. Users will be able to like and comment on the flares. Flares can also be bookmarked so that you don't miss an interesting flare just because you didn't have time to read it.</p>
<p>Users can click on the flare to add comments.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645802473542/ngfrSu6-u.png" alt="image.png" /></p>
<p>Comments are single-level right now, meaning no nested comments.</p>
<hr />
<h3 id="heading-profile-page">Profile Page</h3>
<p>The profile page is the most exciting page of all. It's the most happening page in the application after the feeds page.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645798531101/yuhD64h7A.png" alt="Profile" /></p>
<p>Users can set custom header images and also add a good bio. The social handles provided will be shown on the page as well.</p>
<h4 id="heading-hashnode-blogs">Hashnode Blogs</h4>
<p>If the users have provided their Hashnode profile URL during onboarding, Flare will automatically fetch and display their latest blogs on the profile page. It's a nice way to showcase your blogs to the users.</p>
<h4 id="heading-header-promos-personal-billboard">Header Promos (Personal Billboard)</h4>
<p>This is a really interesting idea that came to me one day. What if we could let people advertise on our profile. So the header image acts as a billboard that can be rented out to show adverts. This way, devs, and creators can earn some money.</p>
<p><strong>Submitting a Header Promo</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645800508792/xWjaeNmUx.png" alt="image.png" /></p>
<p>Promos are basically ads proposals that can be submitted to someone by visiting their profile. The person submitting the promo can add a title, description, compensation, and the header image that needs to be posted as well. </p>
<p><strong>Applying a Promo</strong></p>
<p>Users can see all the proposals they have received by clicking on the <strong>Promo Proposals</strong> button which opens up a modal like below.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645800501384/U7fDw8bzU.png" alt="Promo received" /></p>
<p>The receiver can then choose to set the image as a header by clicking on the <strong>Apply Header</strong> button. This automatically updates the header of the user. A notification can then be sent to the advertiser.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645800430667/05xuT4CtJ.png" alt="Promo applied" /></p>
<h4 id="heading-give-and-recieve-kudos">Give and Recieve Kudos</h4>
<p>If you find someone who is really doing a great job in helping the community and sharing knowledge all the time, you can give them kudos for their contributions.</p>
<p>What a developer or a content creator needs is appreciation. Show them you appreciate them for their contributions. It'll be the biggest motivator for any developer out there.</p>
<h4 id="heading-spotify-integration">Spotify Integration</h4>
<p>A great way to share your taste in music with others. Hopefully, find other folks with the same likes in music. Just another way to make connections on Flare.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645889958426/BJCNTn05t.png" alt="Spotify" /></p>
<p>Users will be able to integrate Spotify to show the last played songs on their profile.</p>
<h2 id="heading-note-due-to-the-limitation-of-spotify-api-in-development-mode-new-users-are-not-able-to-integrate-their-spotify-quote-extension-is-required"><strong>Note</strong>: Due to the limitation of Spotify API in development mode, new users are not able to integrate their spotify. Quote extension is required!</h2>
<h3 id="heading-discover">Discover</h3>
<p>Discover page lets you find top accounts and popular flares. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645798077625/ET4beAxgC.png" alt="Discover" /></p>
<hr />
<h3 id="heading-notifications">Notifications</h3>
<p>Your regular notifications page where you get notifications when someone follows you or posts a new flare. Nothing fancy going on in here.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645798342043/BtedmEnzv.png" alt="Notifications" /></p>
<hr />
<h3 id="heading-onboarding-screens">Onboarding screens</h3>
<p>On the first login, users are taken through a series of pages to complete their profile and follow users</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645803200884/qLirPiIwEj.png" alt="Complete Profile" /></p>
<p>A few basic questions are asked and on the next page, the social handles can be added.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645803189598/lRiXJ8VxE.png" alt="Follow" /></p>
<p>To build the feed, the users are asked to follow a few existing users.</p>
<hr />
<h3 id="heading-responsiveness">Responsiveness</h3>
<p>It's not a proper application in 2022 if it's not responsive and so I've put in a bit of effort to try and make Flare as responsive as possible.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645801109308/qAtED6OJG.png" alt="Responsive" /></p>
<hr />
<h2 id="heading-recipes">Recipes 🍳</h2>
<p>Here are some functionalities worth mentioning.</p>
<h3 id="heading-authentication">Authentication</h3>
<p>Flare currently allows users to signup/login via Google or Github. This is implemented using <a target="_blank" href="https://docs.nestjs.com/security/authentication">passport</a> in the backend.</p>
<p>The authentication token is securely set on the front-end as a <strong>httpOnly</strong> cookie. This way the client itself cannot access the token via code. </p>
<pre><code class="lang-ts">res.cookie(<span class="hljs-string">'token'</span>, accessToken, {
  expires: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(<span class="hljs-built_in">Date</span>.now() + <span class="hljs-number">24</span> * <span class="hljs-number">60</span> * <span class="hljs-number">60</span> * <span class="hljs-number">1000</span>),
  httpOnly: <span class="hljs-literal">true</span>,
  secure: <span class="hljs-literal">true</span>,
  signed: <span class="hljs-literal">true</span>,
  maxAge: <span class="hljs-number">24</span> * <span class="hljs-number">60</span> * <span class="hljs-number">60</span> * <span class="hljs-number">1000</span>,
  sameSite: <span class="hljs-string">'strict'</span>,
});
</code></pre>
<p>So how does a user log out from the system? Since the client can't remove the cookie while logging out, the user gets logged back automatically as the token is present as is valid.</p>
<p>To fix this issue, we introduce a new normal cookie:</p>
<pre><code class="lang-ts"><span class="hljs-comment">/**
 * Set a non http cookie which can be removed by the client on logout
 */</span>
res.cookie(<span class="hljs-string">"token-sync"</span>, accessToken, {
  expires: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(<span class="hljs-built_in">Date</span>.now() + <span class="hljs-number">24</span> * <span class="hljs-number">60</span> * <span class="hljs-number">60</span> * <span class="hljs-number">1000</span>),
  httpOnly: <span class="hljs-literal">false</span>,
  secure: <span class="hljs-literal">true</span>,
  signed: <span class="hljs-literal">true</span>,
  maxAge: <span class="hljs-number">24</span> * <span class="hljs-number">60</span> * <span class="hljs-number">60</span> * <span class="hljs-number">1000</span>,
  sameSite: <span class="hljs-string">"strict"</span>
});
</code></pre>
<p>So now the front-end can clear out the <strong>non-http</strong> cookie. </p>
<p>The server expects both the cookies to be present to validate a session:</p>
<pre><code class="lang-ts"><span class="hljs-comment">// jwt.strategy.ts</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> JwtStrategy <span class="hljs-keyword">extends</span> PassportStrategy(Strategy) {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> config: ConfigService</span>) {
    <span class="hljs-built_in">super</span>({
      jwtFromRequest: <span class="hljs-function">(<span class="hljs-params">req</span>) =&gt;</span> {
        <span class="hljs-comment">/**
         * Get both the http and non http tokens from cookies
         * and match them.
         *
         * For logged-out user, the `token-sync` cookie will not be present.
         */</span>
        <span class="hljs-keyword">const</span> token = req.signedCookies[<span class="hljs-string">'token'</span>];
        <span class="hljs-keyword">const</span> tokenSync = req.signedCookies[<span class="hljs-string">'token-sync'</span>];
        <span class="hljs-keyword">if</span> (token &amp;&amp; tokenSync &amp;&amp; token === tokenSync) {
          <span class="hljs-keyword">return</span> token;
        } <span class="hljs-keyword">else</span> {
          <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
        }
      },
      ignoreExpiration: <span class="hljs-literal">false</span>,
      secretOrKey: config.get(<span class="hljs-string">'JWT_SECRET'</span>),
    });
  }
}
</code></pre>
<p>Problem solved ✅</p>
<h3 id="heading-file-uploads">File Uploads</h3>
<p>Files uploaded in GraphQL are not that straightforward. With REST, we just create a FormData and then append the file to it and send a POST request to the server.</p>
<h4 id="heading-strategy">Strategy</h4>
<p>I investigated how Twitter is handling media uploads for tweets. Opened up the Network tab and started looking for clues. That is when I found something interesting. I tried to think about something like that and implement it for flare. </p>
<p>Here's what I did.</p>
<ol>
<li>User adds an image to the flare.</li>
<li>Immediately the image is uploaded to the server via a REST API.</li>
<li>Server handles the file and saves it locally.</li>
<li>A job is added to the queue as well for the image to be removed if it's not used in the flare. There is a set expiration time. The job id is sent back to the UI.</li>
<li>So if the user cancels or decides to remove the image from the flare. The job will run after the specified expiration time and remove the image from the server.</li>
<li>If the user finally posts the flare, the image is then uploaded to an S3 server and removed from the local server.</li>
</ol>
<h4 id="heading-queue">Queue</h4>
<p>Bull is the most popular library for setting up Queue in a Node application. NestJs has official support for Bull and so setting up things was pretty easy. </p>
<p>Bull uses Redis as the data store. I used UpStash which provides a free Redis instance. 
A queue is basically a "queue", I mean you can put things in the queue and run them one by one as configured.</p>
<p>Setting up Queue in NestJS:</p>
<pre><code><span class="hljs-comment">// app.module.ts</span>
<span class="hljs-keyword">import</span> { <span class="hljs-title">BullModule</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">'@nestjs/bull'</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title">Module</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">'@nestjs/common'</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title">ConfigModule</span>, <span class="hljs-title">ConfigService</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">'@nestjs/config'</span>;

@Module({
  imports: [
    BullModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: async (configService: ConfigService) <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> ({
        redis: {
          host: configService.get(<span class="hljs-string">'REDIS_HOST'</span>),
          port: <span class="hljs-operator">+</span>configService.get(<span class="hljs-string">'REDIS_PORT'</span>),
          password: configService.get(<span class="hljs-string">'REDIS_PASSWORD'</span>),
          maxRetriesPerRequest: <span class="hljs-number">5</span>,
          tls: {
            host: configService.get(<span class="hljs-string">'REDIS_HOST'</span>),
          },
        },
        prefix: <span class="hljs-string">'flare'</span>,
      }),
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}
</code></pre><h4 id="heading-cloud-storage">Cloud Storage</h4>
<p>The files are uploaded to Backblaze which provides Object storage with an S3 compatible API. Backblaze has a generous free plan. There is something interesting about <a target="_blank" href="https://www.backblaze.com/">Backblaze</a>. So under the Bandwidth Allianze program data transferred between Backblaze and Cloudflare is <a target="_blank" href="https://www.backblaze.com/blog/backblaze-and-cloudflare-partner-to-provide-free-data-transfer/">free</a>. Meaning I will only have to worry about uploads.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645813605517/XZc67Xc5v.png" alt="Cloudflare Backblaze alliance" /></p>
<p><strong>Uploading files to Backblaze</strong></p>
<p>Files are uploaded from the server to cloud using the AWS SDK. Here's how the NestJs service for uploads will look like:</p>
<pre><code class="lang-ts"><span class="hljs-meta">@Injectable</span>()
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> S3Service {
  s3: S3Client;
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> config: ConfigService</span>) {
    <span class="hljs-built_in">this</span>.s3 = <span class="hljs-keyword">new</span> S3Client({
      endpoint: config.get(<span class="hljs-string">'S3_ENDPOINT'</span>),
      region: config.get(<span class="hljs-string">'S3_REGION'</span>),
      credentials: {
        accessKeyId: config.get(<span class="hljs-string">'S3_ACCESS_KEY_ID'</span>),
        secretAccessKey: config.get(<span class="hljs-string">'S3_SECRET_ACCESS_KEY'</span>),
      },
    });
  }

  <span class="hljs-keyword">async</span> upload(file: FileWithMeta) {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.s3.send(
      <span class="hljs-keyword">new</span> PutObjectCommand({
        Bucket: <span class="hljs-built_in">this</span>.config.get(<span class="hljs-string">'S3_BUCKET'</span>),
        Key: file.filename,
        Body: file.buffer,
        ContentType: file.mimetype,
      })
    );
  }

  <span class="hljs-keyword">async</span> <span class="hljs-keyword">delete</span>(file: <span class="hljs-built_in">string</span>) {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.s3.send(
      <span class="hljs-keyword">new</span> DeleteObjectCommand({
        Bucket: <span class="hljs-built_in">this</span>.config.get(<span class="hljs-string">'S3_BUCKET'</span>),
        Key: file,
      })
    );
  }
}
</code></pre>
<p>You might have noticed that there is no function written for getting the file from the cloud. Because we don't need it. </p>
<p>Cloudflare sits in between Backblaze and the web app. So we can directly query the files via Cloudflare without any data transfer charges. And on top of that add additional headers to the request to cache images.</p>
<p>How cool is that?</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645813432539/2jksqo8Sr.png" alt="Cloudflare DNS config" /></p>
<p>That is if the image URL in Backblaze is:</p>
<pre><code class="lang-sh">https://f004.backblazeb2.com/file/flare-media/ckzvelloy0000z8xchry323ng.jpeg
</code></pre>
<p>It becomes:</p>
<pre><code class="lang-sh">https://flare-cdn.adi.so/file/flare-media/ckzvelloy0000z8xchry323ng.jpeg
</code></pre>
<p>Here is a details tutorial for setting up the connection between Cloudflare and Backblaze:
https://help.backblaze.com/hc/en-us/articles/217666928-Using-Backblaze-B2-with-the-Cloudflare-CDN</p>
<p><strong>Retrieving the Images in Web App</strong></p>
<p>So when an image is uploaded the image name is the identifier and that is what gets stored in the DB. The image name will be sent to the web app as well. We use a simple Pipe to construct the full media URL from the image name:</p>
<pre><code class="lang-ts"><span class="hljs-meta">@Pipe</span>({
  name: <span class="hljs-string">'mediaUrl'</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> MediaUrlPipe <span class="hljs-keyword">implements</span> PipeTransform {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-meta">@Inject</span>(API_CONFIG) <span class="hljs-keyword">private</span> apiConfig: ApiConfig</span>) {}

  transform(value: <span class="hljs-built_in">string</span>): <span class="hljs-built_in">string</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-string">`<span class="hljs-subst">${<span class="hljs-built_in">this</span>.apiConfig.mediaURL}</span>/<span class="hljs-subst">${value}</span>`</span>; <span class="hljs-comment">// &lt;-- cloudflare connected domain</span>
  }
}
</code></pre>
<p>The benefit here is that we don't have to write APIs to fetch images from the cloud and then stream it back to the web app. This puts load onto our server and we have to do exception handling and caching-related stuff on our side. This just eliminates everything.</p>
<h3 id="heading-spotify-integration">Spotify Integration</h3>
<p>Users can choose to show their last played songs from Spotify by connecting their Spotify account with Flare.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645897491661/H4Yt2HQ34.png" alt="Spotify integration" /></p>
<h4 id="heading-technical-details">Technical Details</h4>
<p>The whole feature is implemented using Netlify Functions which is their serverless offering. Setting up this using Netlify functions was pretty easy once I figured out the logic.</p>
<p><strong>1. Getting the user's refresh token from Spotify</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645899849852/T8JV84j7u.png" alt="Getting token" /></p>
<ul>
<li>The user clicks on Connect Spotify button.</li>
<li>A new window is opened and the <code>spotify-authorize</code> Netlify function endpoint is called inside.</li>
<li>User can log in to their Spotify account.</li>
<li>Spotify redirects to <code>spotify-callback</code> on successful authorization.</li>
<li>Callback encrypts the refresh token and saves it in Redis with the user's username.</li>
<li>Once this is done, the popup window is closed.</li>
</ul>
<p><strong>1. Using the token to get the last played songs</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645899082455/JDc2JApNN.png" alt="Retrieving songs" /></p>
<ul>
<li>The <code>spotify-last-played</code> Netlify function endpoint is then called to get the user's songs. </li>
<li>Refresh token is retrieved from Redis and then decrypted.</li>
<li>An new <a target="_blank" href="https://developer.spotify.com/documentation/general/guides/authorization/code-flow/">auth token is generated</a> for calling the Spotify API.</li>
<li>The <a target="_blank" href="https://developer.spotify.com/console/get-recently-played/">Recently Played API</a> endpoint is called to get the list of songs.</li>
<li>The songs are then saved to Redis for a day.</li>
<li>Response is then sent back to UI with required Cache headers.</li>
</ul>
<p><strong> Cache working</strong>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645899074290/uFPkhemdB.png" alt="Cache" /></p>
<p>When the API is called again, the cache will be checked first to see if the user's songs are already retrieved or not. If it exists, they are returned back.</p>
<p>Code references:</p>
<ol>
<li>Authorization Function - <a target="_blank" href="https://github.com/adisreyaj/flare/blob/main/netlify/functions/spotify-authorize/spotify-authorize.ts">spotify-authorize.ts</a></li>
<li>Authorization Callback - <a target="_blank" href="https://github.com/adisreyaj/flare/blob/main/netlify/functions/spotify-callback/spotify-callback.ts">spotify-callback.ts</a></li>
<li>Get Last Played Songs - <a target="_blank" href="https://github.com/adisreyaj/flare/blob/main/netlify/functions/spotify-last-played/spotify-last-played.ts">spotify-last-played.ts</a></li>
</ol>
<hr />
<h2 id="heading-challenges">Challenges ⏳</h2>
<p>There were lots of challenges along the way. Firstly, GraphQL is very new to me. So learning and doing it all in a few weeks was definitely challenging. But worth it as now I know why people chose GraphQL these days.</p>
<h3 id="heading-how-i-spent-hours-debugging-a-silly-issue">How I spent hours debugging a silly issue 😫</h3>
<p>The introduction of queues was not as easy for me as I made it sound. I made a stupid mistake at first which caused my server to consume a crazy amount of resources and was causing crashes.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645794469957/XJSz9EdAW.png" alt="High CPU Usage" /></p>
<p>This is a screenshot of my PM2 dashboard when the server was idling. </p>
<h4 id="heading-what-could-have-gone-wrong">What could have gone wrong?</h4>
<p>My first hunch was its because I used RxJs for some of the queries. I used it queries which need to get some information from one DB call and then do another DB call based on it. I could have used multiple <code>async-awaits</code> to get it done. But I thought RxJs made such code better.
Here's an example:</p>
<pre><code class="lang-ts">findAllFlaresFromFollowingUsers(user: CurrentUser) {
  <span class="hljs-keyword">const</span> currentUsersFollowing$ = <span class="hljs-keyword">from</span>(
    <span class="hljs-built_in">this</span>.prisma.user.findUnique({
      where: {
        id: user.id,
      },
      select: {
        following: {
          select: {
            id: <span class="hljs-literal">true</span>,
          },
        },
      },
    })
  );

  <span class="hljs-keyword">const</span> getFlaresByAuthorIds$ = <span class="hljs-function">(<span class="hljs-params">userIds: <span class="hljs-built_in">string</span>[]</span>) =&gt;</span>
    <span class="hljs-keyword">from</span>(
      <span class="hljs-built_in">this</span>.prisma.flare.findMany({
        where: {
          deleted: <span class="hljs-literal">false</span>,
          authorId: {
            <span class="hljs-keyword">in</span>: userIds,
          },
        },
        include: getFlareFieldsToInclude(user.id),
        orderBy: { createdAt: <span class="hljs-string">'desc'</span> },
      })
    );

  <span class="hljs-keyword">return</span> currentUsersFollowing$.pipe(
    switchMap(<span class="hljs-function">(<span class="hljs-params">{ following }</span>) =&gt;</span> {
      <span class="hljs-keyword">if</span> (isNil(following)) {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">of</span>([]);
      }

      <span class="hljs-keyword">return</span> getFlaresByAuthorIds$(
        following.map(<span class="hljs-function">(<span class="hljs-params">{ id }</span>) =&gt;</span> id).concat(user.id)
      );
    }),
    take(<span class="hljs-number">1</span>)
  );
}
</code></pre>
<p>So I suspected that there is some memory leak because of the subscriptions. I knew that NestJs handles the subscription and subscription for us. But silly me thought RxJs is to blame here.</p>
<p>I converted all the RxJs code to promise based like any sane man would've done in the first place.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645795194314/I3EMC0uUh.png" alt="image.png" /></p>
<p>After countless hours of debugging, turns out it was the flipping queue that was causing the issue and not RxJs. The bull was trying to connect to the Redis, but it failed and crashed. The reason was that for the TLS connection, there is a special configuration that needs to be passed while configuring the queue.</p>
<p>What helped me find the issue? Logging. Loggin is very important to any application. It can help you find and fix problems fast. I've used <a target="_blank" href="https://github.com/iamolegga/nestjs-pino">NestJs-Pino</a> for logging purposes.</p>
<h4 id="heading-outcome">Outcome</h4>
<p>While creating the Redis DB in Upstash, it gives an option to enable TLS or not. So if you are trying to connect to a TLS enabled Redis DB in Bull. Make sure to add this to your queue config:</p>
<pre><code class="lang-js">{
  <span class="hljs-attr">redis</span>: {
    <span class="hljs-attr">host</span>: configService.get(<span class="hljs-string">'REDIS_HOST'</span>),
    <span class="hljs-attr">port</span>: +configService.get(<span class="hljs-string">'REDIS_PORT'</span>),
    <span class="hljs-attr">password</span>: configService.get(<span class="hljs-string">'REDIS_PASSWORD'</span>),
    <span class="hljs-attr">maxRetriesPerRequest</span>: <span class="hljs-number">5</span>,
    <span class="hljs-attr">tls</span>: { <span class="hljs-comment">//&lt;-- TLS config</span>
      <span class="hljs-attr">host</span>: configService.get(<span class="hljs-string">'REDIS_HOST'</span>), 
    },
  },
  <span class="hljs-attr">prefix</span>: <span class="hljs-string">'flare'</span>,
}
</code></pre>
<p>Here's the SO thread that helped me fix the issue: https://stackoverflow.com/a/57896863/10781739</p>
<p>After all this, I later switched back to local Redis install in a docker container 😅</p>
<h2 id="heading-coding-practices">Coding Practices</h2>
<ul>
<li><p>✅ <strong>Linting</strong> is taken care of by <strong>ESLint</strong> (Thanks to Nx, no work to be done for setting up linting). </p>
</li>
<li><p>✅ <strong>Code formatting</strong> is taken care of by <strong>Prettier</strong> ( Don't know what I would do without it). </p>
</li>
<li><p>✅ And commit messages are following <strong>Conventional Commits</strong> enforced using <strong>Husky</strong></p>
</li>
<li><p>✅ Logging is thanks to <strong>NestJs-Pino</strong>. I can't stress how important logging is for any application out there. Proper logs make debugging easier.</p>
</li>
<li><p>✅ Code Comments are added "only" when required. Not a fan of polluting the codebase with irrelevant code comments.</p>
</li>
<li><p>✅ Custom exception handling for Prisma related errors using <a target="_blank" href="https://docs.nestjs.com/graphql/other-features#exception-filters">NestJs Exception Filters</a>:</p>
</li>
</ul>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> PRISMA_ERROR_CODE_TO_STATUS = {
  P2025: {
    code: <span class="hljs-number">404</span>,
    message: <span class="hljs-string">'Resource not found'</span>,
  },
};

<span class="hljs-meta">@Catch</span>(PrismaClientKnownRequestError)
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> PrismaExceptionFilter <span class="hljs-keyword">implements</span> GqlExceptionFilter {
  logger = <span class="hljs-keyword">new</span> Logger(PrismaExceptionFilter.name);
  <span class="hljs-keyword">catch</span>(exception: PrismaClientKnownRequestError, host: ArgumentsHost) {
    <span class="hljs-keyword">const</span> { code, message } = PRISMA_ERROR_CODE_TO_STATUS[exception.code] ?? {
      code: <span class="hljs-number">500</span>,
      message: <span class="hljs-string">'Internal server error'</span>,
    };
    <span class="hljs-built_in">this</span>.logger.error({
      code,
      message,
      exception,
    });
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> HttpException(message, code);
  }
}
</code></pre>
<h2 id="heading-code-and-demo">Code and Demo 💫</h2>
<p><strong>Demo</strong>: https://flare-api.adi.so/api/demo
For those who just want to <strong>test drive</strong> and not sign in with their accounts, Try the above link 👆🏻</p>
<p><strong>Link</strong>: https://flare.adi.so</p>
<p><strong>Note</strong>: Please don't spam 🙏🏻. Respect others 😊
Its' not a production app right now, still in a very early stage and there will be unforeseen bugs. Please don't try to break it 🥺</p>
<p>Attaching the GitHub repo for the project.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/adisreyaj/flare">https://github.com/adisreyaj/flare</a></div>
<p>Right now only Google and Github based logins are enabled. Demo env will be flushed out regularly so don't worry about your personal data.</p>
<h2 id="heading-running-locally">Running Locally 💻</h2>
<h4 id="heading-1-clone-the-repo">#1. Clone the repo</h4>
<pre><code class="lang-sh">git <span class="hljs-built_in">clone</span> https://github.com/adisreyaj/flare.git
</code></pre>
<h4 id="heading-2-initialize-the-submodule-ui-components">#2. Initialize the submodule (UI components)</h4>
<p>I created a small UI component library called Zigzag that is used in the project as a submodule. </p>
<pre><code class="lang-sh">git submodule update --init
</code></pre>
<h4 id="heading-3-install-the-dependencies">#3.  Install the dependencies</h4>
<pre><code class="lang-sh">npm install
</code></pre>
<h4 id="heading-4-setup-the-environment-variables">#4. Setup the environment variables</h4>
<p>Set up all the required environment variables required for the back-end:</p>
<pre><code>NODE_ENV=development
DATABASE_URL=mysql://root:root@localhost:3307/flare
FRONT_END_CALLBACK_URL=http://localhost:4200/auth/callback

<span class="hljs-comment"># JWT sign secret</span>
JWT_SECRET=veryverysecretkey
JWT_EXPIRY=<span class="hljs-string">"3d"</span>
COOKIE_SECRET=veryverysecretsessionkey

<span class="hljs-comment"># Google OAuth Details</span>
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_CALLBACK_URI=http://localhost:3333/api/auth/google/callback

<span class="hljs-comment"># Github OAuth Details</span>
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
GITHUB_CALLBACK_URI=http://localhost:3333/api/auth/github/callback

<span class="hljs-comment"># Queue</span>
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=

<span class="hljs-comment"># Object Storage</span>
S3_ENDPOINT=
S3_REGION=
S3_BUCKET=
S3_ACCESS_KEY_ID=
S3_SECRET_ACCESS_KEY=
</code></pre><h4 id="heading-5-prepare-the-database">#5. Prepare the Database</h4>
<p>Use docker-compose to spin up MySQL and Redis databases.</p>
<pre><code class="lang-yml"><span class="hljs-attr">version:</span> <span class="hljs-string">'3.1'</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">db:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">mariadb</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">MYSQL_ROOT_PASSWORD:</span> <span class="hljs-string">root</span>
      <span class="hljs-attr">MYSQL_DATABASE:</span> <span class="hljs-string">flare</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'3307:3306'</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">/Users/&lt;username&gt;/Desktop/code/db:/var/lib/mysql</span>
  <span class="hljs-attr">cache:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">redis</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'6379:6379'</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">/Users/&lt;username&gt;/Desktop/code/cache:/var/lib/redis</span>
</code></pre>
<h4 id="heading-6-set-up-the-database">#6. Set up the Database</h4>
<p>Run the command to populate the DB with tables:</p>
<pre><code class="lang-sh">npm run prisma:migrate
</code></pre>
<h4 id="heading-7-generate-the-graphql-interfaces-from-the-schema">#7. Generate the GraphQL interfaces from the schema</h4>
<p>Run the command to generate the required types:</p>
<pre><code class="lang-sh">npm run generate:gql
</code></pre>
<h4 id="heading-8-start-up-the-ui-and-back-end">#8. Start up the UI and Back-end</h4>
<p>For UI:</p>
<pre><code class="lang-sh">npm start
</code></pre>
<p>For Back-end</p>
<pre><code class="lang-sh">npm start api
</code></pre>
<p>UI: <code>http://localhost:4200</code> &amp; GQL: <code>http://localhost:3333</code></p>
<p>You are all set for exploring Flare locally.</p>
<h2 id="heading-links-and-references">Links and References 🔗</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Title</td><td>Link</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td>Angular</td><td>https://angular.io/</td><td>Front-end framework</td></tr>
<tr>
<td>NestJs</td><td>https://docs.nestjs.com/</td><td>Back-end framework based on NodeJs</td></tr>
<tr>
<td>Netlify</td><td>https://www.netlify.com/</td><td>Deployment for UI</td></tr>
<tr>
<td>Prisma</td><td>https://www.prisma.io/</td><td>Node.js and TypeScript ORM</td></tr>
<tr>
<td>Tailwind CSS</td><td>https://tailwindcss.com/</td><td>Utility first CSS framework</td></tr>
<tr>
<td>Nx</td><td>https://nx.dev/#getting-started</td><td>Build system with monorepo support</td></tr>
<tr>
<td>PM2</td><td>https://app.pm2.io/</td><td>Advanced, production process manager for Node.JS</td></tr>
<tr>
<td>Upstash</td><td>https://upstash.com/</td><td>Serverless Redis DB</td></tr>
<tr>
<td>Backblaze</td><td>https://www.backblaze.com/</td><td>Cloud Storage</td></tr>
</tbody>
</table>
</div><h2 id="heading-connect-with-me">Connect with me</h2>
<ul>
<li><a target="_blank" href="https://twitter.com/AdiSreyaj">Twitter</a></li>
<li><a target="_blank" href="https://github.com/adisreyaj">Github</a></li>
<li><a target="_blank" href="https://www.linkedin.com/in/adithyasreyaj/">Linkedin</a></li>
<li><a target="_blank" href="https://cardify.adi.so">Cardify</a> - Dynamic SVG Images for Github Readmes</li>
</ul>
<p>Do add your thoughts or if you have any questions, shoot'em below in the comments.
Stay Safe ❤️</p>
<p><a target="_blank" href="https://www.buymeacoffee.com/adisreyaj"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639498527478/IA3aJ9R0J.png" alt="Buy me a pizza" /></a></p>
]]></content:encoded></item><item><title><![CDATA[✨Libshare - Curate & Share Your favorite JavaScript Libraries!]]></title><description><![CDATA[Libshare helps you curate and share your favorite NPM libraries with the community. Showcase all the awesome libraries that you used in your side project and give them visibility. Made for the ♥ of open-source.
What is Libshare?
You can think of Libs...]]></description><link>https://sreyaj.dev/curate-and-share-javascript-libraries-using-libshare</link><guid isPermaLink="true">https://sreyaj.dev/curate-and-share-javascript-libraries-using-libshare</guid><category><![CDATA[Web Development]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[APIs]]></category><category><![CDATA[webdev]]></category><category><![CDATA[Angular]]></category><dc:creator><![CDATA[Adithya Sreyaj]]></dc:creator><pubDate>Fri, 18 Feb 2022 17:24:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1645204711895/29uZmdo3f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Libshare helps you curate and share your favorite NPM libraries with the community. Showcase all the awesome libraries that you used in your side project and give them visibility. Made for the ♥ of open-source.</p>
<h2 id="heading-what-is-libshare">What is Libshare?</h2>
<p>You can think of Libshare as a curator for JavaScript libraries. How often do you come across a library and then when you need to use it, you forget the name of the library?</p>
<p>Happens to me every time. Libshare solves that problem.</p>
<p>Another great use-case for Libshare is to bring visibility to the open-source packages that are used in your projects. Don't let them hide in your <strong>package.json</strong> file. Let people know about the libraries that are used in building amazing products.</p>
<p>Curate all the libraries used in your project and add them to a list. You can get a public shareable link to it which can be added in Github Readme or blog posts. This makes it easier for people to find out more about the libraries.</p>
<h2 id="heading-tech-stack">Tech Stack</h2>
<p>If you look at the subtitle for the blog post, it says "Powered by Angular and HarperDB". Notice that there is a front-end framework and then there is a Database. You might be wondering What is the back-end used for?</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645204735526/B8skJZiMf.png" alt="angular-hdb.png" /></p>
<p>The interesting thing about the project is that I didn't have to develop a separate back-end, I didn't have to deploy it, set up SSL, or do any of the things I usually do when I'm building a <a target="_blank" href="https://blog.sreyaj.dev/compito-project-management-app-angular-nestjs-auth0">side project</a>.</p>
<p>The whole back-end is written as Custom Functions in HarperDB. It's crazy, I know right? I didn't think that it could do so much when I was first introduced to HarperDB. But once I used it, I can say that it has great potential.</p>
<p>I'll make sure to link to the list of all the libraries that are used in making this project.</p>
<h2 id="heading-setting-up-the-back-end">Setting up the Back-end</h2>
<p>So there are two parts to the Back-end.</p>
<ol>
<li>Setting up Database</li>
<li>API Server which talks to the DB</li>
</ol>
<p>Let's start by setting up the DB for the application.</p>
<p>We are going to be using a functionality called <a target="_blank" href="https://harperdb.io/docs/custom-functions/custom-functions-operations/">Custom Functions</a>. </p>
<h3 id="heading-harperdb-custom-functions">HarperDB Custom Functions</h3>
<p>Custom functions are one of the most interesting features of HarperDB. It's so versatile and makes life easy for a developer. </p>
<ul>
<li><p>Custom functions allow us to create API routes inside of HarperDB. Yes, you heard me. No need to create a separate API server. </p>
</li>
<li><p>You can directly interact with HarperDB from within the custom functions. What this means for you is that no more configuration or initialization is required.</p>
</li>
<li><p>You can write the whole server in the Integrate IDE in HarperDB Studio or if you are like me who prefers to do things locally, you can write your functions locally and then deploy it once it's ready.</p>
</li>
</ul>
<p>I'll be setting up a local instance of HarperDB to write the custom functions and then once everything is ready, will deploy it to a cloud instance. In this way, I can code faster, test it out better, and once everything is ready, deploying it is easy-peasy.</p>
<p><strong>Note</strong>: If you like to write your functions inside the Harper Studio, you can skip setting up the local instance and write your functions directly in the cloud instance.</p>
<h2 id="heading-setting-up-a-harperdb">Setting up a HarperDB</h2>
<p>There are two ways to setup HarperDB:</p>
<ol>
<li>Use their cloud offering</li>
<li>Self-host</li>
</ol>
<h3 id="heading-installing-harperdb-using-docker">Installing HarperDB using Docker.</h3>
<p>There are different ways to install a local instance. You can read more about them <a target="_blank" href="https://harperdb.io/docs/install-harperdb/">here</a>. I'll be using docker to create a container using the <code>harperdb/harperdb</code> image.</p>
<p>Create a file called <code>docker-compose.yml</code> and copy over the content below into that file:</p>
<pre><code class="lang-yml"><span class="hljs-attr">version:</span> <span class="hljs-string">'3.3'</span>
<span class="hljs-attr">services:</span>
    <span class="hljs-attr">harperdb:</span>
        <span class="hljs-attr">volumes:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">/Users/admin/Desktop/libshare:/opt/harperdb/hdb</span>
        <span class="hljs-attr">environment:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">HDB_ADMIN_USERNAME=admin</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">HDB_ADMIN_PASSWORD=password</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">CUSTOM_FUNCTIONS=true</span>
        <span class="hljs-attr">ports:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">'9925:9925'</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">'9926:9926'</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">harperdb/harperdb</span>
</code></pre>
<p><strong>Note</strong>: Provide a valid path for <code>volumes</code> (left-hand side of <code>:</code>). That's where we will be setting up the custom functions.</p>
<p>You can then start the container by running:</p>
<pre><code class="lang-sh">docker-compose up
</code></pre>
<p>Run this command in a separate terminal and keep this open, so you can watch the logs.</p>
<h3 id="heading-registering-the-user-installed-instance">Registering the User Installed Instance</h3>
<p>Go ahead and sign up for an account in HarperDB if you haven't already. </p>
<ul>
<li>Once you are logged in, create an <strong>Organization</strong>.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643437329703/R1qb5fVVG.jpeg" alt="org.jpeg" /></p>
<ul>
<li>Once you are inside the newly created Org, you can click on the <strong>Add</strong> button, which gives you two options. Choose the second option which is <strong>Register User-Installed Instance</strong> and fill in the details.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643437376514/VnAIrITmA.png" alt="instance.png" /></p>
<ul>
<li>You should now be taken to the studio page.</li>
</ul>
<h3 id="heading-setting-up-tables">Setting up tables</h3>
<p>Now we set up the tables required. For that create a new schema first, and then add tables.
Schema is nothing but for grouping tables. I've just named the schema as <code>data</code>.</p>
<p>Setup 3 tables like so:</p>
<ol>
<li>users</li>
<li>lists</li>
<li>libraries</li>
</ol>
<p><strong>Note</strong>: The <code>hash_attr</code> is kind of the primary key. Just use <code>id</code> in our case.</p>
<p>Here's how my setup looks:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1644953677577/IgrJY6kUi.png" alt="Schema and Tables" /></p>
<h3 id="heading-setting-up-custom-functions">Setting up custom functions</h3>
<p>Going to the <strong>Functions</strong> tab will land you on a page where you can create the routes for your API.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643437893780/xOnN7RwWX.jpeg" alt="custom-functions.jpeg" /></p>
<p>Start by creating a new project first. I named it <code>api</code>. The project name can be used to namespace your APIs. So in this case, the endpoint will look like: <code>http://localhost:9926/api</code>.</p>
<p>We are all done setting up the instance and the table. The only thing that is left is to go to the <strong>Config</strong> page and copy the <code>Instance API Auth Header</code> which we need to use.</p>
<h3 id="heading-creating-the-apis">Creating the APIs</h3>
<p>If you navigate to the folder that is mentioned under the <code>volumes</code> in the <code>docker-compose.yml</code> file, you can see there are a couple of folders that got created.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643438524348/TwmMfwK91.png" alt="harper db files" /></p>
<p>We will be working in the <code>custom_functions</code> directory. Navigate into the <code>custom_functions</code> folder and open your favorite IDE.</p>
<p>The first thing that you will notice, there is an <code>api</code> folder. So each project that you create on the custom functions page, will have its folders. The folders would have the following folders/files:</p>
<pre><code class="lang-sh">├── helpers
├── routes
└── static
</code></pre>
<p>The names are self-explanatory.</p>
<p>Let's start by initializing git in the <code>custom_functions</code> folder so that we can save it to a repo.</p>
<pre><code>git <span class="hljs-keyword">init</span>
</code></pre><p>also, initialize npm so that we can install packages</p>
<pre><code>npm <span class="hljs-keyword">init</span>
</code></pre><p>You can see a few boilerplate files inside these folders, just delete them so we can start fresh.</p>
<h4 id="heading-setting-up-first-route">Setting up first route</h4>
<p>You can create multiple files inside the route to organize things better. So we'll few files:</p>
<pre><code class="lang-sh">├── auth.js
├── general.js
├── libraries.js
├── lists.js
└── users.js
</code></pre>
<p>Here's what a route file would look like:</p>
<pre><code class="lang-js"><span class="hljs-meta">'use strict'</span>;

<span class="hljs-built_in">module</span>.exports = <span class="hljs-keyword">async</span> (server) =&gt; {
  server.route({
    <span class="hljs-attr">url</span>: <span class="hljs-string">'/'</span>,
    <span class="hljs-attr">method</span>: <span class="hljs-string">'GET'</span>,
    <span class="hljs-attr">handler</span>: <span class="hljs-function">(<span class="hljs-params">request, reply</span>) =&gt;</span> {
      <span class="hljs-keyword">return</span> {
        <span class="hljs-attr">status</span>: <span class="hljs-string">'OK'</span>,
      };
    },
  });
};
</code></pre>
<p>The best thing about HarperDB custom functions is that they are powered by Fastify which is fantastic. The route files contain basic <a target="_blank" href="https://www.fastify.io/docs/latest/Reference/Routes/">Fastify route</a> declarations.</p>
<p>For maintainability and a better overall structure of code, You can extract the handler into a separate file and add it to the <code>helpers</code> folder. It's not necessary, but doable and I would highly recommend it. I've split my code into multiple handlers and helper files:</p>
<pre><code class="lang-sh">├── auth-handler.js
├── auth-helper.js
├── errors-helper.js
├── libraries-handler.js
├── libraries-helper.js
├── list-handler.js
├── list-helper.js
├── query-builder-helper.js
├── users-handler.js
└── users-helper.js
</code></pre>
<p>Let's see a complete implementation of how to set up a signup route and its handler:</p>
<pre><code class="lang-js"><span class="hljs-comment">// users-handler.js</span>

<span class="hljs-keyword">const</span> createUserHandler =
  <span class="hljs-function">(<span class="hljs-params">{ hdbCore }</span>) =&gt;</span>
  <span class="hljs-keyword">async</span> (request) =&gt; {
    <span class="hljs-keyword">const</span> { firstName, lastName, email, password } = request.body;
    <span class="hljs-keyword">const</span> hashedPass = <span class="hljs-keyword">await</span> hashPassword(password);

    <span class="hljs-keyword">const</span> sqlReq = {
      ...request,
      <span class="hljs-attr">body</span>: {
        <span class="hljs-attr">operation</span>: <span class="hljs-string">'sql'</span>,
        <span class="hljs-attr">sql</span>: qb.buildInsertQuery(<span class="hljs-string">'data.users'</span>, {
          firstName,
          lastName,
          email,
          <span class="hljs-attr">password</span>: hashedPass,
        }),
      },
    };

    <span class="hljs-keyword">return</span> hdbCore.requestWithoutAuthentication(sqlReq);
  };

<span class="hljs-built_in">module</span>.exports = { createUserHandler }
</code></pre>
<p>and the route:</p>
<pre><code class="lang-js"><span class="hljs-meta">'use strict'</span>;

<span class="hljs-keyword">const</span> userHelpers = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../helpers/users-helper'</span>);
<span class="hljs-keyword">const</span> userHandlers = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../helpers/users-handler'</span>);

<span class="hljs-built_in">module</span>.exports = <span class="hljs-keyword">async</span> (server, hdb) =&gt; {
  server.route({
    <span class="hljs-attr">url</span>: <span class="hljs-string">'/signup'</span>,
    <span class="hljs-attr">method</span>: <span class="hljs-string">'POST'</span>,
    <span class="hljs-attr">preValidation</span>: [userHelpers.validateUser(hdb.logger), userHelpers.existingUserValidation(hdb)],
    <span class="hljs-attr">handler</span>: userHandlers.createUserHandler(hdb),
  });
};
</code></pre>
<p><strong>Note</strong>: You can also add Validation methods where the authentication can be checked or the request body can be validated.</p>
<pre><code class="lang-js"><span class="hljs-comment">// users-helper.js</span>
<span class="hljs-meta">
'use strict'</span>;

<span class="hljs-keyword">const</span> joi = <span class="hljs-built_in">require</span>(<span class="hljs-string">'joi'</span>);
<span class="hljs-keyword">const</span> errors = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./errors-helper'</span>);

<span class="hljs-keyword">const</span> USER_VALIDATION_SCHEMA = joi.object({
  <span class="hljs-attr">firstName</span>: joi.string().required(),
  <span class="hljs-attr">lastName</span>: joi.string().required(),
  <span class="hljs-attr">email</span>: joi.string().email().required(),
  <span class="hljs-attr">password</span>: joi.string().required(),
});

<span class="hljs-keyword">const</span> validateUser = <span class="hljs-function">(<span class="hljs-params">logger</span>) =&gt;</span> <span class="hljs-keyword">async</span> (request, reply) =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">await</span> USER_VALIDATION_SCHEMA.validate(request.body);
    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
  } <span class="hljs-keyword">catch</span> (error) {
    logger.error(<span class="hljs-string">'Bad Request'</span>);
    errors.badRequest(reply);
  }
};

<span class="hljs-built_in">module</span>.exports = {
  validateUser
};
</code></pre>
<p>See how I've used <code>joi</code> for validating the request body. You can install and use different libraries like this inside the helpers/routes. <a target="_blank" href="https://github.com/sideway/joi">Joi</a> can be installed with the following command:</p>
<pre><code class="lang-sh">npm install joi
</code></pre>
<p>Head over to the documentation website of Joi for more info: https://joi.dev/</p>
<p>Once all the endpoints are set up. We can now deploy the functions to a cloud instance.</p>
<h2 id="heading-development-tips">Development Tips</h2>
<p>Here are a few recipes that could come in handy when working with HarperDB.</p>
<h3 id="heading-automatically-restart-the-functions-on-changes">Automatically restart the functions on changes</h3>
<p>When working with custom functions, whenever you make changes to the files, you need to restart the Custom Functions server every time for those changes to take effect.</p>
<p>So to speed up the process, I created a file watcher which listens to changes in any of the <code>routes</code> or <code>helpers</code> folders and auto-magically restarts the custom functions server. It's a very simple script that makes an API call to <a target="_blank" href="https://harperdb.io/docs/custom-functions/restarting-the-server/">restart API</a> when you save a file.</p>
<h4 id="heading-getting-auth-token">Getting Auth Token</h4>
<p>Step into HDB studio and go to the config page. Under the heading of <strong>Instance API Auth Header (this user)</strong> you can see the token. Copy the token using the icon on the left.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1644953295728/eO_On0W5e.png" alt="Auth token" /></p>
<p> Add a <code>.env</code> file in the root folder of <code>custom_functions</code> where you can mention the copied token which is needed to call the HarperDB API.</p>
<pre><code>HDB_AUTH=
</code></pre><h4 id="heading-create-a-file-watcher">Create a file watcher</h4>
<p>Create a file <code>file-watcher.js</code> under the <code>custom_functions/scripts</code> folder. We don't want this to be part of the project so it is kept outside the project folder.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> fetch = <span class="hljs-built_in">require</span>(<span class="hljs-string">'node-fetch'</span>);
<span class="hljs-keyword">const</span> chokidar = <span class="hljs-built_in">require</span>(<span class="hljs-string">'chokidar'</span>);
<span class="hljs-built_in">require</span>(<span class="hljs-string">'dotenv'</span>).config(); <span class="hljs-comment">// &lt;-- to read the env variables</span>
<span class="hljs-keyword">const</span> updateFunctions = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">try</span> {
    fetch(<span class="hljs-string">'http://localhost:9925'</span>, {
      <span class="hljs-attr">method</span>: <span class="hljs-string">'POST'</span>,
      <span class="hljs-attr">headers</span>: {
        <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>,
        <span class="hljs-attr">authorization</span>: <span class="hljs-string">`Basic <span class="hljs-subst">${process.env.HDB_AUTH}</span>`</span>,
      },
      <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify({ <span class="hljs-attr">operation</span>: <span class="hljs-string">'restart_service'</span>, <span class="hljs-attr">service</span>: <span class="hljs-string">'custom_functions'</span> }),
    });
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Custom functions server restarted'</span>);
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Failed to restart custom functions'</span>,error);
  }
};

<span class="hljs-comment">// Make sure the path is correct</span>
chokidar.watch(<span class="hljs-string">'./api/**/*.js'</span>).on(<span class="hljs-string">'change'</span>, <span class="hljs-function">() =&gt;</span> {
  updateFunctions();
});
</code></pre>
<p>You can then open a terminal and run the script:</p>
<pre><code class="lang-sh">node file-watcher.js
</code></pre>
<p><strong>Note</strong>: Make sure the path to your custom functions is correct.</p>
<h3 id="heading-how-to-use-env-variables-in-harperdb-custom-functions">How to use env variables in HarperDB Custom Functions</h3>
<p>I needed to save a private key for signing the JWT when the user logs in. For this purpose, the keys should be saved as environment variables.</p>
<p>We use a popular library called <a target="_blank" href="https://www.npmjs.com/package/dotenv">dotenv</a> to implement this. <code>dotenv</code> will automatically read the variables in our <code>.env</code> file and inject it to <code>process.env</code> The only gotcha is that the <code>.env</code> file for each project should be placed inside the project folder. In this case, the .env file path is:</p>
<pre><code>custom_functions<span class="hljs-operator">/</span>api<span class="hljs-operator">/</span>.env
</code></pre><p>And for using it, specify the path to the <code>.env</code> file:</p>
<pre><code class="lang-js"><span class="hljs-built_in">require</span>(<span class="hljs-string">'dotenv'</span>).config({
  <span class="hljs-attr">path</span>: <span class="hljs-string">`<span class="hljs-subst">${__dirname}</span>/../.env`</span>, <span class="hljs-comment">// &lt;-- relative url</span>
});

<span class="hljs-built_in">console</span>.log(process.env.JWT_SECRET) <span class="hljs-comment">// &lt;-- access it</span>
</code></pre>
<p>Here is the content of the <code>.env</code> file:</p>
<pre><code>JWT_SECRET=
</code></pre><p><code>JWT_SECRET</code> is used for signing and verifying the access tokens. You can see the usage in the <a target="_blank" href="https://github.com/adisreyaj/libshare-hdb-functions/blob/3f661b34a39959277b41c45668f13875877376eb/api/helpers/auth-helper.js#L13">auth-helper.js</a> file.</p>
<p><strong>Note</strong>: There are <code>.env.example</code> files kept in certain places in the repo which is where the actual <code>.env</code> files should be.</p>
<h2 id="heading-repo">Repo</h2>
<p>Here is the Github repo with all the routes, helpers written for Libshare.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/adisreyaj/libshare-hdb-functions">https://github.com/adisreyaj/libshare-hdb-functions</a></div>
<h3 id="heading-testing-the-custom-functions">Testing the custom functions</h3>
<p>All the API endpoints can be validated locally using apps like <strong>Postman</strong> or <strong>Insomnia</strong>. The URL will be <code>http://localhost:9926/api</code> with your route specified in the routing files. Eg: The signup route will be <code>http://localhost:9926/api</code>.</p>
<p>Here's an example cURL command:</p>
<pre><code class="lang-sh">`curl --request POST \
  --url http://localhost:9926/api/signup \
  --header <span class="hljs-string">'Content-Type: application/json'</span> \
  --data <span class="hljs-string">'{
    "firstName": "Adithya",
    "lastName": "Sreyaj",
    "email": "hi@adi.so",
    "password": "mysecretpassword"
}'</span>
</code></pre>
<h3 id="heading-final-files-and-folders">Final files and folders</h3>
<p>Here's what the whole project now looks like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1644954195520/2G7kHvLF3.png" alt="File tree" /></p>
<h3 id="heading-deploying-the-custom-functions">Deploying the custom functions</h3>
<p>There are two different ways to deploy the custom functions to a cloud instance. One involves us zipping the <code>custom_functions</code> folders and making an API call for <strong>packaging</strong> the functions and then another call to <strong>deploy</strong> the packaged functions. This is really cumbersome and I'm not a fan of it.</p>
<p>The other one is to deploy it using the HarperDB Studio, which deploys the local custom functions to the chosen cloud instance with a button click. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643440672144/XGgJ0eTaE.jpeg" alt="deploy.jpeg" /></p>
<ol>
<li>Go to the <code>functions</code> tab.</li>
<li>Select the project in the left sidebar.</li>
<li>Click on the <strong>deploy</strong> button on the top right of the editor.</li>
<li>Select the cloud instance to deploy to and press that green <strong>deploy</strong> button.</li>
</ol>
<p>Woohoo. We have successfully deployed a whole back-end. You can now visit the cloud instance functions URL to see the API.</p>
<h2 id="heading-setting-up-the-ui">Setting up the UI</h2>
<p>The Libshare UI is built using Angular and styles are handled using Tailwind CSS. Here's how you can set up the UI to run locally.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/adisreyaj/libshare">https://github.com/adisreyaj/libshare</a></div>
<ol>
<li><p>Clone or download the repo:</p>
<pre><code class="lang-sh">git <span class="hljs-built_in">clone</span> https://github.com/adisreyaj/libshare.git
</code></pre>
</li>
<li><p>Navigate to the folder</p>
<pre><code class="lang-sh"><span class="hljs-built_in">cd</span> libshare
</code></pre>
</li>
<li><p>Initialize the submodule</p>
<pre><code class="lang-sh">git submodule  update --init
</code></pre>
</li>
<li><p>Install the dependencies</p>
<pre><code class="lang-sh">npm install
</code></pre>
</li>
<li><p>Serve the UI</p>
<pre><code class="lang-sh">npm start
</code></pre>
</li>
</ol>
<p>Now you can visit <a target="_blank" href="http://localhost:4200">http://localhost:4200</a> in your browser to see the application running.</p>
<p><strong>Note</strong>: The UI components come from another repo called <a target="_blank" href="https://github.com/adisreyaj/zigzag">zigzag</a> which is added as a submodule in the repo. We need to initialize the submodule before installing dependencies. It's a set of common components that I use in my projects.</p>
<p>Make sure the HarperDB docker image is running as in the local environment the UI is going to be calling the API at <code>http://localhost:9926</code>.</p>
<h2 id="heading-pages-in-ui">Pages in UI</h2>
<p>So the UI is actually quite simple, there are like 4 pages in the application at the moment:</p>
<ol>
<li>Login</li>
<li>Signup</li>
<li>Libraries</li>
<li>Lists</li>
</ol>
<h3 id="heading-libraries-page">Libraries Page</h3>
<p>This is where you can add the NPM libraries that you have used or you found useful. You can then add a new library by just entering the name and searching for it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1644866078696/IRv51_mJg.png" alt="Add new Library" /></p>
<p>If the API is able to find the library, all the details are pre-filled by the application. If not the user can simply enter them manually.</p>
<h3 id="heading-lists-page">Lists Page</h3>
<p>Lists are a way to group the libraries that are added. So let's say you can think of them as folders. So if you worked on a project, you can create a list for that project and add all the libraries that are used. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1644866309764/Py1jb6yiM.png" alt="New List" /></p>
<p>The list can then be made public, which produces a public link that can be shared. If not the list is only visible to the owner.</p>
<p>Here's the link to Libshare's list page: https://libshare.adi.so/view/libshare-api-libraries-i95t9kib9a</p>
<h3 id="heading-public-lists">Public Lists</h3>
<p>Here's what a public list page looks like.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1644866441312/-rgunzqU7.png" alt="Libshare public page" /></p>
<p>You get a nice list of libraries used with some useful info about it. There is a title and description along with the last update date.</p>
<p>Have you worked on something interesting? Try <a target="_blank" href="https://libshare.adi.so">Libshare</a>! You can showcase all the cool packages that made your application great. </p>
<h2 id="heading-connect-with-me">Connect with me</h2>
<ul>
<li><a target="_blank" href="https://twitter.com/AdiSreyaj">Twitter</a></li>
<li><a target="_blank" href="https://github.com/adisreyaj">Github</a></li>
<li><a target="_blank" href="https://www.linkedin.com/in/adithyasreyaj/">Linkedin</a></li>
<li><a target="_blank" href="https://cardify.adi.so">Cardify</a> - Dynamic SVG Images for Github Readmes</li>
</ul>
<p>Do add your thoughts or if you have any questions, shoot'em below in the comments.
Stay Safe ❤️</p>
<p><a target="_blank" href="https://www.buymeacoffee.com/adisreyaj"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639498527478/IA3aJ9R0J.png" alt="Buy me a pizza" /></a></p>
]]></content:encoded></item><item><title><![CDATA[What the heck is HttpContext in Angular?]]></title><description><![CDATA[Have you heard about HttpContext in Angular? If not, there is such a thing. HttpContext is used to pass additional metadata to HTTP Interceptors in Angular. 
HttpContext in Angular
HttpContext is used to store additional metadata that can be accessed...]]></description><link>https://sreyaj.dev/what-is-httpcontext-in-angular</link><guid isPermaLink="true">https://sreyaj.dev/what-is-httpcontext-in-angular</guid><category><![CDATA[Angular]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[webdev]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Adithya Sreyaj]]></dc:creator><pubDate>Tue, 01 Feb 2022 14:29:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1643656327960/MugIvYLPi.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Have you heard about HttpContext in Angular? If not, there is such a thing. HttpContext is used to pass additional metadata to HTTP Interceptors in Angular. </p>
<h2 id="heading-httpcontext-in-angular">HttpContext in Angular</h2>
<p>HttpContext is used to store additional metadata that can be accessed from HTTP Interceptors. Before this, there was no proper way to configure interceptors on a per request basis. This feature was introduced by <a target="_blank" href="https://github.com/angular/angular/pull/25751">Angular v12</a>.</p>
<p>If you had use cases where you need to treat a particular request differently or override some logic in an HTTP Interceptor, this is what you need to use. </p>
<p>I came to know about it only recently and actually started using it in one of my recent projects - <a target="_blank" href="https://github.com/adisreyaj/libshare/blob/2e2a41ab46e942fbe5b47e7c60fdf7dea870e3a8/src/app/core/interceptors/auth.interceptor.ts#L12">Libshare</a>. </p>
<h2 id="heading-how-to-use-httpcontext-in-angular">How to use HttpContext in Angular?</h2>
<p>Let's take a practical use case for understanding how to use HttpContext.</p>
<p> I was working on a small application that can be used to curate and share the libraries. Most of the APIs are authenticated, meaning we need to add <code>Authorization</code> header with all the API requests.</p>
<p>For pages like Login and Signup, we don't need to pass the token in the headers. Let's see how we can skip certain APIs and only add Bearer tokens to the other API requests.</p>
<p>The way we use it is pretty straightforward. There are two parts to it:</p>
<h3 id="heading-1-create-a-new-httpcontexttoken">1. Create a new <code>HttpContextToken</code></h3>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> IS_PUBLIC_API = <span class="hljs-keyword">new</span> HttpContextToken&lt;<span class="hljs-built_in">boolean</span>&gt;(<span class="hljs-function">() =&gt;</span> <span class="hljs-literal">false</span>);
</code></pre>
<h3 id="heading-2-passing-the-context-while-making-http-calls">2. Passing the context while making <code>http</code> calls.</h3>
<p>When using the <code>HttpClient</code> to make requests, you can pass the <code>context</code> along with other options.
We create an instance of <code>HttpContext</code> class and use the <code>set</code> method to pass the value to the token we created above.</p>
<pre><code class="lang-ts">getSomeData(slug: <span class="hljs-built_in">string</span>) {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.http
      .get(&lt;URL&gt;, {
        context: <span class="hljs-keyword">new</span> HttpContext().set(IS_PUBLIC_API, <span class="hljs-literal">true</span>),
      })
  }
</code></pre>
<h3 id="heading-3-retrieve-the-data-inside-an-interceptor">3. Retrieve the data inside an Interceptor.</h3>
<p>We can now retrieve the context data from the interceptor by accessing the <code>request.context</code>:</p>
<pre><code class="lang-ts"><span class="hljs-meta">@Injectable</span>()
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AuthInterceptor <span class="hljs-keyword">implements</span> HttpInterceptor {
  intercept(req: HttpRequest&lt;<span class="hljs-built_in">any</span>&gt;, next: HttpHandler): Observable&lt;HttpEvent&lt;<span class="hljs-built_in">any</span>&gt;&gt; {
    <span class="hljs-keyword">if</span> (req.context.get(IS_PUBLIC_API)) {
      <span class="hljs-keyword">return</span> next.handle(req);
    }
   <span class="hljs-comment">// Add token to other requests</span>
  }
}
</code></pre>
<p>You can check a practical usage here: <a target="_blank" href="https://github.com/adisreyaj/libshare/blob/main/src/app/core/interceptors/auth.interceptor.ts">Libshare Repo</a></p>
<h2 id="heading-addtional-info">Addtional Info</h2>
<p>HttpContext is backed by a Map and so has methods like:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">class</span> HttpContext {
  set&lt;T&gt;(token: HttpContextToken&lt;T&gt;, value: T): HttpContext
  get&lt;T&gt;(token: HttpContextToken&lt;T&gt;): T
  <span class="hljs-keyword">delete</span>(token: HttpContextToken&lt;unknown&gt;): HttpContext
  has(token: HttpContextToken&lt;unknown&gt;): <span class="hljs-built_in">boolean</span>
  keys(): IterableIterator&lt;HttpContextToken&lt;unknown&gt;&gt;
}
</code></pre>
<p>The context is also <strong>type-safe</strong>, which is a good thing.
Also, keep in mind that the context is mutable and is shared between cloned requests unless explicitly specified.</p>
<p>There are a lot of ways this could prove useful like if you want to <strong>cache</strong> only particular requests, or maybe add some <strong>additional headers</strong> conditionally. </p>
<p>Documentation: https://angular.io/api/common/http/HttpContext</p>
<h2 id="heading-connect-with-me">Connect with me</h2>
<ul>
<li><a target="_blank" href="https://twitter.com/AdiSreyaj">Twitter</a></li>
<li><a target="_blank" href="https://github.com/adisreyaj">Github</a></li>
<li><a target="_blank" href="https://www.linkedin.com/in/adithyasreyaj/">Linkedin</a></li>
<li><a target="_blank" href="https://cardify.adi.so">Cardify</a> - Dynamic SVG Images for Github Readmes</li>
</ul>
<p>Do add your thoughts in the comments section.
Stay Safe ❤️</p>
<p><a target="_blank" href="https://www.buymeacoffee.com/adisreyaj"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639498527478/IA3aJ9R0J.png" alt="Buy me a pizza" /></a></p>
]]></content:encoded></item><item><title><![CDATA[Get Signup Notifications In Telegram Using Auth0 Actions.]]></title><description><![CDATA[Auth0 actions are so powerful that they can be used to do a lot of cool things. Here's how you can send notifications to Telegram whenever a new user signs up. 
I recently worked on a small project which is a small e-commerce application built using ...]]></description><link>https://sreyaj.dev/signup-notifications-telegram-using-auth0-actions</link><guid isPermaLink="true">https://sreyaj.dev/signup-notifications-telegram-using-auth0-actions</guid><category><![CDATA[authentication]]></category><category><![CDATA[Auth0]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Adithya Sreyaj]]></dc:creator><pubDate>Mon, 17 Jan 2022 15:11:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1642333900296/NuR988Yla.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Auth0 actions are so powerful that they can be used to do a lot of cool things. Here's how you can send notifications to Telegram whenever a new user signs up. </p>
<p>I recently worked on a small project which is a small e-commerce application built using Angular and NestJs. Auth0 is used for authenticating the users. I had a very interesting thought of adding notifications when a new user signs up. The easiest way for me was to use Auth0 Actions.</p>
<h2 id="heading-auth0-actions">Auth0 Actions</h2>
<p>Actions are one of the coolest features of Auth0. I love it and have used it for multiple scenarios. Actions are custom Node.js functions that get executed during certain points like User Login, Registration, etc.</p>
<p>Here's a definition from the docs:</p>
<blockquote>
<p>Actions are functional services that fire during specific events across multiple identity flows.</p>
</blockquote>
<p><strong>Auth0 hooks</strong> allowed us to add custom logic that gets triggered when certain events happen. Actions are a more advanced version of hooks that provides more extensibility.</p>
<p>Official docs: https://auth0.com/docs/customize/actions</p>
<h3 id="heading-action-triggers">Action Triggers</h3>
<p>The custom functions that we write are called by certain events. Here are the supported triggers:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642323501366/zq800xdZ8.png" alt="Action triggers" /></p>
<p>Here are more details of when exactly these actions are triggered:
https://auth0.com/docs/customize/actions/triggers</p>
<h2 id="heading-implementing-the-action">Implementing the Action</h2>
<p>For our use case, we need the notification to be sent when a new user signs up. So we could use the <strong>Post User Registration</strong> trigger for our action.</p>
<h3 id="heading-1-create-a-custom-action">1. Create a custom action</h3>
<p>The first step is to create a new custom Action. We do that by navigating to <code>Actions &gt; Custom</code> and then clicking on the <strong>Build Custom</strong> button.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642324835222/9LKAt5nwl.png" alt="Actions" /></p>
<p>We get a modal asking to give the Action a name and also select a Trigger and the Environment.</p>
<p>You can fill the form up with the following details:</p>
<ul>
<li><em>Name</em>: <strong>New User Notifications</strong></li>
<li><em>Trigger</em>: <strong>Post User Registration</strong></li>
<li><em>Runtime</em>: <strong>Node 16 (Recommended)</strong></li>
</ul>
<h3 id="heading-2-setting-up-pre-requisites">2. Setting up pre-requisites</h3>
<p>Once the action is created, you can see the list of the Actions under <strong>Custom</strong> tab on the Actions Library page.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642324644640/8-U_hk8WF.png" alt="Custom Actions Page" /></p>
<p>There are a few things that we need to do before we can start writing the actual logic.</p>
<h4 id="heading-creating-a-telegram-bot">Creating a Telegram Bot</h4>
<p>Telegram is a really powerful messaging platform that can do a lot of stuff. One of the best things about it is that we can create bots and also send messages using the  <a target="_blank" href="https://core.telegram.org/bots/api">Telegram Bot API</a> . </p>
<p>Put a message to  <a target="_blank" href="https://t.me/botfather">BotFather</a> on Telegram</p>
<pre><code><span class="hljs-operator">/</span>newbot
</code></pre><p>It will prompt to you give a name. Give a name and then you'll be given the <strong>Bot Token</strong>.
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642327890346/DXu0QEak1.png" alt="telegram bot.png" /></p>
<p>Now that we have the bot token, we can make API calls using the Telegram Bot API.</p>
<p>Ref: https://core.telegram.org/bots#3-how-do-i-create-a-bot</p>
<p>Before we can send a message to the Bot, we need to get the <strong>Channel Id</strong>. For that send a message to the bot and then just paste the following URL in the browser (replace the  with yours):</p>
<pre><code><span class="hljs-attribute">https</span>:<span class="hljs-comment">//api.telegram.org/bot&lt;bot-token&gt;/getUpdates</span>
</code></pre><p>You will be able to see the message details that was sent to the bot:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"ok"</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">"result"</span>: [
    {
      <span class="hljs-attr">"update_id"</span>: <span class="hljs-number">723563447</span>,
      <span class="hljs-attr">"message"</span>: {
        <span class="hljs-attr">"message_id"</span>: <span class="hljs-number">7</span>,
        <span class="hljs-attr">"from"</span>: {
          <span class="hljs-attr">"id"</span>: <span class="hljs-number">627445600</span>, <span class="hljs-comment">// &lt;-- Copy this Id</span>
          <span class="hljs-attr">"is_bot"</span>: <span class="hljs-literal">false</span>,
          <span class="hljs-attr">"first_name"</span>: <span class="hljs-string">"John Doe"</span>,
          <span class="hljs-attr">"username"</span>: <span class="hljs-string">"johndoe"</span>,
          <span class="hljs-attr">"language_code"</span>: <span class="hljs-string">"en"</span>
        },
        <span class="hljs-attr">"chat"</span>: {
          <span class="hljs-attr">"id"</span>: <span class="hljs-number">627445600</span>,
          <span class="hljs-attr">"first_name"</span>: <span class="hljs-string">"Jane Doe"</span>,
          <span class="hljs-attr">"username"</span>: <span class="hljs-string">"janedoe"</span>,
          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"private"</span>
        },
        <span class="hljs-attr">"date"</span>: <span class="hljs-number">1642266764</span>,
        <span class="hljs-attr">"text"</span>: <span class="hljs-string">"Test"</span>
      }
    }
  ]
}
</code></pre>
<p>The <code>id</code> is the <code>channel_id</code> that we are going to use for sending messages.</p>
<h3 id="heading-3-writing-the-action-logic">3. Writing the Action Logic</h3>
<p>Now that we have the things needed, let's start writing the logic. So here are the things that need to be set up in the actions.</p>
<h4 id="heading-installing-dependencies">Installing Dependencies</h4>
<p>Actions allow us to install packages that we can use inside the function, in our case we need to make API requests to the Telegram Bot API to send messages. For that, we can install a library called <code>node-fetch</code>. </p>
<p>To do so, go to your newly created action and click on the <strong>Modules</strong> section.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642328919220/kcxIDQv9K.png" alt="Actions Modules" /></p>
<p><strong>Note</strong>: We install <code>node-fetch@2</code> explicitly because we want the <code>CommonJs</code> version of the library.</p>
<h4 id="heading-adding-env-variables">Adding Env Variables</h4>
<p>Actions also have a way to keep our environment variables a secret. This is where we are going to save the <strong>Bot Token</strong> and the <strong>Channel id</strong> that we will use in the code. It's not a good idea to put them in the code as they are sensitive information.</p>
<p>There is a <strong>Secrets</strong> section under which we can save them. Create a secret for the token and the channel id.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642329296076/ZqDEWKjrK.png" alt="Actions Secrets" /></p>
<h4 id="heading-writing-the-actual-logic">Writing the actual logic</h4>
<p>Now you can use <code>node-fetch</code> to make a post request to the <code>/sendMessage</code> API endpoint.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> request = <span class="hljs-built_in">require</span>(<span class="hljs-string">'node-fetch'</span>); <span class="hljs-comment">// &lt;-- require the library</span>

<span class="hljs-comment">/**
* Handler that will be called during the execution of a PostUserRegistration flow.
*
* @param {Event} event - Details about the context and user that has registered.
*/</span>
<span class="hljs-built_in">exports</span>.onExecutePostUserRegistration = <span class="hljs-keyword">async</span> (event) =&gt; {
  <span class="hljs-keyword">try</span>{
    <span class="hljs-keyword">const</span> {family_name, given_name} = event.user;
    <span class="hljs-keyword">await</span> request(<span class="hljs-string">`https://api.telegram.org/bot<span class="hljs-subst">${event.secrets.BOT_TOKEN}</span>/sendMessage`</span>, 
      {
        <span class="hljs-attr">method</span>:<span class="hljs-string">'POST'</span>,
        <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify({
          <span class="hljs-string">"chat_id"</span>: event.secrets.TELEGRAM_CHAT_ID,
          <span class="hljs-string">"text"</span>:<span class="hljs-string">`New User Signed up: <span class="hljs-subst">${given_name}</span> <span class="hljs-subst">${family_name}</span>`</span>
        }),
        <span class="hljs-attr">headers</span>: {
          <span class="hljs-string">'content-type'</span>: <span class="hljs-string">'application/json'</span>
        }
      }
    );
  } <span class="hljs-keyword">catch</span>(err){
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Failed to notify'</span>);
  }
};
</code></pre>
<p>Now the action can be deployed.</p>
<p>Ref: https://core.telegram.org/bots/api#sendmessage</p>
<h3 id="heading-4-using-the-action">4. Using the Action</h3>
<p>Once the action is deployed, we can use it in a flow. To do so, navigate to the <code>Actions &gt; Flows</code> Page and select <strong>Post User Registration</strong> flow from the cards.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642330322367/5kzrptgw-.png" alt="Auth0 flow" /></p>
<p>We can find the action that we built under that <strong>Custom</strong> tab. Dragging and dropping the action to the flow does the job of activating it. The only thing left now is to just <strong>Apply</strong> the flow.</p>
<p>We are done setting up.</p>
<p>So now whenever someone signs up, you get a message in Telegram.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642330497846/LSvxZsnUp.png" alt="Notification" /></p>
<p>There are tons of cool use-cases for Actions. If you would like to see more blogs on it, do let me know.</p>
<h2 id="heading-connect-with-me">Connect with me</h2>
<ul>
<li><a target="_blank" href="https://twitter.com/AdiSreyaj">Twitter</a></li>
<li><a target="_blank" href="https://github.com/adisreyaj">Github</a></li>
<li><a target="_blank" href="https://www.linkedin.com/in/adithyasreyaj/">Linkedin</a></li>
<li><a target="_blank" href="https://cardify.adi.so">Cardify</a> - Dynamic SVG Images for Github Readmes</li>
</ul>
<p>Do add your thoughts in the comments section.
Stay Safe ❤️</p>
<p><a target="_blank" href="https://www.buymeacoffee.com/adisreyaj"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639498527478/IA3aJ9R0J.png" alt="Buy me a pizza" /></a></p>
]]></content:encoded></item><item><title><![CDATA[All About Validators in Angular + Creating Custom Sync & Async Validators]]></title><description><![CDATA[Validators in angular are just simple functions that check our form values and return an error if some things are not the way its meant to be. Angular ships with a bunch of Validators out of the box. These can be used directly without the need for an...]]></description><link>https://sreyaj.dev/creating-custom-sync-and-async-validators-in-angular</link><guid isPermaLink="true">https://sreyaj.dev/creating-custom-sync-and-async-validators-in-angular</guid><category><![CDATA[Angular]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[webdev]]></category><category><![CDATA[forms]]></category><dc:creator><![CDATA[Adithya Sreyaj]]></dc:creator><pubDate>Thu, 16 Dec 2021 10:59:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1639321426078/3LZv_tYL9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Validators in angular are just simple functions that check our form values and return an error if some things are not the way its meant to be. Angular ships with a bunch of Validators out of the box. These can be used directly without the need for any configuration.</p>
<h2 id="heading-using-validators-in-angular-forms">Using validators in angular forms</h2>
<p>Validators can be set up in different ways to validate user inputs in form elements. Angular provides a lot of validators that are commonly needed for any form.</p>
<p>If we special validation requirements or if we are dealing with custom components, the default validators might not just cut it. We can always create custom validators for these cases.</p>
<h2 id="heading-in-built-validators-of-angular">In-built validators of angular</h2>
<p>Angular ships with different validators out of the box, both for use with Template forms as well as Reactive forms. </p>
<ul>
<li>In-built Validator Directives.</li>
<li>A set of validator functions exported via the <code>Validators</code> class.</li>
</ul>
<p>Both the directives and <code>Validators</code> class use the same function under the hood. We can provide multiple validators for an element. What Angular does is stack up the validators in an array and call them one by one.</p>
<h3 id="heading-validator-directives">Validator Directives</h3>
<p>Native HTML form has inbuilt validation attributes like <code>required</code>, <code>min</code>, <code>max</code>, etc. Angular has created directives to match each of these  <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Constraint_validation#validation-related_attributes">native validation attributes</a>. So when we place these attributes on an <code>input</code>, Angular can get access to the element and call a validation function whenever the value changes.</p>
<p>Here's how you would use a validator directive:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"email"</span> <span class="hljs-attr">required</span> <span class="hljs-attr">minlength</span> [(<span class="hljs-attr">ngModel</span>)]=<span class="hljs-string">"email"</span>/&gt;</span>
</code></pre>
<p>The attributes <code>required</code> and <code>minlength</code> are selectors for the <code>RequiredValidator</code>( <a target="_blank" href="https://github.com/angular/angular/blob/0115e2b66493b06532f5399f3cad79e6149aed7f/packages/forms/src/directives/validators.ts#L347">ref</a> ) and <code>MinLengthValidator</code> ( <a target="_blank" href="https://github.com/angular/angular/blob/0115e2b66493b06532f5399f3cad79e6149aed7f/packages/forms/src/directives/validators.ts#L550">ref</a> ) directives respectively. These can be used with both <strong>Template drive forms</strong> and <strong>Reactive Forms</strong>.</p>
<p>Here's how the <code>required</code>directive looks like:</p>
<pre><code class="lang-ts"><span class="hljs-meta">@Directive</span>({
  ...
  providers: [{
  provide: NG_VALIDATORS,
  useExisting: forwardRef(<span class="hljs-function">() =&gt;</span> RequiredValidator),
  multi: <span class="hljs-literal">true</span>
}],
  ...
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> RequiredValidator <span class="hljs-keyword">implements</span> Validator {
   <span class="hljs-comment">// .....</span>

   validate(control: AbstractControl): ValidationErrors|<span class="hljs-literal">null</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.required ? requiredValidator(control) : <span class="hljs-literal">null</span>;
  }

   <span class="hljs-comment">// .....</span>
}
</code></pre>
<p>Let's break down the code:</p>
<ol>
<li>The class <code>RequiredValidator</code> implements an interface called <code>Validator</code>.</li>
<li>The <code>Validator</code> interface forces the class to implement a <code>validate()</code> method.</li>
<li>The method will be called for validation of the value.</li>
<li>The validation logic can be performed in the method and just have to return an object if there is an error or <code>null</code> if there is no error.</li>
<li>Now, we need to let Angular know about this custom validation that we've set up.</li>
<li>We use the <code>NG_VALIDATORS</code> Injection token for this.</li>
<li>We ask Angular to use our same <code>RequiredValidator</code> class by using the <code>useExisitng</code> property.</li>
</ol>
<p>So when the user places <code>required</code> on a form control, the <code>RequiredValidator</code> directive gets instantiated and the validator also gets attached to the element. </p>
<p>Take a look into the  <a target="_blank" href="https://github.com/angular/angular/blob/13.0.3/packages/forms/src/directives/validators.ts#L325-L386">source code</a>  for <code>Required Validator</code>( <a target="_blank" href="https://angular.io/api/forms/RequiredValidator">ref</a> ).</p>
<h3 id="heading-validators-class">Validators class</h3>
<p><code>Validators</code> class exposes a set of static methods that can be used when dealing with <strong>Reactive Forms</strong> like so:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { FormControl, Validators } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/forms'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppComponent{
  email = <span class="hljs-keyword">new</span> FormControl(<span class="hljs-string">''</span>, [Validators.required, Validators.minLength(<span class="hljs-number">5</span>)]);
}
</code></pre>
<p>Here's the list of all the function inside the class:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">class</span> Validators {
  <span class="hljs-keyword">static</span> min(min: <span class="hljs-built_in">number</span>): ValidatorFn
  <span class="hljs-keyword">static</span> max(max: <span class="hljs-built_in">number</span>): ValidatorFn
  <span class="hljs-keyword">static</span> required(control: AbstractControl): ValidationErrors | <span class="hljs-literal">null</span>
  <span class="hljs-keyword">static</span> requiredTrue(control: AbstractControl): ValidationErrors | <span class="hljs-literal">null</span>
  <span class="hljs-keyword">static</span> email(control: AbstractControl): ValidationErrors | <span class="hljs-literal">null</span>
  <span class="hljs-keyword">static</span> minLength(minLength: <span class="hljs-built_in">number</span>): ValidatorFn
  <span class="hljs-keyword">static</span> maxLength(maxLength: <span class="hljs-built_in">number</span>): ValidatorFn
  <span class="hljs-keyword">static</span> pattern(pattern: <span class="hljs-built_in">string</span> | <span class="hljs-built_in">RegExp</span>): ValidatorFn
  <span class="hljs-keyword">static</span> nullValidator(control: AbstractControl): ValidationErrors | <span class="hljs-literal">null</span>
  <span class="hljs-keyword">static</span> compose(validators: ValidatorFn[]): ValidatorFn | <span class="hljs-literal">null</span>
  <span class="hljs-keyword">static</span> composeAsync(validators: AsyncValidatorFn[]): AsyncValidatorFn | <span class="hljs-literal">null</span>
}
</code></pre>
<hr />
<h2 id="heading-custom-sync-validators">Custom Sync Validators</h2>
<p>We can also create custom validators in Angular which are tailored to our particular use case. You can't just always rely on the built-in capabilities of Angular.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639250726448/kRjNuzoni.png" alt="Custom Sync validators in Angular" /></p>
<p>Validators are just functions of the below type:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> ValidatorFn {
  (control: AbstractControl): ValidationErrors|<span class="hljs-literal">null</span>;
}
</code></pre>
<p>Let's create a custom validator function that checks if a domain is secure (<code>https</code>) or not.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> ProtocolValidator: ValidatorFn = <span class="hljs-function">(<span class="hljs-params">control</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> { value } = control;
  <span class="hljs-keyword">const</span> isSecure = (value <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>).startsWith(<span class="hljs-string">"https://"</span>);
  <span class="hljs-keyword">return</span> isSecure ? <span class="hljs-literal">null</span> : { protocol: <span class="hljs-string">`Should be https URI`</span> };
};
</code></pre>
<h4 id="heading-custom-validator-with-parameters">Custom validator with parameters</h4>
<p>If we want our custom validator to be more configurable and re-use in multiple places, we can pass parameters to our validator function to create a validator based on the provided parameters.</p>
<p>For example, if the Secure validator needs to validate Websocket URIs also, we can modify the <code>ProtocolValidator</code> to accommodate this change:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> ProtocolValidator = (protocol: <span class="hljs-built_in">string</span>): <span class="hljs-function"><span class="hljs-params">ValidatorFn</span> =&gt;</span> (control) =&gt; {
  <span class="hljs-keyword">const</span> { value } = control;
  <span class="hljs-keyword">const</span> isSecure = (value <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>).startsWith(protocol);
  <span class="hljs-keyword">return</span> isSecure ? <span class="hljs-literal">null</span> : { protocol: <span class="hljs-string">`Should be <span class="hljs-subst">${protocol}</span> URI`</span> };
};
</code></pre>
<h3 id="heading-use-custom-validators-in-reactive-forms">Use custom validators in Reactive Forms</h3>
<p>We can directly use the function in the reactive forms like so:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> urlControl = <span class="hljs-keyword">new</span> FormControl(<span class="hljs-string">''</span>, [Validators.required, ProtocolValidator(<span class="hljs-string">'https://'</span>)]);
</code></pre>
<p>and the template will be something like this:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> [<span class="hljs-attr">formControl</span>]=<span class="hljs-string">"urlControl"</span> /&gt;</span>
</code></pre>
<h3 id="heading-use-custom-validator-in-template-driven-forms">Use custom validator in Template-driven Forms</h3>
<p>If we want to use these validators with Template-drive forms, we need to create a directive.</p>
<pre><code class="lang-ts"><span class="hljs-meta">@Directive</span>({
  selector: <span class="hljs-string">"[protocol]"</span>,
  providers: [{
      provide: NG_VALIDATORS,
      useExisting: forwardRef(<span class="hljs-function">() =&gt;</span> ProtocolValidatorDirective),
      multi: <span class="hljs-literal">true</span>
    }]
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> ProtocolValidatorDirective <span class="hljs-keyword">implements</span> Validator {
  <span class="hljs-meta">@Input</span>() protocol!: <span class="hljs-built_in">string</span>;

  validate(control: AbstractControl): ValidationErrors|<span class="hljs-literal">null</span> {
    <span class="hljs-keyword">return</span> ProtocolValidator(<span class="hljs-built_in">this</span>.protocol)(control);
  }
}
</code></pre>
<p>and we use it like this:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">protocol</span>=<span class="hljs-string">"wss://"</span> [(<span class="hljs-attr">ngModel</span>)]=<span class="hljs-string">"url"</span> /&gt;</span>
</code></pre>
<p><strong>Note</strong>: For the directive selector, it's always a good idea to also look for whether there is a valid form connector added to the element:</p>
<pre><code class="lang-ts"><span class="hljs-meta">@Directive</span>({
   selector: <span class="hljs-string">'[protocol][formControlName],[protocol][formControl],[protocol][ngModel]'</span>
})
</code></pre>
<p>What this translates to is that the element where our <code>protocol</code> directive is placed should also have either of these attributes:</p>
<ol>
<li>formControlName</li>
<li>formControl</li>
<li>ngModel</li>
</ol>
<p>This makes sure that the directive is not activated on non-form elements and you won't get any unwanted errors.</p>
<hr />
<h2 id="heading-custom-async-validators">Custom async validators</h2>
<p>The process of creating async validators in angular is exactly the same, except this time we are doing our validation in an async way (by calling an API for example).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639250696922/5rNdCrCHC.png" alt="Async validators in Angular" /></p>
<p>Here is the type of async validator function:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">interface</span> AsyncValidatorFn {
  (control: AbstractControl): <span class="hljs-built_in">Promise</span>&lt;ValidationErrors | <span class="hljs-literal">null</span>&gt; | Observable&lt;ValidationErrors | <span class="hljs-literal">null</span>&gt;
}
</code></pre>
<p>The only thing that is different here is that the method now returns either an <strong>Observable</strong> or a <strong>Promise</strong>.</p>
<p>Let's create an async validator by modifying the above validator that we wrote. Ideally, we will be using async validation for meaningful validations like:</p>
<ol>
<li>Check username availability</li>
<li>Whether a user is blocked</li>
<li>If the user's phone number is part of a blocklist.</li>
</ol>
<p>Let's create an async validator to check if a username is available. </p>
<p>We are gonna be creating 3 things:</p>
<ol>
<li><strong>Username Service</strong> - which makes the API call to see if the username is available</li>
<li><strong>Validator Service</strong> - which contains the validation logic</li>
<li><strong>Validator Directive</strong> - for using template-driven forms </li>
</ol>
<h3 id="heading-username-service">Username Service</h3>
<p>We'll mock the logic for this:</p>
<pre><code class="lang-ts"><span class="hljs-meta">@Injectable</span>({
  providedIn: <span class="hljs-string">"root"</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> UsernameService {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> http: HttpClient</span>) {}

  isUsernameAvailable(username: <span class="hljs-built_in">string</span>) {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.http.get(<span class="hljs-string">"https://jsonplaceholder.typicode.com/users"</span>).pipe(
      map(<span class="hljs-function">(<span class="hljs-params">users: <span class="hljs-built_in">any</span>[]</span>) =&gt;</span> users.map(<span class="hljs-function">(<span class="hljs-params">user</span>) =&gt;</span> user?.username?.toLowerCase())),
      map(
        <span class="hljs-function">(<span class="hljs-params">existingUsernames: <span class="hljs-built_in">string</span>[]</span>) =&gt;</span> !existingUsernames.includes(username)
      ),
      startWith(<span class="hljs-literal">true</span>),
      delay(<span class="hljs-number">1000</span>)
    );
  }
}
</code></pre>
<h3 id="heading-async-validator-service">Async Validator Service</h3>
<p>This is the main part of our validation process. </p>
<pre><code class="lang-ts"><span class="hljs-meta">@Injectable</span>({
  providedIn: <span class="hljs-string">"root"</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> UsernameValidatorService <span class="hljs-keyword">implements</span> Validator {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> usernameService: UsernameService</span>) {}

  validatorFunction: AsyncValidatorFn = <span class="hljs-function">(<span class="hljs-params">control</span>) =&gt;</span>
    control?.value !== <span class="hljs-string">""</span>
      ? <span class="hljs-built_in">this</span>.usernameService
          .isUsernameAvailable(control.value)
          .pipe(
            map(<span class="hljs-function">(<span class="hljs-params">isUsernameAvailable</span>) =&gt;</span>
              isUsernameAvailable
                ? <span class="hljs-literal">null</span>
                : { username: <span class="hljs-string">"Username not available"</span> }
            )
          )
      : <span class="hljs-keyword">of</span>(<span class="hljs-literal">null</span>);

  validate(control: AbstractControl) {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.validatorFunction(control);
  }
}
</code></pre>
<p>Why did we create a separate <code>validatorFunction()</code>? Why can't the logic be placed inside the <code>validate()</code> method itself?</p>
<p>This is done so that, we can use the <code>validatorFunction()</code> when we are using Reactive Forms:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppComponent {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> usernameValidator: UsernameValidatorService</span>) {}
  username = <span class="hljs-keyword">new</span> FormControl(<span class="hljs-string">""</span>, {
    asyncValidators: <span class="hljs-built_in">this</span>.usernameValidator.validatorFunction
  });
}
</code></pre>
<p>Now to use the validator with <strong>Template-driven</strong> forms, we need to create a <strong>Directive</strong> to bind the validator to the element.</p>
<h3 id="heading-async-validator-directive">Async Validator Directive</h3>
<pre><code class="lang-ts"><span class="hljs-meta">@Directive</span>({
  selector: <span class="hljs-string">"[username][ngModel]"</span>,
  providers: [
    {
      provide: NG_ASYNC_VALIDATORS,
      useExisting: UsernameValidatorService,
      multi: <span class="hljs-literal">true</span>
    }
  ]
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> UsernameValidatorDirective {}
</code></pre>
<p>We provide <code>NG_ASYNC_VALIDATORS</code> instead of <code>NG_VALIDATORS</code> in this case. And since we already have the <code>UsernameValidatorService</code> (which implements the <code>Validator</code> interface).</p>
<p><strong>Note:</strong> <code>UsernameValidatorService</code> is <code>providedIn: 'root'</code>, which means the <strong>Injector</strong> has the service instance with it. So we just say use that same instance of <code>UsernameValidatorService</code> by using the <code>useExisitng</code> property.</p>
<p>Angular takes care of subscriptions of these validators so we don't have to worry about cleaning the subscriptions later.</p>
<h2 id="heading-code-and-demo">Code and Demo</h2>
<iframe src="https://codesandbox.io/embed/angular-async-validator-5idsm?fontsize=14&amp;hidenavigation=1&amp;theme=dark&amp;view=preview" style="width:100%;height:600px;border:0;border-radius:4px;overflow:hidden" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>

<p>Code: https://codesandbox.io/s/angular-async-validator-5idsm</p>
<p>Make sure to not just use the code as-is. For the scope of this post, things are kept simple and straightforward. Take some time to see if you can improve something in the code before you use it.</p>
<h2 id="heading-connect-with-me">Connect with me</h2>
<ul>
<li><a target="_blank" href="https://twitter.com/AdiSreyaj">Twitter</a></li>
<li><a target="_blank" href="https://github.com/adisreyaj">Github</a></li>
<li><a target="_blank" href="https://www.linkedin.com/in/adithyasreyaj/">Linkedin</a></li>
<li><a target="_blank" href="https://cardify.adi.so">Cardify</a> - Dynamic SVG Images for Github Readmes</li>
</ul>
<p>Do add your thoughts in the comments section.
Stay Safe ❤️</p>
<p><a target="_blank" href="https://www.buymeacoffee.com/adisreyaj"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639498527478/IA3aJ9R0J.png" alt="Buy me a pizza" /></a></p>
]]></content:encoded></item><item><title><![CDATA[How I implemented sticky columns in tables using directives - Angular!]]></title><description><![CDATA[How to create sticky columns in Angular using directives. Implementing tables with sticky columns can be tricky especially when you have to make multiple columns sticky. Using directives, we can easily implement sticky columns.
I can't emphasize more...]]></description><link>https://sreyaj.dev/implement-sticky-columns-using-directives-angular</link><guid isPermaLink="true">https://sreyaj.dev/implement-sticky-columns-using-directives-angular</guid><category><![CDATA[Angular]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Adithya Sreyaj]]></dc:creator><pubDate>Sun, 12 Dec 2021 16:56:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1639328151099/vugsl8iig.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>How to create sticky columns in Angular using directives. Implementing tables with sticky columns can be tricky especially when you have to make multiple columns sticky. Using directives, we can easily implement sticky columns.</p>
<p>I can't emphasize more the power of directives in Angular. I've written a couple of articles showcasing how one can actually use it to implement really cool stuff. You can check some use-cases for directives here:  <a target="_blank" href="https://ng-directives.vercel.app/">Angular Directive Showcase</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1637340755156/2X26NikwO.gif" alt="Sticky columns" /></p>
<h2 id="heading-tables-with-sticky-columns">Tables with sticky columns</h2>
<p>We make use of the <code>position: sticky</code> CSS property to make an element sticky. Read more about sticky positioning at  <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/position#sticky_positioning">MDN</a>.</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.sticky</span> {
  <span class="hljs-attribute">position</span>: sticky;
  <span class="hljs-attribute">left</span>: <span class="hljs-number">0</span>;
}
</code></pre>
<p>For the sticky position to work properly, at least one of <code>top</code>, <code>right</code>, <code>bottom</code>, or <code>left</code> should be specified.</p>
<h2 id="heading-the-problem">The problem</h2>
<p>Making the first column in a table sticky is super simple, you basically add the <code>sticky</code> class to the column.</p>
<p>When two columns need to stick to the left, we can't just add the <code>sticky</code> class to both of the columns. This is how it looks if you did so:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1637334396868/4_MzPb15p.gif" alt="Sticky columns" /></p>
<p>Here you can see the <strong>Manager</strong> column overlapping with the <strong>Company</strong> column. This is because we gave both the columns <code>left:0</code>.</p>
<p>To make it work as expected, the styles should be like this:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.company</span> {
  <span class="hljs-attribute">position</span>: sticky;
  <span class="hljs-attribute">left</span>: <span class="hljs-number">0px</span>;
}

<span class="hljs-selector-class">.manager</span> {
  <span class="hljs-attribute">position</span>: sticky;
  <span class="hljs-attribute">left</span>: <span class="hljs-number">120px</span>; // &lt;-- width of the company column
}
</code></pre>
<p>What we did here is we added the offset of the <strong>Manager</strong> column as the <code>left</code> property value.</p>
<h2 id="heading-sticky-calculations">Sticky Calculations</h2>
<p>For calculating the <code>left</code> value, we need to find the <code>x</code> value of the column. If we look at the first column <strong>Company</strong> and get its offset from the left side.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1637337274353/UiOlAV_fBf.png" alt="Column bounding rect" /></p>
<p>We expect the <code>x</code> value to be <code>0</code> but we get <code>85</code> here. This is because the <code>x</code> value is calculated from the left side of the window to the column. For getting the left threshold of the column with respect to the table, we need to find the <code>x</code> value of the table. Once we get the table's offset, we can subtract it from the offset of the column.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1637338173315/3ge9s1bZ2.png" alt="Offset Calculation" /></p>
<p>Example of the calculation:</p>
<ul>
<li>Position of Table = (100, 200) // &lt;-- x = 100</li>
<li>Position of Company = (100, 200) // &lt;-- x with respect to table = 100 - 100 = 0</li>
<li>Position of Manager = (300, 200) // &lt;-- x with respect to table = 300 - 100 = 200</li>
</ul>
<h2 id="heading-sticky-directive">Sticky Directive</h2>
<p>We are going to create a directive to do just that. The directive can then be placed on the columns which need to be sticky. If you are thinking about why a directive for this particular use case, the calculation of the sticky thresholds can be done easily. Creating a directive makes it easy to re-use the functionality for different elements.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { CommonModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/common'</span>;
<span class="hljs-keyword">import</span> {
  AfterViewInit,
  Directive,
  ElementRef,
  NgModule,
  Optional,
} <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;

<span class="hljs-meta">@Directive</span>({
  selector: <span class="hljs-string">'[stickyTable]'</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> StickyTableDirective {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> el: ElementRef</span>) {}

  get x() {
    <span class="hljs-keyword">return</span> (<span class="hljs-built_in">this</span>.el.nativeElement <span class="hljs-keyword">as</span> HTMLElement)?.getBoundingClientRect()?.x;
  }
}

<span class="hljs-meta">@Directive</span>({
  selector: <span class="hljs-string">'[sticky]'</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> StickyDirective <span class="hljs-keyword">implements</span> AfterViewInit {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">
    <span class="hljs-keyword">private</span> el: ElementRef,
    <span class="hljs-meta">@Optional</span>() <span class="hljs-keyword">private</span> table: StickyTableDirective
  </span>) {}

  ngAfterViewInit() {
    <span class="hljs-keyword">const</span> el = <span class="hljs-built_in">this</span>.el.nativeElement <span class="hljs-keyword">as</span> HTMLElement;
    <span class="hljs-keyword">const</span> { x } = el.getBoundingClientRect();
    el.style.position = <span class="hljs-string">'sticky'</span>;
    el.style.left = <span class="hljs-built_in">this</span>.table ? <span class="hljs-string">`<span class="hljs-subst">${x - <span class="hljs-built_in">this</span>.table.x}</span>px`</span> : <span class="hljs-string">'0px'</span>;
  }
}

<span class="hljs-meta">@NgModule</span>({
  declarations: [StickyDirective, StickyTableDirective],
  imports: [CommonModule],
  <span class="hljs-built_in">exports</span>: [StickyDirective, StickyTableDirective],
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> StickyDirectiveModule {}
</code></pre>
<p>If you look at the code above, we have two directives:</p>
<ol>
<li>StickyDirective</li>
<li>StickyTableDirective</li>
</ol>
<p>The second directive is really interesting here. Why do we need a second directive?
We have a separate directive that can be placed on the table to get the offset of the table. The directive can then be injected inside the main <code>StickyDirective</code>.</p>
<pre><code class="lang-ts">  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">
    <span class="hljs-keyword">private</span> el: ElementRef,
    <span class="hljs-meta">@Optional</span>() <span class="hljs-keyword">private</span> table: StickyTableDirective
  </span>) {}
</code></pre>
<p>We mark the <code>StickyTableDirective</code> as <code>@Optional()</code> so that we can add the <code>StickyDirective</code> directive on other elements and it can automatically be sticky with the default value.</p>
<p>Ref: https://angular.io/guide/hierarchical-dependency-injection#optional</p>
<p>Here is how we use it in HTML.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">table</span> <span class="hljs-attr">stickyTable</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">th</span> <span class="hljs-attr">sticky</span>&gt;</span>Company<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">th</span> <span class="hljs-attr">sticky</span>&gt;</span>Manager<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">th</span>&gt;</span>Employees<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">th</span>&gt;</span>Contractors<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">th</span>&gt;</span>Jobs<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">th</span>&gt;</span>Contracts<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">th</span>&gt;</span>Vacancy<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">th</span>&gt;</span>Offices<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ng-container</span> *<span class="hljs-attr">ngFor</span>=<span class="hljs-string">"let item of data"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">td</span> <span class="hljs-attr">sticky</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"min-width:200px"</span>&gt;</span>{{ item.company }}<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">td</span> <span class="hljs-attr">sticky</span>&gt;</span>{{ item?.manager }}<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span> {{ item?.employees }} <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span> {{ item?.contractors }} <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span> {{ item?.employees }} <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span> {{ item?.contractors }} <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span> {{ item?.employees }} <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span> {{ item?.contractors }} <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ng-container</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">table</span>&gt;</span>
</code></pre>
<p>We add the <code>stickyTable</code> directive to the table and the <code>sticky</code> directive to the column.</p>
<h2 id="heading-demo-and-code">Demo and Code</h2>
<iframe src="https://stackblitz.com/edit/angular-ivy-mcwnrf?embed=1&amp;file=src/app/app.component.html&amp;hideExplorer=1&amp;hideNavigation=1&amp;view=preview" width="100%" height="600px"></iframe>

<p> <a target="_blank" href="https://stackblitz.com/edit/angular-ivy-mcwnrf?file=src/app/sticky.directive.ts">Stackblitz Link</a></p>
<h2 id="heading-improvements">Improvements</h2>
<p>A lot of improvements can be made to this directive to make it more re-usable like: </p>
<ul>
<li>Add support for other directions.</li>
<li>Generalize the <code>StickyTableDirective</code> to be able to use it on other elements as well.</li>
</ul>
<p>For the sake of keeping the example here simple, I've kept it simple.</p>
<h2 id="heading-similar-reads">Similar Reads</h2>
<ol>
<li><a target="_blank" href="https://blog.sreyaj.dev/how-to-implement-heatmap-in-tables-using-directives-in-angular">Implement Heatmaps in a table using directives</a> </li>
<li><a target="_blank" href="https://blog.sreyaj.dev/highlight-text-in-angular-using-directives">Highlight text in paragraphs with a simple directive in Angular</a></li>
<li><a target="_blank" href="https://blog.sreyaj.dev/fullscreen-toggle-angular-using-directives">Fullscreen toggle functionality in Angular using Directives.</a>  </li>
</ol>
<h2 id="heading-connect-with-me">Connect with me</h2>
<ul>
<li><a target="_blank" href="https://twitter.com/AdiSreyaj">Twitter</a></li>
<li><a target="_blank" href="https://github.com/adisreyaj">Github</a></li>
<li><a target="_blank" href="https://www.linkedin.com/in/adithyasreyaj/">Linkedin</a></li>
<li><a target="_blank" href="https://cardify.adi.so">Cardify</a> - Dynamic SVG Images for Github Readmes</li>
</ul>
<p>Do add your thoughts in the comments section.
Stay Safe ❤️</p>
<p><a target="_blank" href="https://www.buymeacoffee.com/adisreyaj"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639498527478/IA3aJ9R0J.png" alt="Buy me a pizza" /></a></p>
]]></content:encoded></item><item><title><![CDATA[How To Implement Heatmap In Tables Using Directives In Angular]]></title><description><![CDATA[Let's see how easy it is to add heatmaps to tables in Angular using Directives. We'll go for a really simple and elegant solution to have separate heatmap colors to different columns in the table.
As I always say, Directives are a really powerful fea...]]></description><link>https://sreyaj.dev/how-to-implement-heatmap-in-tables-using-directives-in-angular</link><guid isPermaLink="true">https://sreyaj.dev/how-to-implement-heatmap-in-tables-using-directives-in-angular</guid><category><![CDATA[Angular]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Adithya Sreyaj]]></dc:creator><pubDate>Tue, 12 Oct 2021 13:51:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1633846302091/vQ_xTbmhu.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Let's see how easy it is to add heatmaps to tables in Angular using Directives. We'll go for a really simple and elegant solution to have separate heatmap colors to different columns in the table.</p>
<p>As I always say, Directives are a really powerful feature of Angular. It can be used as an elegant solution to implement a lot of cool functionalities. You can clearly see why the directive approach makes more sense when you reach the end of the post.</p>
<h2 id="heading-heatmaps-in-table">Heatmaps in table</h2>
<p>Even though it's not that often we see heatmaps in tables, but heatmaps can really add some value in terms of visualization. It would make sense in data sets where there is some kind of comparison or range.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1633803285442/U7JaJsr8f.png" alt="Table with Heatmap" /></p>
<h2 id="heading-why-directives">Why Directives?</h2>
<p>If you have this question in mind, here are some reasons which justify why creating a directive for implementing this feature will make sense.
The logic can be completely moved outside of the component, making it simpler and leaner. If the logic is separated from the component, that means it's more reusable.
When something is built in a re-usable manner, it will be easy to scale and maintain.</p>
<h2 id="heading-heatmap-logic">Heatmap Logic</h2>
<p>For implementing this functionality let's look at what exactly needs to be done here. So basically, heatmaps give the user idea of the magnitude of something by variation in color or hue.</p>
<p>So If we have a set of numbers:</p>
<pre><code class="lang-ts">[<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">4</span>,<span class="hljs-number">5</span>,<span class="hljs-number">6</span>,<span class="hljs-number">7</span>,<span class="hljs-number">8</span>,<span class="hljs-number">9</span>,<span class="hljs-number">10</span>]
</code></pre>
<p>Here based on the value we can manipulate the intensity of a color. Meaning <code>1</code> will be the lightest shade of the color and <code>10</code> will be the color itself. So we just need to map the values to the intensity of the colors here. We can also have the opposite condition as well.</p>
<p>There are different ways to implement this. </p>
<h3 id="heading-using-alpha-channel">Using Alpha Channel</h3>
<p>We can easily implement heatmaps using <strong>RGBA</strong> or <strong>HSLA</strong> by just changing the alpha channel meaning the transparency of the color.</p>
<p>I am not going with this approach as we are also trying to generate accessible text colors based on the background color. This will ensure the text will remain readable for all the color stops.</p>
<h3 id="heading-using-hsl-color-expression">Using HSL Color Expression</h3>
<p>Here I am gonna be using HSL color expression to easily get the right color for each value by manipulating the <code>L (Lightness)</code> parameter. 
<strong>HSL</strong> is a really good way to express colors and manipulating the colors is very easy with it.</p>
<p><strong>HSL</strong> stands for <code>Hue</code> <code>Saturation</code> <code>Lightness</code> and it can also have an <code>Alpha</code> channel with <strong>HSLA</strong></p>
<p>So the idea here is to find the <code>Lightness</code> factor for each value. Here's how we can do it.</p>
<p>So here the original color value is first parsed to HSLA:</p>
<p><code>hsla(234, 77%, 46%, 1)</code> --&gt; <code>Lightness = 46%</code></p>
<p>We have the min possible value for Lightness ie 0.46. So the highest value will have a lightness of <code>46%</code> and for other values, it will be higher. When lightness increases it moves nearer to <code>White</code>.</p>
<p>Here is the formula:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> color = <span class="hljs-string">'#1b2dd0'</span>;
<span class="hljs-keyword">const</span> [h,s,l,a] = parseHSLA(color); <span class="hljs-comment">// &lt;-- [234, 0.77,0.46,1]</span>
<span class="hljs-keyword">const</span> highestValue = <span class="hljs-number">10</span>;
<span class="hljs-keyword">const</span> maxLightness = <span class="hljs-number">1</span> - l; <span class="hljs-comment">// &lt;-- 1 - 0.46 = 0.54</span>

<span class="hljs-keyword">const</span> lightness = <span class="hljs-number">1</span> - (value * maxLightness / highestValue);

<span class="hljs-comment">// 1 --&gt; 1 - (1 * 0.54 / 10) = (1 - 0.05) ~ 95% </span>
<span class="hljs-comment">// 5 --&gt; 1 - (5 * 0.46 / 10) = (1 - 0.23) ~ 77%</span>
<span class="hljs-comment">// 10 -&gt; 1 - (10 * 0.54 / 10) = (1 - 0.54) ~ 46%</span>
</code></pre>
<p>Here 10 will be the lowest number and hence we need a very light color so 95% will make it very light.
Lightness % as it increases makes the color whiter.
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1633803094299/55wlsTqtd.png" alt="Lightness Palette" />
So now we have the logic in place, let's start with the directives!</p>
<h2 id="heading-creating-heatmap-directives">Creating Heatmap Directives</h2>
<p>So I mentioned "Directives" (plural) as we will be creating multiple directives for this functionality. To be specific 3 of them. Out of the 3, two of them are just for tagging the element and setting some metadata:</p>
<ol>
<li>Heatmap Table</li>
<li>Heatmap Column</li>
<li>Heatmap Cell</li>
</ol>
<p>Here is how we will use the directives in the template:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">table</span> <span class="hljs-attr">heatMapTable</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">th</span>&gt;</span>Company<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">th</span>&gt;</span>Manager<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">th</span> [<span class="hljs-attr">heatMapColumn</span>]=<span class="hljs-string">"options.employees"</span>  <span class="hljs-attr">id</span>=<span class="hljs-string">"employees"</span>&gt;</span>
        Employees
    <span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">th</span> [<span class="hljs-attr">heatMapColumn</span>]=<span class="hljs-string">"options.contractors"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"contractors"</span>&gt;</span>
        Contractors
    <span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ng-container</span> *<span class="hljs-attr">ngFor</span>=<span class="hljs-string">"let item of data"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>{{ item.company }}<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>{{ item?.manager }}<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">td</span> [<span class="hljs-attr">heatMapCell</span>]=<span class="hljs-string">"item.employees"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"employees"</span>&gt;</span>
         {{ item?.employees }}
      <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">td</span> [<span class="hljs-attr">heatMapCell</span>]=<span class="hljs-string">"item.contractors"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"contractors"</span>&gt;</span>
        {{ item?.contractors }}
      <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ng-container</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">table</span>&gt;</span>
</code></pre>
<h3 id="heading-heatmap-cell-directive">Heatmap Cell Directive</h3>
<pre><code class="lang-ts"><span class="hljs-meta">@Directive</span>({
  selector: <span class="hljs-string">'[heatMapCell]'</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> HeatmapCellDirective {
  <span class="hljs-meta">@Input</span>(<span class="hljs-string">'heatMapCell'</span>)
  heatMap = <span class="hljs-number">0</span>;

  <span class="hljs-meta">@Input</span>(<span class="hljs-string">'id'</span>)
  colId = <span class="hljs-literal">null</span>;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">public</span> el: ElementRef&lt;HTMLElement&gt;</span>) {}
}
</code></pre>
<p>We have an input to pass the value into the directive and also accept the id of the column to which the cell belongs in the table. We inject the <code>ElementRef</code> so that we can manipulate the element later.</p>
<h3 id="heading-heatmap-column-directive">Heatmap Column Directive</h3>
<pre><code class="lang-ts"><span class="hljs-meta">@Directive</span>({
  selector: <span class="hljs-string">'[heatMapColumn]'</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> HeatmapColumnDirective {
  <span class="hljs-meta">@Input</span>(<span class="hljs-string">'id'</span>)
  colId = <span class="hljs-literal">null</span>;

  <span class="hljs-meta">@Input</span>(<span class="hljs-string">'heatMapColumn'</span>)
  options = {};
}
</code></pre>
<p>Here we can pass options for styling like the color etc and also the id of the column.</p>
<h3 id="heading-heatmap-table-directive">Heatmap Table Directive</h3>
<p>This is the main directive where all the work is done. This directive is placed on the table. And the other directives are placed on the column and the cells.</p>
<p>Here we can see how we can access child directives from the parent directive using <a target="_blank" href="https://angular.io/api/core/ContentChildren#description">ContentChildren</a>.</p>
<pre><code class="lang-ts"><span class="hljs-meta">@Directive</span>({
  selector: <span class="hljs-string">'[heatMapTable]'</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> HeatmapTableDirective <span class="hljs-keyword">implements</span> AfterViewInit {
  <span class="hljs-meta">@ContentChildren</span>(HeatmapCellDirective, { descendants: <span class="hljs-literal">true</span> })
  heatMapCells: QueryList&lt;HeatmapCellDirective&gt;; <span class="hljs-comment">// &lt;-- Get all the cells</span>
  <span class="hljs-meta">@ContentChildren</span>(HeatmapColumnDirective, { descendants: <span class="hljs-literal">true</span> })
  heatMapColumns: QueryList&lt;HeatmapColumnDirective&gt;; <span class="hljs-comment">// &lt;-- Get all the columns</span>

  highestValues = {};
  cells: HeatmapCellDirective[] = [];
  columns: HeatmapColumnDirective[] = [];
  config = {};

  ngAfterViewInit() {
    <span class="hljs-built_in">this</span>.cells = <span class="hljs-built_in">this</span>.heatMapCells.toArray();
    <span class="hljs-built_in">this</span>.columns = <span class="hljs-built_in">this</span>.heatMapColumns.toArray();
    <span class="hljs-built_in">this</span>.setOptions();
    <span class="hljs-built_in">this</span>.calculateHighestValues();
    <span class="hljs-built_in">this</span>.applyHeatMap();
  }

  <span class="hljs-keyword">private</span> setOptions() {
    <span class="hljs-built_in">this</span>.columns.forEach(<span class="hljs-function">(<span class="hljs-params">col</span>) =&gt;</span> {
      <span class="hljs-built_in">this</span>.config = {
        ...this.config,
        [col.colId]: col.options,
      };
    });
  }

  <span class="hljs-keyword">private</span> calculateHighestValues() {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.cells.forEach(<span class="hljs-function">(<span class="hljs-params">{ colId, heatMap }</span>) =&gt;</span> {
      <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">Object</span>.prototype.hasOwnProperty.call(<span class="hljs-built_in">this</span>.highestValues, colId)) {
        <span class="hljs-built_in">this</span>.highestValues[colId] = <span class="hljs-number">0</span>;
      }
      <span class="hljs-keyword">if</span> (heatMap &gt; <span class="hljs-built_in">this</span>.highestValues?.[colId])
        <span class="hljs-built_in">this</span>.highestValues[colId] = heatMap;
    });
  }

  <span class="hljs-keyword">private</span> applyHeatMap() {
    <span class="hljs-built_in">this</span>.cells.forEach(<span class="hljs-function">(<span class="hljs-params">cell</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> { bgColor, color } = <span class="hljs-built_in">this</span>.getColor(cell.colId, cell.heatMap);
      <span class="hljs-keyword">if</span> (bgColor) cell.el.nativeElement.style.backgroundColor = bgColor;
      <span class="hljs-keyword">if</span> (color) cell.el.nativeElement.style.color = color;
    });
  }

  <span class="hljs-keyword">private</span> getColor(id: <span class="hljs-built_in">string</span>, value: <span class="hljs-built_in">number</span>) {
    <span class="hljs-keyword">const</span> color = <span class="hljs-built_in">this</span>.config[id].color;
    <span class="hljs-keyword">let</span> textColor = <span class="hljs-literal">null</span>;
    <span class="hljs-keyword">let</span> bgColor = <span class="hljs-literal">null</span>;
    <span class="hljs-keyword">if</span> (color != <span class="hljs-literal">null</span>) {
      <span class="hljs-keyword">const</span> [h, s, l, a] = parseToHsla(color);
      <span class="hljs-keyword">const</span> maxLightness = <span class="hljs-number">1</span> - l;
      <span class="hljs-keyword">const</span> percentage = (value * maxLightness) / <span class="hljs-built_in">this</span>.highestValues[id];
      <span class="hljs-keyword">const</span> lightness = +percentage.toFixed(<span class="hljs-number">3</span>);
      bgColor = hsla(h, s, <span class="hljs-number">1</span> - lightness, a);
      textColor = readableColor(bgColor);
    }
    <span class="hljs-keyword">return</span> {
      bgColor,
      color: textColor,
    };
  }
</code></pre>
<p>Let me break down the code.</p>
<h4 id="heading-get-access-to-the-cells-and-columns">Get access to the cells and columns</h4>
<p>We get access to the cells to which the heatmap needs to be applied:</p>
<pre><code class="lang-ts"><span class="hljs-meta">@ContentChildren</span>(HeatmapCellDirective, { descendants: <span class="hljs-literal">true</span> })
  heatMapCells: QueryList&lt;HeatmapCellDirective&gt;;
</code></pre>
<p>This <code>heatMapCells</code> variable will have the list of <code>td</code> to which the <code>heatMapCell</code> was applied. Make sure to set <code>{ descendants: true }</code>.</p>
<p>Note: If true include all descendants of the element. If false then only query direct children of the element.</p>
<h4 id="heading-save-the-options-for-each-column">Save the options for each column</h4>
<p>We can save the options provided for each column in an object. Currently, we are only configuring the color, but this object can be used for all kinds of different options for customizing the heatmap for each column.</p>
<pre><code class="lang-ts">config = {
    <span class="hljs-string">"employees"</span>: {
        <span class="hljs-string">"color"</span>: <span class="hljs-string">"#000fff"</span>
    },
    <span class="hljs-string">"contractors"</span>: {
        <span class="hljs-string">"color"</span>: <span class="hljs-string">"#309c39"</span>
    }
}
</code></pre>
<h4 id="heading-calculate-the-highest-value-for-each-column">Calculate the Highest Value for each column</h4>
<p>We can now calculate the highest value for each column and save it in an object with the <code>colId</code> as the key.</p>
<pre><code class="lang-ts">highestValues = {
   employees: <span class="hljs-number">1239</span>,
   contractors: <span class="hljs-number">453</span>
}
</code></pre>
<h4 id="heading-applying-the-heatmap-styles">Applying the Heatmap styles</h4>
<p>We can now loop through the cells and then apply <code>backgroundColor</code> and <code>color</code> to the cell. Since we have injected the <code>ElementRef</code> in the cell, we can use the <code>el</code> property to modify styles:</p>
<pre><code class="lang-ts">cell.el.nativeElement.style.backgroundColor = <span class="hljs-string">'blue'</span>;
</code></pre>
<p>We have a helper function which finds the color for each cell based on the logic we have discussed above:</p>
<pre><code class="lang-ts">  <span class="hljs-keyword">private</span> getColor(id: <span class="hljs-built_in">string</span>, value: <span class="hljs-built_in">number</span>) {
    <span class="hljs-keyword">const</span> color = <span class="hljs-built_in">this</span>.config[id].color;
    <span class="hljs-keyword">let</span> textColor = <span class="hljs-literal">null</span>;
    <span class="hljs-keyword">let</span> bgColor = <span class="hljs-literal">null</span>;
    <span class="hljs-keyword">if</span> (color != <span class="hljs-literal">null</span>) {
      <span class="hljs-keyword">const</span> [h, s, l, a] = parseToHsla(color);
      <span class="hljs-keyword">const</span> maxLightness = <span class="hljs-number">1</span> - l;
      <span class="hljs-keyword">const</span> percentage = (value * maxLightness) / <span class="hljs-built_in">this</span>.highestValues[id];
      <span class="hljs-keyword">const</span> lightness = +percentage.toFixed(<span class="hljs-number">3</span>);
      bgColor = hsla(h, s, <span class="hljs-number">1</span> - lightness, a);
      textColor = readableColor(bgColor);
    }
    <span class="hljs-keyword">return</span> {
      bgColor,
      color: textColor,
    };
  }
</code></pre>
<p>The color manipulation is done using a super simple library <code>color2k</code> which provides a lot of utilities to mess with colors.</p>
<p>We have used something called <code>readableColor()</code> which returns black or white for best contrast depending on the luminosity of the given color. This will make our heatmap more accessible.</p>
<h2 id="heading-demo-and-code">Demo and Code</h2>
<iframe src="https://stackblitz.com/edit/angular-ivy-ohdjff?ctl=1&amp;embed=1&amp;file=src/app/app.component.ts&amp;hideExplorer=1&amp;hideNavigation=1&amp;view=preview" width="100%" height="600px"></iframe>

<p> <a target="_blank" href="https://stackblitz.com/edit/angular-ivy-ohdjff?file=src/app/heatmap.directive.ts">Stackblitz Link</a></p>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>As you can see, there is not much code in the component. All the logic is beautifully handled inside the directive. The only complex stuff going on in the directive is finding the colors. Everything else is straightforward.</p>
<p>This is a very basic implementation and not perfect too. To make it better, we might have to add some validation and error handling as well. Also, this can be extended by providing more options like Ascending/Descending heatmaps, color ranges, positive and negative heatmaps, and more.</p>
<p>The whole idea of the blog post is to showcase how a directive can be used for implementing this feature.</p>
<h2 id="heading-connect-with-me">Connect with me</h2>
<ul>
<li><a target="_blank" href="https://twitter.com/AdiSreyaj">Twitter</a></li>
<li><a target="_blank" href="https://github.com/adisreyaj">Github</a></li>
<li><a target="_blank" href="https://www.linkedin.com/in/adithyasreyaj/">Linkedin</a></li>
<li><a target="_blank" href="https://cardify.adi.so">Cardify</a> - Dynamic SVG Images for Github Readmes</li>
</ul>
<p>Do add your thoughts in the comments section.
Stay Safe ❤️</p>
<p><a target="_blank" href="https://www.buymeacoffee.com/adisreyaj"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639498527478/IA3aJ9R0J.png" alt="Buy me a pizza" /></a></p>
]]></content:encoded></item><item><title><![CDATA[How to backup Postgres data to S3 Bucket using Minio]]></title><description><![CDATA[Managing a database on your own can be problematic. As someone who is not that into DevOps, setting up a database and managing it is a difficult task. I have currently hosted two of my side projects -  Cartella  and  Compito  in a barebones Ubuntu VP...]]></description><link>https://sreyaj.dev/how-to-backup-postgres-data-to-s3-bucket-using-minio</link><guid isPermaLink="true">https://sreyaj.dev/how-to-backup-postgres-data-to-s3-bucket-using-minio</guid><category><![CDATA[PostgreSQL]]></category><category><![CDATA[Amazon S3]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[Devops]]></category><category><![CDATA[deployment]]></category><dc:creator><![CDATA[Adithya Sreyaj]]></dc:creator><pubDate>Fri, 03 Sep 2021 11:15:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1630667560527/psZsgkHli.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Managing a database on your own can be problematic. As someone who is not that into DevOps, setting up a database and managing it is a difficult task. I have currently hosted two of my side projects -  <a target="_blank" href="https://cartella.sreyaj.dev">Cartella</a>  and  <a target="_blank" href="https://blog.sreyaj.dev/compito-project-management-app-angular-nestjs-auth0">Compito</a>  in a barebones <strong>Ubuntu</strong> VPS. </p>
<h2 id="self-hosted-vs-managed-db">Self-hosted vs Managed DB</h2>
<p>There can be a lot of problems when you are new to this whole deployment scene. I recently faced an issue with Cartella, where all the data was lost somehow. I hosted a Postgres database in a docker container which was used by the node application running in another docker container. </p>
<p>One day, I realized I was not able to log in to Cartella, I checked the PM2 dashboard to see if the server is running fine or not. The API which is a node application was running fine. So next I checked the DB, and to my surprise, there was no data.</p>
<p>With managed DB services, we don't have to worry about anything. Everything is already taken care of for us, backups in time, restoration, guaranteed uptime, etc. The only downside is that it can be expensive. For instance, DO provides managed Postgres DB for $15/month while you can self host a Postgres DB in one of their droplets for $5/month.</p>
<h2 id="the-need-for-backup">The need for backup</h2>
<p>That day I realized how important it is to have backups. Even if you don't have all the data, there is at least some data which is much better If you ask me. The application was just running as a demo and so I didn't bother much to set up backups and DB properly. </p>
<p>I didn't bother much as I was experimenting with deployments and stuff. Backing up of data is still important though. So once I get hold of how to deploy DB and set up connections, it's finally time to learn how to backup data.</p>
<h2 id="how-to-create-local-postgres-backups">How to create local Postgres backups</h2>
<p>There are quite a few ways to create Postgres backups. Out of them, one of the most common methods that I could find is using <code>pg_dump</code>.
<code>pg_dump</code> is a small utility for backing up Postgres databases. It can be used to back up a single DB at a time. What the utility does is that it creates an SQL query with the data in the database. So if we want to restore the backup, we could simply run the query.</p>
<pre><code class="lang-sh">pg_dump -U &lt;DB_USER&gt; &lt;DB_NAME&gt; -h &lt;DB_HOST&gt; -p &lt;DB_PORT&gt; &gt; backup.sql
</code></pre>
<p>Replace the arguments like below. You can omit the host and port if its the default(localhost:5432)</p>
<pre><code class="lang-sh">pg_dump -U postgres mydb  &gt; backup.sql
</code></pre>
<p>Now that we have the backup ready, It's safe to upload it elsewhere so that we don't lose the backup in case something goes wrong with our server.</p>
<h3 id="save-the-postgres-credentials">Save the Postgres credentials</h3>
<p>We need to save the Postgres DB credentials locally so that we can use <code>pg_dump</code> without having to enter the credentials. </p>
<ol>
<li>Create a file named <code>.pgpass</code> in the root folder of the user( Eg: <code>/home/ubuntu/</code>)</li>
<li>Use an editor and add the following data (replace with your credentials) in the file<pre><code class="lang-sh">hostname:port:database:username:password
</code></pre>
</li>
<li>Save the file and update the file permissions :<pre><code class="lang-sh">chmod 600 .pgpass
</code></pre>
</li>
</ol>
<h2 id="uploading-the-backup-file-to-cloud-storage">Uploading the backup file to Cloud Storage</h2>
<p>I'm gonna be using Object storage from Oracle Cloud (Similar to AWS S3). Oracle cloud object storage has S3 compatibility and so we can easily use any S3 compatible client to access it. For interacting with our bucket, we can use the Min.io client.
Min.io client provides us with a CLI that can be used to do all kinds of operations in our object storage.</p>
<ol>
<li>Install the Min.io Client:<pre><code class="lang-sh">wget https://dl.min.io/client/mc/release/linux-amd64/mcli_20210902092127.0.0_amd64.deb
dpkg -i mcli_20210902092127.0.0_amd64.deb
</code></pre>
Check the official docs for other OS: https://min.io/download</li>
<li>Verify the installation:<pre><code class="lang-sh">mcli version
</code></pre>
</li>
<li>Connect to the storage<pre><code class="lang-sh">mcli <span class="hljs-built_in">alias</span> <span class="hljs-built_in">set</span> &lt;ALIAS&gt; &lt;YOUR-S3-ENDPOINT&gt; &lt;YOUR-ACCESS-KEY&gt; &lt;YOUR-SECRET-KEY&gt;
</code></pre>
</li>
<li>Verify the connection by running the command to list the available buckets<pre><code class="lang-sh">mcli ls &lt;ALIAS&gt;
</code></pre>
This should list all the buckets in the storage.</li>
</ol>
<p>Ref:  <a target="_blank" href="https://docs.min.io/docs/minio-client-quickstart-guide.html">Minio Client Docs</a> </p>
<h2 id="creating-the-backup-script">Creating the backup script</h2>
<p>We'll create a script that can create the backup and upload the same to the object storage. A very small bash script can do this:</p>
<pre><code class="lang-sh"><span class="hljs-meta">#!/bin/bash</span>

<span class="hljs-comment"># Constants</span>
USER=<span class="hljs-string">"postgres"</span>
DATABASE=<span class="hljs-string">"compito"</span>
HOST=<span class="hljs-string">"localhost"</span>
BACKUP_DIRECTORY=<span class="hljs-string">"/home/ubuntu/backups"</span>

<span class="hljs-comment"># Date stamp (formated YYYYMMDD)</span>
CURRENT_DATE=$(date <span class="hljs-string">"+%Y%m%d"</span>)

<span class="hljs-comment"># Create the backup and then zip it</span>
pg_dump -U <span class="hljs-variable">$USER</span> <span class="hljs-variable">$DATABASE</span> -h <span class="hljs-variable">$HOST</span> | gzip - &gt; <span class="hljs-variable">$BACKUP_DIRECTORY</span>/<span class="hljs-variable">$DATABASE</span>\_<span class="hljs-variable">$CURRENT_DATE</span>.sql.gz

<span class="hljs-comment"># Upload to cloud</span>
mcli cp <span class="hljs-variable">$BACKUP_DIRECTORY</span>/<span class="hljs-variable">$DATABASE</span>\_<span class="hljs-variable">$CURRENT_DATE</span>.sql.gz oracle/compito-backup
</code></pre>
<p>You can save the code as a bash file --&gt; <code>backup.sh</code></p>
<p>What the script does is self-explanatory.</p>
<ol>
<li>Setup the constants like the user name, database name, etc</li>
<li>Get the current time so that we can use it for tagging the backup files.</li>
<li>Create the backup using the <code>pg_dump</code> command and use <code>gzip</code> to compress it.</li>
<li>Use the minio CLI to copy the file to the cloud bucket<pre><code class="lang-sh">mcli cp &lt;FILE&gt; &lt;BUCKET&gt;/&lt;FOLDER&gt;
</code></pre>
</li>
</ol>
<h2 id="setup-a-cron-job-to-automate-the-backup-process">Setup a CRON job to automate the backup process</h2>
<p>It's very easy to set up a cronjob. Follow the steps and create a cronjob for our backup script.</p>
<p>Use any  <a target="_blank" href="https://crontab.guru/daily">crontab string generator</a> to create the desired frequency of the job. I plan on backing up the DB daily. Here's how the cron schedule expression looks like:</p>
<pre><code><span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-string">*</span> <span class="hljs-string">*</span> <span class="hljs-string">*</span>
</code></pre><p>It runs the script daily at 12.00 AM.</p>
<p>In Linux, the cron daemon is inbuilt and looks at the Cron Tables for the scheduled jobs. We need to add a new entry in the <code>crontab</code> like so:</p>
<ol>
<li>In the terminal, run the below command to edit the cron-table:<pre><code class="lang-sh">crontab -e
</code></pre>
</li>
<li>Append the line at the end and save.<pre><code class="lang-sh">0 0 * * * /home/ubuntu/backup.sh
</code></pre>
</li>
</ol>
<p>You are all set now! You can sit back and relax now. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1630666111993/HTfD_XZAS.png" alt="Backups in storage" />
If you go to the object storage and check the folder, you'll be able to see the backups saved.</p>
<p>Note: This script is just a starter and intended to be used with demo or side projects. In a production setup, you would have to consider a lot more stuff like error handling, backup rotation, and more. Thanks to  <a target="_blank" href="https://twitter.com/ascherbaum">Andreas</a> for bringing this up.</p>
<h2 id="connect-with-me">Connect with me</h2>
<ul>
<li><a target="_blank" href="https://twitter.com/AdiSreyaj">Twitter</a></li>
<li><a target="_blank" href="https://github.com/adisreyaj">Github</a></li>
<li><a target="_blank" href="https://www.linkedin.com/in/adithyasreyaj/">Linkedin</a></li>
<li><a target="_blank" href="https://compito.adi.so">Compito</a> - open source project management application</li>
</ul>
<p><a target="_blank" href="https://www.buymeacoffee.com/adisreyaj"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1618661389599/2B667-okT.png" alt="Buy me a pizza" /></a></p>
<p>Do add your thoughts in the comments section.
Stay Safe ❤️</p>
]]></content:encoded></item><item><title><![CDATA[Compito - A project management app built using Angular, NestJs & Auth0]]></title><description><![CDATA[Compito is a simple and easy-to-use project management application built using Angular and NestJs. Authentication/Authorization is all handled by Auth0.
It's my submission towards the  Auth0 x Hashnode hackthon. It was a great experience building som...]]></description><link>https://sreyaj.dev/compito-project-management-app-angular-nestjs-auth0</link><guid isPermaLink="true">https://sreyaj.dev/compito-project-management-app-angular-nestjs-auth0</guid><category><![CDATA[Auth0Hackathon]]></category><category><![CDATA[Auth0]]></category><category><![CDATA[Angular]]></category><category><![CDATA[nest]]></category><category><![CDATA[TypeScript]]></category><dc:creator><![CDATA[Adithya Sreyaj]]></dc:creator><pubDate>Sat, 28 Aug 2021 18:23:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1629827787604/DEVExf_9e.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Compito is a simple and easy-to-use project management application built using Angular and NestJs. Authentication/Authorization is all handled by Auth0.</p>
<p>It's my submission towards the  <a target="_blank" href="https://townhall.hashnode.com/auth0-hackathon">Auth0 x Hashnode hackthon</a>. It was a great experience building something within such a short span of 3 weeks. After more than 300 commits, I feel the app is having all the basic functionality that I planned.</p>
<iframe width="100%" height="500" src="https://www.youtube.com/embed/TAnBjB0fKuE"></iframe>

<h2 id="heading-thought">Thought</h2>
<p>There are a lot of ways to manage tasks on the internet from simple to really complex project management applications. I always wanted a simple and aesthetic application that should be dead simple without a lot of features. Only the essentials.
And also there should be a simple way to separate projects and share them with different users without a hassle.</p>
<p>And for someone with a lot of side projects, I could see myself building a side project to manage my other side projects, and here we are! </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1629573733871/FVVfRh0Sf.jpeg" alt="Compito project management" /></p>
<h2 id="heading-features">Features</h2>
<p>The basic idea is to create a simple kanban board but later came up with more features and finally decided to go with a Multi-Org project management application.</p>
<h3 id="heading-home-page">Home Page</h3>
<p>The home page gives you an overview of the tasks and projects the user has access to. The user can see tasks that were recently created and the high-priority tasks as well.</p>
<p>There are quick links to get to projects and boards from the home page.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1629628696196/eHA9aCCm3.jpeg" alt="Home Page" /></p>
<h3 id="heading-board-page">Board Page</h3>
<p>With a simple Kanban board, you can easily track your tasks and assign it to different users within the project.
You can create new tasks with ease. Cards can be dragged and dropped as you progress.</p>
<h4 id="heading-attachments">Attachments</h4>
<p>Attachments can be added to the tasks by simply dragging and dropping them into the modal. It will be automatically uploaded and attached to the task. Multiple files with different formats are supported as well.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1630174518740/0V26VopQE.gif" alt="Task attachments" /></p>
<h3 id="heading-orgs-page">Orgs Page</h3>
<p>Users can be part of multiple orgs. User can view all the orgs he/she is part of. The user can only access the data of an org. When the user logs in, If they are part of multiple orgs, they will be asked to select an org to log in to.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1629628741306/LUlRTsvjm.jpeg" alt="Orgs page" /></p>
<h3 id="heading-projects-page">Projects Page</h3>
<p>Shows the list of projects in the org the user has access to. Clicking on the project would take you to the detail page where the boards and members within the project can be seen.
Users with admin access will be able to update the project details and add/remove members to/from the project.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1629628855861/o3f6Oi8zL.jpeg" alt="Projects page" /></p>
<h3 id="heading-users">Users</h3>
<p>List the members in the org/project based on the role. Admin users can invite new users to the org. The list of pending user invites is also shown on the users page.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1629628805943/vtgVF952d.jpeg" alt="Users Page" />
One can signup for the application using the signup page. The user will be asked to create an org when he/she first signs up. The user will be assigned the Admin role for the org.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1630174651171/tgUbH43LJ.png" alt="Signup" /></p>
<p>Once the user signs up, they can invite new users to the org. The invite will be stored in the DB and whenever the user signup/login to the system, they will be able to see invites. The user can then choose to Accept/Reject the invitation.</p>
<p>This is how users can be added to a new org. User's with the most restrictive role <code>User</code> will be added to a <code>Project</code> and everything will be scoped to within the Project. The role hierarchy diagram explains it better.</p>
<h2 id="heading-tech-stack">Tech Stack</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1629625002359/D-RrPy9RV.png" alt="Tech Stack" /></p>
<h3 id="heading-front-end">Front-end</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1629909688037/2rHAH6aw6.jpeg" alt="App theme and colors" /></p>
<p>With this idea in mind, it was time to decide the stack. I choose frameworks that I have already worked with in the past. I am most comfortable with <strong>Angular</strong> when it comes to front-end framework and the choice was very easy for me.</p>
<p>I have tried <strong>Tailwind</strong> a few months back, and since then It's my go-to when it comes to CSS framework. I have stopped using UI frameworks like Material, NgBootstrap, etc and it's all Tailwind.</p>
<h3 id="heading-back-end">Back-end</h3>
<p>Next up for the back-end, I am familiar with express and <strong>NestJs</strong>. NestJs was the obvious choice here as we are dealing with TypeScript here. I love TypeScript and prefer it over JavaScript at any given time. Nest is a really good framework and is super structured which I like. So for the back-end, I decided to go with NestJs.</p>
<p>Next up, I decided to go with <strong>Postgres</strong> and <strong>Prisma</strong>. They work great. Prisma is the best ORM that I've used so far. I have worked with MongoDB, but I thought Prisma would better fit the whole stack. Prisma works really well with TypeScript.</p>
<p>One of the most important parts of the whole application is authentication. Since there is this concept of multi-org, there is a need for RBAC. Even if it wasn't for the hackathon, the first obvious choice I had in mind is <strong>Auth0</strong>. Everything you could think of when it comes to authentication and authorization, Auth0 has it all covered. </p>
<p>So here we are with the final Tech Stack:</p>
<ul>
<li>Angular</li>
<li>TailwindCSS</li>
<li>NGXS</li>
<li>NestJs</li>
<li>Prisma</li>
<li>Postgres</li>
<li>Auth0</li>
<li>TypeScript</li>
</ul>
<h3 id="heading-deployment">Deployment</h3>
<p>Front-end is deployed to Vercel. The back-end is deployed to a VPS (ubuntu w/ Nginx) using Github Actions. The build artifacts are pushed to the server using <code>rsync</code> and then run using <code>pm2</code>. The database is also running in the same instance for reduced latency.</p>
<p>Refs:</p>
<ul>
<li><a target="_blank" href="https://github.com/adisreyaj/compito/blob/main/.github/workflows/main.yml">Github Action</a> </li>
<li><a target="_blank" href="https://www.digitalocean.com/community/tutorials/how-to-use-rsync-to-sync-local-and-remote-directories">rsync</a> </li>
<li><a target="_blank" href="https://github.com/Unitech/pm2">pm2</a>  </li>
</ul>
<h3 id="heading-misc">Misc</h3>
<p>Code formatting is handled by <code>prettier</code> and lining is taken care of by <code>eslint</code>. All the commits are validated using <code>husky</code> and <code>commitlint</code>. Commits follow the Conventional commits structure.</p>
<p>Refs:</p>
<ul>
<li><a target="_blank" href="https://eslint.org/">EsLint</a> </li>
<li><a target="_blank" href="https://prettier.io/">Prettier</a> </li>
<li><a target="_blank" href="https://github.com/conventional-changelog/commitlint">Commit Lint</a> </li>
<li><a target="_blank" href="https://typicode.github.io/husky/#/">Husky</a> </li>
</ul>
<h2 id="heading-folder-structure">Folder Structure</h2>
<p>The whole project is in a monorepo managed by Nx. The API and the Front-end are the two base apps.</p>
<p>All the other pages in the UI are libs. And the UI components and shared items are in the <code>UI</code> lib.</p>
<pre><code>├── apps
│   ├── api
│   └── compito
└── libs
   ├── api<span class="hljs-operator">-</span>interfaces
   └── web
       ├── auth
       ├── boards
       ├── home
       ├── orgs
       ├── profile
       ├── projects
       ├── settings
       ├── tasks
       ├── users
       └── ui
            ├── components
            ├── directives
            ├── index.ts
            ├── interceptors
            ├── pipes
            ├── test<span class="hljs-operator">-</span>setup.ts
            ├── tokens
            └── util
</code></pre><h2 id="heading-data-modelling">Data Modelling</h2>
<p>Here are the main models in the application. Everything revolves around an Organization aka Org.</p>
<ul>
<li>Org</li>
<li>User</li>
<li>Project</li>
<li>Board</li>
<li>Task</li>
<li>Role</li>
<li>Invite
An org is the root where everything will be associated to. Inside an org, there can be multiple projects. Each project can have multiple boards. And boards can have multiple tasks.</li>
</ul>
<p>Here's the full  <a target="_blank" href="https://github.com/adisreyaj/compito/blob/main/prisma/schema.prisma">database schema file</a></p>
<p>The data modeling is not that complex if you see. But when we bring in roles and permissions-based access, the whole thing just becomes more complex. Now we have to validate the user's role and permission and based on that send only data that the user has access to.</p>
<h2 id="heading-roles-and-permissions">Roles and Permissions</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1629572973771/kiYbf8NVC.jpeg" alt="Roles Heirarchy" /></p>
<p>These are the roles defined in the application (No custom roles at this point):</p>
<ul>
<li>Admin</li>
<li>Org Admin</li>
<li>Project Admin</li>
<li>User</li>
</ul>
<p>The hierarchy chart should give some idea about how the users of the application will be placed.</p>
<p>Each role has a set of assigned permissions which is used in the front-end to show only necessary actions and in the back-end to allow access to data they are authorized to view.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1629623675729/srIAiexfK.jpeg" alt="Role Permission Matrix" /></p>
<h2 id="heading-setting-up-auth0">Setting up Auth0</h2>
<p>Auth0 manages all things related to user login and tokens. There is quite a bit of setup that needs to be done in Auth0 to make it work with the current architecture of the application.</p>
<p>The good thing is that there is a good amount of documentation and has SDKs for everything which makes our life easier. For the front-end, I used the Auth0-Angular library for everything from logins to token management.</p>
<h3 id="heading-setting-up-front-end-and-auth0">Setting up front-end and Auth0</h3>
<p>The first step is to create a new application in Auth0, of type Single Page Application.</p>
<h4 id="heading-1-login-to-auth0-and-create-a-new-application">1. Login to Auth0 and create a new application</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1629612973108/awjz9auZr.jpeg" alt="Auth0 Create App" /></p>
<p>Once you have given a name and selected the type of the application. Go ahead and create it.</p>
<h4 id="heading-2-go-to-the-settings-tab-and-copy-the-details-to-your-application">2. Go to the <code>Settings</code> tab and copy the details to your application.</h4>
<pre><code><span class="hljs-bullet">-</span> Domain
<span class="hljs-bullet">-</span> Client ID
</code></pre><h4 id="heading-3-add-the-front-end-url-in-the-allowed-url">3. Add the front-end URL in the Allowed URL</h4>
<p>Add the front-end URL in </p>
<ul>
<li><code>Allowed Callback URLs</code></li>
</ul>
<pre><code class="lang-sh">http://localhost:4200
</code></pre>
<ul>
<li><code>Allowed Web Origins</code></li>
</ul>
<pre><code class="lang-sh">http://localhost:4200
</code></pre>
<ul>
<li><code>Allowed Logout URLs</code> </li>
</ul>
<pre><code class="lang-sh">http://localhost:4200/auth/login
</code></pre>
<h4 id="heading-4-paste-the-above-details-in-the-environmentts">4. Paste the above details in the <code>environment.ts</code></h4>
<pre><code class="lang-ts"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> environment = {
  production: <span class="hljs-literal">false</span>,
  baseURL: <span class="hljs-string">'http://localhost:4200'</span>,
  api: <span class="hljs-string">'http://localhost:3333/api'</span>,
  auth: {
    audience: <span class="hljs-string">'&lt;auth0_audience&gt;'</span>, <span class="hljs-comment">// &lt;-- will come back to this</span>
    domain: <span class="hljs-string">'&lt;auth0_domain&gt;'</span>,
    clientId: <span class="hljs-string">'&lt;auth0_clientId&gt;'</span>,
    redirectUri: <span class="hljs-built_in">window</span>.location.origin,
  },
};
</code></pre>
<h4 id="heading-5-install-the-auth0-angular-library">5. Install the Auth0-Angular library:</h4>
<pre><code class="lang-sh">npm i @auth0/auth0-angular
</code></pre>
<h4 id="heading-6-setup-the-imports-correctly">6. Setup the imports correctly</h4>
<p>Import the <code>AuthModule</code> module in <code>app.module.ts</code>:</p>
<pre><code class="lang-ts">...
imports:[
AuthModule.forRoot({
      domain: environment.auth.domain,
      audience: environment.auth.audience,
      clientId: environment.auth.clientId,
      redirectUri: <span class="hljs-built_in">window</span>.location.origin,
      errorPath: <span class="hljs-string">'/auth/login'</span>,
      cacheLocation: <span class="hljs-string">'localstorage'</span>,
      useRefreshTokens: <span class="hljs-literal">false</span>,
      httpInterceptor: {
        allowedList: [<span class="hljs-string">'/api/*'</span>],
      },
    })]
...
</code></pre>
<h4 id="heading-7-add-the-button-on-the-login-page">7. Add the button on the Login page</h4>
<p>Now we just have to creat a button for login and call <code>loginWithRedirect()</code> method:</p>
<pre><code class="lang-ts"><span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'compito-login'</span>,
  template: <span class="hljs-string">` 
     &lt;main&gt;
       &lt;div&gt;
         &lt;button btn (click)="login()"&gt;Login to Compito&lt;/button&gt;
       &lt;/div&gt;
    &lt;/main&gt;`</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> LoginComponent <span class="hljs-keyword">implements</span> OnInit {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">public</span> auth: AuthService</span>) {}
  login() {
    <span class="hljs-built_in">this</span>.auth.loginWithRedirect({});
  }
}
<span class="hljs-string">`</span>
</code></pre>
<p>Ref <code>login.component.ts</code>  <a target="_blank" href="https://github.com/adisreyaj/compito/blob/main/libs/web/auth/src/lib/login/login.component.ts">source code</a> </p>
<h4 id="heading-8-add-the-route-guard">8. Add the route guard</h4>
<p>Auth0 ships with a built-in Route guard that can be used to protect the routes.</p>
<pre><code class="lang-ts"> {
    path: <span class="hljs-string">''</span>,
    canActivateChild: [AuthGuard], <span class="hljs-comment">// &lt;-- Add here</span>
    loadChildren: <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./dashboard/dashboard.module'</span>).then(<span class="hljs-function">(<span class="hljs-params">m</span>) =&gt;</span> m.DashboardModule),
  }
</code></pre>
<h4 id="heading-9-adding-the-auth-interceptor">9. Adding the Auth Interceptor</h4>
<p>We can then add the provided interceptor to attach the token with the API calls. Auth0 provides the interceptor out of the box making things easy. We don't have to worry about saving the token or writing the interceptor on our own.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { AuthHttpInterceptor } <span class="hljs-keyword">from</span> <span class="hljs-string">'@auth0/auth0-angular'</span>;
<span class="hljs-meta">@NgModule</span>({
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthHttpInterceptor,
      multi: <span class="hljs-literal">true</span>,
    },
  ],
  bootstrap: [AppComponent],
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppModule {}
</code></pre>
<p>Ref <code>app.module.ts</code>  <a target="_blank" href="https://github.com/adisreyaj/compito/blob/main/apps/compito/src/app/app.module.ts">source code</a> </p>
<p>Now, Auth0 will automatically attach the token with API calls. 
This is all that we have to do in the front-end.</p>
<p>So If the configuration is done correctly, clicking on the login button should take you to the Auth0 login screen.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1629613649639/LnmKT1n0E.png" alt="Auth0 Login Screen" /></p>
<p>Ref: https://auth0.com/docs/quickstart/spa/angular</p>
<h3 id="heading-setting-up-back-end-and-auth0">Setting up back-end and Auth0</h3>
<p>When it comes to the back-end, we need some way to add users to Auth0 using an API. For that Auth0 provides a NodeJs SDK which is a great way to interact with Auth0 Management APIs.</p>
<p>Management APIs can be used to do a lot of stuff in Auth0 like:</p>
<ul>
<li>Create a new user</li>
<li>Update user</li>
<li>Change roles</li>
<li>and more</li>
</ul>
<p>Ref: <a target="_blank" href="https://auth0.com/docs/api/management/v2/">Auth0 Management APIs</a></p>
<p>Ref: <a target="_blank" href="https://github.com/auth0/node-auth0">Auth0 Node SDK</a> </p>
<p>For the SDK to work, we need to create a <code>Machine-to-Machine</code> or <code>Non-Interactive</code> application.</p>
<h4 id="heading-1-create-a-machine-to-machine-app">1. Create a Machine-To-Machine App</h4>
<p>Go to the Applications page in Auth0 and create a new application, this time select Machine to Machine applications.</p>
<p>Once done, copy the credentials and paste them into the <code>.env</code> file so that we can later access it.</p>
<h4 id="heading-2-create-a-new-api-in-auth0">2. Create a new API in Auth0</h4>
<p>Next up we need to create an API. Go to the API section and create a new API. The identifier that you provide is going to be the <code>audience</code> property for the application.
If you see in the front-end environment file, we have added this property. Make sure to add <code>AUTH0_AUDIENCE</code> in the <code>.env</code> file as well.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1629614824780/UYQ-VmFnI.png" alt="Auth0 new API" /></p>
<h4 id="heading-3-creating-an-auth-service">3. Creating an Auth Service</h4>
<p>The Auth service will provide us with an instance of the Auth0 Management APIs which can be used throughout our application.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { ManagementClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'auth0'</span>;
<span class="hljs-keyword">const</span> managmentClient = <span class="hljs-keyword">new</span> ManagementClient({
  domain: <span class="hljs-string">'{YOUR_ACCOUNT}.auth0.com'</span>,
  clientId: <span class="hljs-string">'{YOUR_NON_INTERACTIVE_CLIENT_ID}'</span>,
  clientSecret: <span class="hljs-string">'{YOUR_NON_INTERACTIVE_CLIENT_SECRET}'</span>
});
</code></pre>
<p>For the full code, view <code>auth.service.ts</code>  <a target="_blank" href="https://github.com/adisreyaj/compito/blob/main/apps/api/src/app/auth/auth.service.ts">source code</a> </p>
<h4 id="heading-3-validating-the-api-token">3. Validating the API token</h4>
<p>In NestJS, we can provide guards to validate the tokens. We can use <code>passport</code> to help us with the extraction and validation of the Bearer tokens.</p>
<p>We need to create a Passport Strategy for that:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { Injectable, UnauthorizedException } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
<span class="hljs-keyword">import</span> { PassportStrategy } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/passport'</span>;
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> dotenv <span class="hljs-keyword">from</span> <span class="hljs-string">'dotenv'</span>;
<span class="hljs-keyword">import</span> { passportJwtSecret } <span class="hljs-keyword">from</span> <span class="hljs-string">'jwks-rsa'</span>;
<span class="hljs-keyword">import</span> { ExtractJwt, Strategy } <span class="hljs-keyword">from</span> <span class="hljs-string">'passport-jwt'</span>;
<span class="hljs-keyword">import</span> { getUserDetails } <span class="hljs-keyword">from</span> <span class="hljs-string">'../core/utils/payload.util'</span>;

dotenv.config();

<span class="hljs-meta">@Injectable</span>()
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> JwtStrategy <span class="hljs-keyword">extends</span> PassportStrategy(Strategy) {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) {
    <span class="hljs-built_in">super</span>({
      secretOrKeyProvider: passportJwtSecret({
        cache: <span class="hljs-literal">true</span>,
        rateLimit: <span class="hljs-literal">true</span>,
        jwksRequestsPerMinute: <span class="hljs-number">5</span>,
        jwksUri: <span class="hljs-string">`<span class="hljs-subst">${process.env.AUTH0_ISSUER_URL}</span>.well-known/jwks.json`</span>,
      }),
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      audience: <span class="hljs-string">`<span class="hljs-subst">${process.env.AUTH0_AUDIENCE}</span>`</span>,
      issuer: <span class="hljs-string">`<span class="hljs-subst">${process.env.AUTH0_ISSUER_URL}</span>`</span>,
      algorithms: [<span class="hljs-string">'RS256'</span>],
    });
  }

  validate(payload: <span class="hljs-built_in">any</span>): <span class="hljs-built_in">any</span> {
    <span class="hljs-keyword">return</span> payload;
  }
}
</code></pre>
<p>Make sure you have added <code>AUTH0_AUDIENCE</code> and <code>AUTH0_ISSUER_URL</code> in the <code>.env</code> file.</p>
<h4 id="heading-4-register-the-strategy">4. Register the strategy</h4>
<p>We need to register this as the default strategy in passport.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { Module } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
<span class="hljs-keyword">import</span> { PassportModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/passport'</span>;
<span class="hljs-keyword">import</span> { AuthService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./auth.service'</span>;
<span class="hljs-keyword">import</span> { JwtStrategy } <span class="hljs-keyword">from</span> <span class="hljs-string">'./jwt.stategy'</span>;

<span class="hljs-meta">@Module</span>({
  imports: [PassportModule.register({ defaultStrategy: <span class="hljs-string">'jwt'</span> })],
  providers: [JwtStrategy, AuthService],
  <span class="hljs-built_in">exports</span>: [PassportModule, AuthService],
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AuthModule {}
</code></pre>
<p>Create an AuthModule and then define the default strategy. And then we import the <code>AuthModule</code> in the <code>app.module.ts</code>.</p>
<h4 id="heading-5-setting-up-the-auth-guard">5. Setting up the Auth Guard</h4>
<p>Once the <code>AuthModule</code> is imported, we can now provide the Auth Guard:</p>
<pre><code>pimport { Module } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
<span class="hljs-keyword">import</span> { APP_GUARD } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/core'</span>;
<span class="hljs-keyword">import</span> { AuthGuard } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/passport'</span>;

@Module({
  imports: [
    AuthModule,
  ],
  providers: [
    {
      provide: APP_GUARD,
      useClass: AuthGuard(<span class="hljs-string">'jwt'</span>),
    },
  ],
})
<span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AppModule</span> {}</span>
</code></pre><p>If you don't need any custom login in the Guard, directly import and use the <code>AuthGuard</code> from <code>@nestjs/passport</code>. Else you can create a  <a target="_blank" href="https://github.com/adisreyaj/compito/blob/main/apps/api/src/app/core/guards/auth.guard.ts">custom guard</a>  and use it as I did in this case.</p>
<p><strong>Note</strong>: Here we have enabled the guard globally. You can also enable  <a target="_blank" href="https://docs.nestjs.com/guards#binding-guards">guards per request</a> in NestJs.</p>
<p>So now when the Front-end sends the <code>Bearer token</code> to the Back-end, it's validated by the guard and if successful, the Details in the <code>Access Token</code> are available for us in the Request prop (<code>req.user</code>).</p>
<p>Ref:  https://auth0.com/blog/developing-a-secure-api-with-nestjs-getting-started/</p>
<h2 id="heading-multi-org-user-management">Multi-Org User Management</h2>
<p>Since the application is a multi-org application, each user can have access to different orgs. For each org, the user will have a different role. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1629616108372/ct1MNWoLL.png" alt="User-Role-Org" /></p>
<p>In the example above Tomy is an Admin for Org B while he is a Project Admin for Org A.</p>
<p>For the MVP, the initial way is to lock the user to one org on login. So If they want to access another org, they can log out of the current org and then log in to another one. In this way, a lot of complexity tied up to the RBAC can be removed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1629616974730/xFVWzonoJ.jpeg" alt="Login flow" /></p>
<p>This is the proposed user login flow.</p>
<ol>
<li>User logins to the system.</li>
<li>If the user is only part of one org, log in directly</li>
<li>Else redirect the user to the org selection page, where the user can select an org to log in to.</li>
<li>User selects and org, which completes the login.</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1629618759857/zUW-7Uc2C.png" alt="Org selection" /></p>
<h3 id="heading-implementing-the-login-flow-in-auth0">Implementing the Login flow in Auth0</h3>
<p>I thought this could be a problem in the architecture and might not be feasible. But to my surprise, all this was super easy thanks to  <a target="_blank" href="https://auth0.com/docs/actions">Auth0 Actions</a>.
Auth0 Actions are a game-changer. It opens up the door to a lot of extendability. The whole login flow that I mentioned above can be done using an action.</p>
<h4 id="heading-creating-an-action">Creating an action</h4>
<p>Go to Actions &gt; Custom and Create a new action. For the <code>Trigger</code>, we need to go with <code>Login / Post Login</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1629617500845/zTUSah-VU.png" alt="Auth0 Action" /></p>
<p>Once you click on the action, we can write custom JavaScript to do a lot of stuff. Our main aim here is to check and redirect the user to an Org Selection landing page if the user is part of multiple orgs.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1629617667231/5oRYyJM2S.png" alt="Auth0 Action Page" /></p>
<p>Also once the user is logged in, we need to add some metadata into the access token so that we can easily access them and don't have to hit the DB for every request.
Here is the information that we need to add to the tokens.</p>
<ul>
<li><code>User Id</code> - user id associated with the logged-in user</li>
<li><code>Email</code> - logged in user's email</li>
<li><code>Org Id</code> - the current logged in org id</li>
<li><code>Role Details</code> - the role of the user for the current org</li>
</ul>
<h4 id="heading-setup-the-action">Setup the action</h4>
<p>We need to set few environment variables in the action. Then add <code>axios</code> as a dependency for the action.</p>
<ol>
<li>Once the user is logged in, we call our API to fetch the details of the user. For validating pre-auth request, inside actions we can create <code>sessions_token</code> which can be validated in the back-end. The session token <code>payload</code> can be used to transport any information to the back-end.<pre><code class="lang-js"><span class="hljs-keyword">const</span> token = api.redirect.encodeToken({
   <span class="hljs-attr">secret</span>: event.secrets.TOKEN_SECRET,
   <span class="hljs-attr">payload</span>: {
     <span class="hljs-attr">email</span>: event.user.email,
     <span class="hljs-attr">userId</span>: event.user.user_metadata.userId,
   },
 });
 <span class="hljs-keyword">const</span> { status, data } = <span class="hljs-keyword">await</span> axios.get(<span class="hljs-string">`<span class="hljs-subst">${event.secrets.API_URL}</span>/users/auth`</span>, {
   <span class="hljs-attr">headers</span>: {
     <span class="hljs-string">'x-session-token'</span>: token,
   },
 });
</code></pre>
</li>
<li><p>If the user is part of multiple orgs, we redirect the user to the org selection page:</p>
<pre><code class="lang-js"> <span class="hljs-keyword">if</span> (partOfMultipleOrgs || pendingInvites) {
   <span class="hljs-keyword">return</span> api.redirect.sendUserTo(event.secrets.ORG_SELECT_REDIRECT, {
     <span class="hljs-attr">query</span>: { <span class="hljs-attr">session_token</span>: token },
   });
 }
</code></pre>
<p>The <code>session_token</code> is passed as a query so that once in the org selection page, we can use the same to get the org details from the Back-end.</p>
</li>
<li><p>Else if the user is part of only one org, we proceed with attaching custom claims to the tokens:</p>
<pre><code class="lang-js">api.accessToken.setCustomClaim(<span class="hljs-string">`<span class="hljs-subst">${namespace}</span>/role`</span>, role);
api.idToken.setCustomClaim(<span class="hljs-string">`<span class="hljs-subst">${namespace}</span>/role`</span>, role);
</code></pre>
<p>It's that simple, now our tokens will have the role information.
The user gets redirected to the org selection page. We get the <code>session_token</code> in the front-end and make an API call to get the user's orgs and pending invites. When the user clicks on the org, we call the <code>/continue</code> API from the front-end which will resume the action.</p>
<pre><code class="lang-ts">loginToOrg(orgId: <span class="hljs-built_in">string</span>) {
 <span class="hljs-built_in">window</span>.location.href = <span class="hljs-string">`https://<span class="hljs-subst">${<span class="hljs-built_in">this</span>.environment.auth.domain}</span>/continue?state=<span class="hljs-subst">${<span class="hljs-built_in">this</span>.state}</span>&amp;orgId=<span class="hljs-subst">${orgId}</span>`</span>;
}
</code></pre>
</li>
</ol>
<p>Ref:  <a target="_blank" href="https://github.com/adisreyaj/compito/blob/bb06389d5917ea4d51aa8f4d0c33c859d83612df/libs/web/auth/src/lib/login/org-selection/org-selection.component.ts#L89">Front-end API Call</a> </p>
<pre><code class="lang-js"><span class="hljs-built_in">exports</span>.onContinuePostLogin = <span class="hljs-keyword">async</span> (event, api) =&gt; {}
</code></pre>
<p>We then attach the role and other info to the token. Here is the full action code:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> axios = <span class="hljs-built_in">require</span>(<span class="hljs-string">'axios'</span>); <span class="hljs-comment">//&lt;-- require axios for making api calls</span>

<span class="hljs-built_in">exports</span>.onExecutePostLogin = <span class="hljs-keyword">async</span> (event, api) =&gt; {
  <span class="hljs-keyword">const</span> userId = event.user.user_metadata.userId; <span class="hljs-comment">// &lt;-- Get user Id</span>
  <span class="hljs-keyword">if</span> (!userId) {
    <span class="hljs-keyword">return</span> api.access.deny(<span class="hljs-string">`Access denied`</span>);
  }
  <span class="hljs-keyword">try</span> {
    <span class="hljs-comment">// Create the session token</span>
    <span class="hljs-keyword">const</span> token = api.redirect.encodeToken({
      <span class="hljs-attr">secret</span>: event.secrets.TOKEN_SECRET,
      <span class="hljs-attr">payload</span>: {
        <span class="hljs-attr">email</span>: event.user.email,
        <span class="hljs-attr">userId</span>: event.user.user_metadata.userId,
      },
    });

    <span class="hljs-comment">// Get user details from the back-end</span>
    <span class="hljs-keyword">const</span> { status, data } = <span class="hljs-keyword">await</span> axios.get(<span class="hljs-string">`<span class="hljs-subst">${event.secrets.API_URL}</span>/users/auth`</span>, {
      <span class="hljs-attr">headers</span>: {
        <span class="hljs-string">'x-session-token'</span>: token,
      },
    });
    <span class="hljs-keyword">if</span> (status !== <span class="hljs-number">200</span>) {
      <span class="hljs-keyword">return</span> api.access.deny(<span class="hljs-string">`Something went wrong!`</span>);
    }

    <span class="hljs-keyword">const</span> { partOfMultipleOrgs, pendingInvites, orgs, roles, projects } = data;
    <span class="hljs-comment">// If the user is part of multiple orgs, redirect to org selection page</span>
    <span class="hljs-keyword">if</span> (partOfMultipleOrgs || pendingInvites) {
      <span class="hljs-keyword">return</span> api.redirect.sendUserTo(event.secrets.ORG_SELECT_REDIRECT, {
        <span class="hljs-attr">query</span>: { <span class="hljs-attr">session_token</span>: token },
      });
    }
    <span class="hljs-keyword">const</span> namespace = event.secrets.CLAIM_NAMESPACE;
    <span class="hljs-comment">// Add extra data to tokens as custom claims</span>
    <span class="hljs-keyword">if</span> (event.authorization) {
      <span class="hljs-keyword">const</span> org = orgs[<span class="hljs-number">0</span>];
      <span class="hljs-keyword">const</span> role = roles[org.id];
      api.accessToken.setCustomClaim(<span class="hljs-string">`<span class="hljs-subst">${namespace}</span>/role`</span>, role);
      api.accessToken.setCustomClaim(<span class="hljs-string">`<span class="hljs-subst">${namespace}</span>/projects`</span>, projects);
      api.accessToken.setCustomClaim(<span class="hljs-string">`<span class="hljs-subst">${namespace}</span>/org`</span>, org);
      api.accessToken.setCustomClaim(<span class="hljs-string">`<span class="hljs-subst">${namespace}</span>/userId`</span>, userId);
      api.accessToken.setCustomClaim(<span class="hljs-string">`<span class="hljs-subst">${namespace}</span>/email`</span>, event.user.email);
      api.idToken.setCustomClaim(<span class="hljs-string">`<span class="hljs-subst">${namespace}</span>/role`</span>, role);
      api.idToken.setCustomClaim(<span class="hljs-string">`<span class="hljs-subst">${namespace}</span>/projects`</span>, projects);
      api.idToken.setCustomClaim(<span class="hljs-string">`<span class="hljs-subst">${namespace}</span>/org`</span>, org);
      api.idToken.setCustomClaim(<span class="hljs-string">`<span class="hljs-subst">${namespace}</span>/userId`</span>, userId);
    }
  } <span class="hljs-keyword">catch</span> (e) {
    <span class="hljs-keyword">return</span> api.access.deny(<span class="hljs-string">`Something went wrong`</span>);
  }
};


<span class="hljs-comment">// Once the user has selected the org in UI, we will do the same thing as above</span>
<span class="hljs-built_in">exports</span>.onContinuePostLogin = <span class="hljs-keyword">async</span> (event, api) =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> userId = event.user.user_metadata.userId;
    <span class="hljs-keyword">const</span> orgId = event.request.query.orgId;
    <span class="hljs-keyword">if</span> (!orgId) {
      <span class="hljs-keyword">return</span> api.access.deny(<span class="hljs-string">`Something went wrong`</span>);
    }
    <span class="hljs-keyword">const</span> token = api.redirect.encodeToken({
      <span class="hljs-attr">secret</span>: event.secrets.TOKEN_SECRET,
      <span class="hljs-attr">payload</span>: {
        <span class="hljs-attr">email</span>: event.user.email,
        <span class="hljs-attr">org</span>: orgId,
        <span class="hljs-attr">userId</span>: event.user.user_metadata.userId,
      },
    });
    <span class="hljs-keyword">const</span> { status, data } = <span class="hljs-keyword">await</span> axios.get(<span class="hljs-string">`<span class="hljs-subst">${event.secrets.API_URL}</span>/users/auth`</span>, {
      <span class="hljs-attr">headers</span>: {
        <span class="hljs-string">'x-session-token'</span>: token,
      },
    });
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Project Response Status'</span>, status);
    <span class="hljs-keyword">if</span> (status !== <span class="hljs-number">200</span> || data == <span class="hljs-literal">null</span>) {
      <span class="hljs-keyword">return</span> api.access.deny(<span class="hljs-string">`Something went wrong!`</span>);
    }
    <span class="hljs-keyword">const</span> { org, role, projects } = data;
    <span class="hljs-keyword">const</span> namespace = event.secrets.CLAIM_NAMESPACE;
    <span class="hljs-keyword">if</span> (event.authorization) {
      api.accessToken.setCustomClaim(<span class="hljs-string">`<span class="hljs-subst">${namespace}</span>/role`</span>, role);
      api.accessToken.setCustomClaim(<span class="hljs-string">`<span class="hljs-subst">${namespace}</span>/projects`</span>, projects);
      api.accessToken.setCustomClaim(<span class="hljs-string">`<span class="hljs-subst">${namespace}</span>/org`</span>, org);
      api.accessToken.setCustomClaim(<span class="hljs-string">`<span class="hljs-subst">${namespace}</span>/userId`</span>, userId);
      api.accessToken.setCustomClaim(<span class="hljs-string">`<span class="hljs-subst">${namespace}</span>/email`</span>, event.user.email);
      api.idToken.setCustomClaim(<span class="hljs-string">`<span class="hljs-subst">${namespace}</span>/role`</span>, role);
      api.idToken.setCustomClaim(<span class="hljs-string">`<span class="hljs-subst">${namespace}</span>/projects`</span>, projects);
      api.idToken.setCustomClaim(<span class="hljs-string">`<span class="hljs-subst">${namespace}</span>/org`</span>, org);
      api.idToken.setCustomClaim(<span class="hljs-string">`<span class="hljs-subst">${namespace}</span>/userId`</span>, userId);
    }
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-keyword">return</span> api.access.deny(<span class="hljs-string">`Something went wrong`</span>);
  }
};
</code></pre>
<p>Ref:  <a target="_blank" href="https://github.com/adisreyaj/compito/blob/main/auth0/actions/add-role.js">Aut0 Action source code</a>
Note: If you are planning to copy the actions code, copy it from the source code.</p>
<p>Once the action is ready and all the environment variables are configured properly, <strong>Deploy</strong> the action.</p>
<h4 id="heading-implementing-the-action">Implementing the action</h4>
<p>Head to <strong>Actions</strong> &gt; <strong>Flows</strong> and Choose Login Flow.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1629619085367/CE0p5f2MX.png" alt="Login Flow" /></p>
<p>Now we can add the action to our login flow by just dragging and dropping the action in the flow.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1629619125581/pn43PGIfY.png" alt="Add action to login flow" /></p>
<p>Once you apply the flow, we are all set. See the outcome below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1630165724140/2K2JdjO4V.gif" alt="User onboarding" /></p>
<h2 id="heading-setting-up-roles-and-permissions-in-back-end">Setting up Roles and Permissions in Back-end</h2>
<p>In the back-end we need to set two guards:</p>
<ul>
<li>Role Guard - to check if the user has the required role to perform the operation</li>
<li>Permission Guard - to check if the user has required permission to perform the operation</li>
</ul>
<p>Role Points:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> ROLE_LEVEL = {
  <span class="hljs-string">'user'</span>: <span class="hljs-number">1</span>,
  <span class="hljs-string">'project-admin'</span>: <span class="hljs-number">2</span>,
  <span class="hljs-string">'org-admin'</span>: <span class="hljs-number">3</span>,
  <span class="hljs-string">'admin'</span>: <span class="hljs-number">4</span>,
  <span class="hljs-string">'super-admin'</span>: <span class="hljs-number">5</span>,
};
</code></pre>
<p>User has the lowest level of access or is the most restrictive role we can say.</p>
<p>Ref:  <a target="_blank" href="https://github.com/adisreyaj/compito/blob/main/apps/api/src/app/core/guards/roles.guard.ts">Role guard source code</a> </p>
<p>Ref:  <a target="_blank" href="https://github.com/adisreyaj/compito/blob/main/apps/api/src/app/core/guards/permissions.guard.ts">Permission guard source code</a> </p>
<p>I'll write separate articles about them.</p>
<p>This is how we would use these guards:</p>
<pre><code class="lang-ts">  <span class="hljs-meta">@UseGuards</span>(RolesGuard, PermissionsGuard) <span class="hljs-comment">// &lt;-- apply guards</span>
  <span class="hljs-meta">@Role</span>(<span class="hljs-string">'org-admin'</span>) <span class="hljs-comment">// &lt;-- set min required role</span>
  <span class="hljs-meta">@Permissions</span>(PERMISSIONS.project.create) <span class="hljs-comment">// &lt;-- permission required</span>
  <span class="hljs-meta">@Post</span>()
  create(<span class="hljs-meta">@Body</span>() project: ProjectRequest, <span class="hljs-meta">@Req</span>() req: RequestWithUser) {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.projectService.create(project, req.user);
  }
</code></pre>
<h2 id="heading-handling-role-and-permission-in-ui">Handling Role and Permission in UI</h2>
<p>In the UI, the user should only be seeing actions that they have access to. So for example a <code>Project Admin</code> should not be able to see the <code>Edit</code> option in the Org card.</p>
<p>For implementing this, I created a simple directive that does the job.</p>
<pre><code class="lang-ts"><span class="hljs-meta">@Directive</span>({
  selector: <span class="hljs-string">'[permission]'</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> PermissionsDirective <span class="hljs-keyword">implements</span> OnInit, OnDestroy {
  <span class="hljs-keyword">private</span> loggedInUser!: UserDetails;
  <span class="hljs-keyword">private</span> requiredPermission!: <span class="hljs-built_in">string</span>;

  subscription!: Subscription;

  <span class="hljs-meta">@Input</span>()
  set permission(permission: <span class="hljs-built_in">string</span>) {
    <span class="hljs-built_in">this</span>.requiredPermission = permission;
    <span class="hljs-built_in">this</span>.updateView();
  }
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">
    <span class="hljs-keyword">private</span> tpl: TemplateRef&lt;<span class="hljs-built_in">any</span>&gt;,
    <span class="hljs-keyword">private</span> vcr: ViewContainerRef,
    <span class="hljs-keyword">private</span> authService: AuthService,
    <span class="hljs-keyword">private</span> cdr: ChangeDetectorRef,
  </span>) {}

  ngOnInit() {
    <span class="hljs-built_in">this</span>.subscription = <span class="hljs-built_in">this</span>.authService.user$.pipe(formatUser()).subscribe(<span class="hljs-function">(<span class="hljs-params">user</span>) =&gt;</span> {
      <span class="hljs-keyword">if</span> (user) {
        <span class="hljs-built_in">this</span>.vcr.clear();
        <span class="hljs-built_in">this</span>.loggedInUser = user;
        <span class="hljs-built_in">this</span>.updateView();
      }
    });
  }

  ngOnDestroy() {
    <span class="hljs-built_in">this</span>.subscription.unsubscribe();
  }

  <span class="hljs-keyword">private</span> updateView() {
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.hasPermission()) {
      <span class="hljs-built_in">this</span>.vcr.createEmbeddedView(<span class="hljs-built_in">this</span>.tpl);
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">this</span>.vcr.clear();
    }
    <span class="hljs-built_in">this</span>.cdr.markForCheck();
  }

  <span class="hljs-keyword">private</span> hasPermission() {
    <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">this</span>.loggedInUser) <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
    <span class="hljs-keyword">const</span> userPermissions = <span class="hljs-built_in">this</span>.loggedInUser.role.permissions;
    <span class="hljs-keyword">if</span> (userPermissions) {
      <span class="hljs-keyword">return</span> userPermissions.includes(<span class="hljs-built_in">this</span>.requiredPermission);
    }
    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
  }
}
</code></pre>
<p>The directive will only render the component if the user has access to the item.</p>
<h2 id="heading-attachments-and-object-storage">Attachments and Object storage</h2>
<p>For saving and managing the files, I've used Oracle Cloud which provides object storage under its  <a target="_blank" href="https://www.oracle.com/in/cloud/free/">free tier</a> . For managing the uploads, <code>minio</code> (https://docs.min.io/docs/minio-client-complete-guide.html) client is used for interacting with the S3 compatible APIs of Oracle Cloud.</p>
<pre><code class="lang-ts"><span class="hljs-meta">@Injectable</span>()
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> FileStorageService {
  <span class="hljs-keyword">private</span> client: Client;
  <span class="hljs-keyword">private</span> logger = <span class="hljs-keyword">new</span> CompitoLogger(<span class="hljs-string">'FILE_STORAGE'</span>);
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> config: ConfigService</span>) {
    <span class="hljs-built_in">this</span>.client = <span class="hljs-built_in">this</span>.createMinioInstance();
  }

  <span class="hljs-keyword">private</span> createMinioInstance(): Client {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Client({
      endPoint: <span class="hljs-built_in">this</span>.config.get(<span class="hljs-string">'FILE_STORAGE_URI'</span>),
      useSSL: <span class="hljs-literal">true</span>,
      pathStyle: <span class="hljs-literal">true</span>,
      region: <span class="hljs-string">'ap-mumbai-1'</span>,
      accessKey: <span class="hljs-built_in">this</span>.config.get(<span class="hljs-string">'FILE_STORAGE_ACCESS_KEY'</span>),
      secretKey: <span class="hljs-built_in">this</span>.config.get(<span class="hljs-string">'FILE_STORAGE_ACCESS_SECRET'</span>),
    });
  }

  <span class="hljs-keyword">async</span> get(path: <span class="hljs-built_in">string</span>) {
    <span class="hljs-keyword">const</span> bucket = <span class="hljs-built_in">this</span>.config.get(<span class="hljs-string">'BUCKET'</span>);
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.client.getObject(bucket, path);
  }

  <span class="hljs-keyword">async</span> upload(file: Express.Multer.File, name: <span class="hljs-built_in">string</span>, folder: <span class="hljs-built_in">string</span>) {
    <span class="hljs-keyword">const</span> bucket = <span class="hljs-built_in">this</span>.config.get(<span class="hljs-string">'BUCKET'</span>);
    <span class="hljs-keyword">const</span> fileName = <span class="hljs-string">`<span class="hljs-subst">${folder}</span>/<span class="hljs-subst">${name}</span>.<span class="hljs-subst">${extension(file.mimetype)}</span>`</span>;
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.client.putObject(bucket, fileName, file.buffer);
      <span class="hljs-keyword">return</span> { result, filePath: fileName };
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-built_in">this</span>.logger.error(<span class="hljs-string">'upload'</span>, <span class="hljs-string">'Failed to upload'</span>, error);
      <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
    }
  }

  <span class="hljs-keyword">async</span> <span class="hljs-keyword">delete</span>(path: <span class="hljs-built_in">string</span>) {
    <span class="hljs-keyword">const</span> bucket = <span class="hljs-built_in">this</span>.config.get(<span class="hljs-string">'BUCKET'</span>);
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.client.removeObject(bucket, path);
      <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-built_in">this</span>.logger.error(<span class="hljs-string">'delete'</span>, <span class="hljs-string">'Failed to delete object'</span>, error);
      <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
    }
  }
}
</code></pre>
<p>Full <a target="_blank" href="https://github.com/adisreyaj/compito/blob/main/apps/api/src/app/core/services/file-storage.service.ts">source code</a> </p>
<h2 id="heading-roadmap">Roadmap</h2>
<ul>
<li>Task Dates and Time tracking</li>
<li>Calendar view</li>
<li>Activity Bar</li>
<li>Notifications (Email and Push)</li>
<li>Audit Trail Logs</li>
<li>Easy Org Switching</li>
<li>Tags</li>
<li>Sub Tasks</li>
</ul>
<p>I am planning to write about individual features and API in-depth as it would be very hard to include all in this one blog post. So it makes sense to maybe make a series on the same in the future. Till then, feel free to hit me up If you have any questions or doubts.</p>
<h2 id="heading-best-practices">Best practices</h2>
<h4 id="heading-validation">Validation</h4>
<p>The server uses  <a target="_blank" href="https://github.com/sideway/joi"><strong>joi</strong></a>  for validating the request even before reaching the services. There is also a custom logger implemented to give a clear idea of the errors. The custom logger can be used to connect to log monitoring services like Sentry in the future.</p>
<h4 id="heading-error-handling">Error handling</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1630085333514/UxgOHrjeO.png" alt="Custom Error Messages" /></p>
<p>Error handling is very important in any project and so is logging. With proper logs in place, it's much easier to sport the bugs. The errors are streamed by pm2 and are visible via the PM2 Dashboard.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1630255964921/sqmT9Yq4j.png" alt="Sentry error management" />
Errors are also sent to Sentry for easy RCA. Sentry has a good integration and setting up was not that difficult. Will try to write about it in a separate blog.</p>
<h4 id="heading-database-backups">Database backups</h4>
<p>Database backups are created locally on a daily basis and uploaded to object storage in the cloud. More details about the setup here:  <a target="_blank" href="https://blog.sreyaj.dev/how-to-backup-postgres-data-to-s3-bucket-using-minio">How to backup Postgres data to object storage</a>.</p>
<h4 id="heading-security-and-others">Security and Others</h4>
<p>When it comes to API Security, there is CORS, Rate limiting, etc implemented. Thanks to middleware like</p>
<ul>
<li><a target="_blank" href="https://helmetjs.github.io/">Helmet</a>  </li>
<li><a target="_blank" href="https://www.npmjs.com/package/express-rate-limit">Express Rate Limit</a> </li>
</ul>
<p>Since the API is proxied via Nginx, by setting up  <a target="_blank" href="https://github.com/google/brotli">brolti</a>  compression, the API payload can be compressed very well making APIs faster.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1630086049506/aiOoprtrR.png" alt="Brotli Compression" /></p>
<p>I am pretty much satisfied with the outcome of the project given I was able to put all this together in under 3 weeks of my free time. Code quality is checked by SonarQube. There are few smells, which can be easily cleaned up.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1629744896624/TuORh-_ZA.png" alt="Sonarqube result" /></p>
<h2 id="heading-lighthouse-scores">Lighthouse Scores</h2>
<p>The initial lighthouse scores without much optimizations done are pretty good I would say. The test is done on the production website. There are a lot of improvements in the application and I can see the scores improving more over time.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1630066974418/4N_3Y2qs6.png" alt="Lighthouse score" /></p>
<p>As always thanks to Vercel for making the websites super fast.</p>
<h2 id="heading-links">Links</h2>
<ul>
<li><a target="_blank" href="https://github.com/adisreyaj/compito">Github Repo</a> </li>
<li><a target="_blank" href="https://compito.adi.so/">Demo</a> </li>
</ul>
<h2 id="heading-update">Update</h2>
<p>Thanks all for the massive support. We've crossed <strong>100</strong> stars on Github, More than <strong>120</strong> users signed up in Auth0, and also around <strong>1.5K</strong> pageviews after the launch.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1630996920359/0LcL0loH7.png" alt="Github Stats" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1630997083694/JgcovCOAT.png" alt="Analytics" /></p>
<h2 id="heading-connect-with-me">Connect with me</h2>
<ul>
<li><a target="_blank" href="https://twitter.com/AdiSreyaj">Twitter</a></li>
<li><a target="_blank" href="https://github.com/adisreyaj">Github</a></li>
<li><a target="_blank" href="https://www.linkedin.com/in/adithyasreyaj/">Linkedin</a></li>
<li><a target="_blank" href="https://cardify.adi.so">Cardify</a> - Dynamic SVG Images for Github Readmes</li>
</ul>
<p>Do add your thoughts in the comments section.
Stay Safe ❤️</p>
<p><a target="_blank" href="https://www.buymeacoffee.com/adisreyaj"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639498527478/IA3aJ9R0J.png" alt="Buy me a pizza" /></a></p>
]]></content:encoded></item><item><title><![CDATA[Creating custom form controls using Control Value Accessor in Angular]]></title><description><![CDATA[How to create custom form controls in Angular using ControlValueAccessor? We can create custom form components and connect them to either template-driven forms or reactive forms.
So when I say custom form controls, I am talking about those elements t...]]></description><link>https://sreyaj.dev/custom-form-controls-controlvalueaccessor-in-angular</link><guid isPermaLink="true">https://sreyaj.dev/custom-form-controls-controlvalueaccessor-in-angular</guid><category><![CDATA[Angular]]></category><category><![CDATA[Angular 2]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Adithya Sreyaj]]></dc:creator><pubDate>Mon, 12 Jul 2021 14:44:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1625989406113/1ikG9bwZJ.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>How to create custom form controls in Angular using ControlValueAccessor? We can create custom form components and connect them to either template-driven forms or reactive forms.</p>
<p>So when I say custom form controls, I am talking about those elements that are not your typical controls like input fields, radio buttons, or checkboxes. For example, a star rating component or a knob. These are not available out of the box.</p>
<p>We can also make sections in a form as child components which can be then used as custom form controls. In this way, larger forms can be broken down into manageable pieces. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625915624322/469cYvV-q.png" alt="Custom Form Controls using ControlValueAccessor" /></p>
<p>For all the default form controls like input field, radio button, checkbox, select dropdown, etc, custom control value accessors are already written and shipped with Angular. Eg:  <a target="_blank" href="https://angular.io/api/forms/CheckboxControlValueAccessor">CheckboxControlValueAccessor</a> </p>
<p>We will be talking more about <code>ControlValueAccessor</code> it and how to use it to create really cool form.</p>
<h2 id="heading-custom-form-elements">Custom Form Elements</h2>
<p>When we hear the term form, we would be thinking of few input text fields and maybe some checkboxes and stuff. But when it comes to really complex forms where we have a lot of custom buttons, lists, and selections, the whole form will become very complex. And managing such a complex form would be a problem.</p>
<p>When there are a lot of custom form elements or when the form starts to get big, it's probably a good idea to break it into smaller sections. Placing everything in a single template would make it really messy.</p>
<p>We can break down the form into multiple components and then connect it with the main form.</p>
<h2 id="heading-custom-form-control-in-angular">Custom form control in Angular</h2>
<p> <a target="_blank" href="https://angular.io/api/forms/ControlValueAccessor">ControlValueAccessor</a> is something that comes with Angular. It acts as a bridge between DOM elements and the angular Form API.</p>
<p>So If you have a custom element that you would like to connect to your form, you have to make use of ControlValueAccessor to make the element compatible with Angular Forms API. Doing so will enable the element to be connected using <code>ngModel</code> (Template Driven Forms) or <code>formControl</code> (Reactive Forms).</p>
<p>Let's take a look at how do we create a custom form control.</p>
<p>When I started with Angular, I was not aware that something like this existed. I remember when I wrote child components for forms and used <code>@Input()</code> and <code>@Output()</code> to receive and send form values to the parent form component. I used to listen to the changes in the child component and then emit the values to the parent.</p>
<p>In the parent, the values will be taken and used to patch the form. This was until I came across the magical ControlValueAccessor. No more inputs and outputs, everything just works. </p>
<h3 id="heading-implement-the-controlvalueaccessor-interface">Implement the ControlValueAccessor interface.</h3>
<p>Step 1 is to implement the interface in the custom component. The interface would ask us to add few methods in our class.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">interface</span> ControlValueAccessor {
  writeValue(obj: <span class="hljs-built_in">any</span>): <span class="hljs-built_in">void</span>
  registerOnChange(fn: <span class="hljs-built_in">any</span>): <span class="hljs-built_in">void</span>
  registerOnTouched(fn: <span class="hljs-built_in">any</span>): <span class="hljs-built_in">void</span>
  setDisabledState(isDisabled: <span class="hljs-built_in">boolean</span>)?: <span class="hljs-built_in">void</span>
}
</code></pre>
<p>Let us see what each of the methods is doing. Once we are clear on how things are, we can dive into the implementation.</p>
<ul>
<li><code>writeValue()</code> - this function is called by the Forms API to update the value of the element. When <code>ngModel</code> or <code>formControl</code> value changes, this function gets called and the latest value is passed in as the argument to the function. We can use the latest value and make changes in the component. (<a target="_blank" href="https://angular.io/api/forms/ControlValueAccessor#writevalue">ref</a>)</li>
<li><code>registerOnChange()</code> - we get access to a function in the argument that can be saved to a local variable. Then this function can be called when there are any changes in the value of our custom form control. (<a target="_blank" href="https://angular.io/api/forms/ControlValueAccessor#registerOnChange">ref</a>)</li>
<li><code>registerOnTouched()</code> - we get access to another function that can be used to update the state of the form to <code>touched</code>. So when the user interacts with our custom form element, we can call the saved function to let Angular know that the element has been interacted with. (<a target="_blank" href="https://angular.io/api/forms/ControlValueAccessor#registerOnTouched">ref</a>)</li>
<li><code>setDisabledState()</code> - this function will be called by the forms API when the disabled state is changed. We can get the current state and update the state of the custom form control. (<a target="_blank" href="https://angular.io/api/forms/ControlValueAccessor#setDisabledState">ref</a>)</li>
</ul>
<p>Once we implement these functions, the next step is to provide the <code>NG_VALUE_ACCESSOR</code> token in the component's providers array like so:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> COUNTRY_CONTROL_VALUE_ACCESSOR: Provider = {
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(<span class="hljs-function">() =&gt;</span> CustomFormControlComponent),
  multi: <span class="hljs-literal">true</span>,
};

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-country-selector'</span>,
  template: <span class="hljs-string">``</span>,
  providers: [COUNTRY_CONTROL_VALUE_ACCESSOR], <span class="hljs-comment">// &lt;-- provided here</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> CustomFormControlComponent <span class="hljs-keyword">implements</span> ControlValueAccessor {}
</code></pre>
<p><strong>Note</strong>: Here I created a provider constant and then passed it into the <code>providers</code>. Also you can see the use of <code>forwardRef</code> (<a target="_blank" href="https://angular.io/api/core/forwardRef">ref</a>) here. It is needed because we are referring to the <code>CountrySelectorComponent</code> class which is not defined before its reference.</p>
<p>So now that we know what each of these functions does, we can start implementing our custom form element.</p>
<h2 id="heading-basic-form">Basic Form</h2>
<p>We are going to take a look at the base form that we are gonna work with. We just have some basic input fields and 2 custom form elements.</p>
<pre><code class="lang-json">{
    name: 'Adithya',
    github: 'https:<span class="hljs-comment">//github.com/AdiSreyaj',</span>
    website: 'https:<span class="hljs-comment">//adi.so',</span>
    server: 'IN',
    communications: [{
          label: 'Marketing',
          modes: [{
              name: 'Email',
              enabled: <span class="hljs-literal">true</span>,
            },
            {
              name: 'SMS',
              enabled: <span class="hljs-literal">false</span>,
            }],
        },
        {
          label: 'Product Updates',
          modes: [{
              name: 'Email',
              enabled: <span class="hljs-literal">true</span>,
            },
            {
              name: 'SMS',
              enabled: <span class="hljs-literal">true</span>,
            }],
        },
      ]
  }
</code></pre>
<p>This is how we need the data to be. Here the <code>server</code> and the <code>communications</code> fields are going to be connected to a custom form control. We are using  <a target="_blank" href="https://angular.io/guide/reactive-forms">Reactive Forms</a>  in the example.</p>
<p>Here is how our form will look like: </p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> form = <span class="hljs-built_in">this</span>.fb.group({
    name: [<span class="hljs-string">''</span>],
    github: [<span class="hljs-string">''</span>],
    website: [<span class="hljs-string">''</span>],
    server: [<span class="hljs-string">''</span>],
    communications: [[]]
  });
</code></pre>
<p>and in the template</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> [<span class="hljs-attr">formGroup</span>]=<span class="hljs-string">"form"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-group"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"name"</span>&gt;</span>Name<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"name"</span> <span class="hljs-attr">formControlName</span>=<span class="hljs-string">"name"</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-group"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"github"</span>&gt;</span>Github<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"url"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"github"</span> <span class="hljs-attr">formControlName</span>=<span class="hljs-string">"github"</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-group"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"website"</span>&gt;</span>Website<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"url"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"website"</span> <span class="hljs-attr">formControlName</span>=<span class="hljs-string">"website"</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-group"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span>Region<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">app-country-selector</span> <span class="hljs-attr">formControlName</span>=<span class="hljs-string">"server"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">app-country-selector</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-group"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span>Communication<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">app-communication-preference</span> <span class="hljs-attr">formControlName</span>=<span class="hljs-string">"communications"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">app-communication-preference</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
</code></pre>
<p>Notice in the above template we are directly using <code>formControlName</code> on the <code>app-country-selector</code> and <code>app-communication-preference</code> components. This will be only possible if those components are implementing the <code>ControlValueAccessor</code> interface. This is how you make a component behave like a form control.</p>
<h2 id="heading-country-selector-custom-form-control">Country Selector custom form control</h2>
<p>We are going to see how to implement a cool country selector component as a custom form control that can be directly connected to a form. In this example, I'll be using Reactive Forms.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625984816808/1lhdZ5lBP.png" alt="Country Selector Custom Form Control" /></p>
<p>The component is pretty straightforward, we will give the user to select one country from a given list. The behavior is similar to a radio button. The only difference here is that we are using our own custom component to implement this design.</p>
<p>As always, I start by creating a new module and component for our country selector form control.</p>
<p>Here is how we implement the ControlValueAccessor for our country selector component.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> COUNTRY_CONTROL_VALUE_ACCESSOR: Provider = {
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(<span class="hljs-function">() =&gt;</span> CountrySelectorComponent),
  multi: <span class="hljs-literal">true</span>,
};
</code></pre>
<p>We provide it in the providers array inside the <code>@Component</code> decorator.</p>
<pre><code class="lang-ts"><span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-country-selector'</span>,
  template: <span class="hljs-string">`
    &lt;div&gt;
      &lt;ng-container *ngFor="let country of countries"&gt;
        &lt;button [disabled]="disabled" (click)="selectCountry(country.code)"
             [class.selected]="!disabled &amp;&amp; selected === country.code"&gt;
          &lt;ng-container *ngIf="!disabled &amp;&amp; selected === country.code"&gt;
            &lt;!-- Checkmark Icon --&gt;
          &lt;/ng-container&gt;
          &lt;img [src]="...flag src" [alt]="country.name" /&gt;
          &lt;p&gt;{{ country?.name }}&lt;/p&gt;
        &lt;/button&gt;
      &lt;/ng-container&gt;
    &lt;/div&gt;
  `</span>,
  providers: [COUNTRY_CONTROL_VALUE_ACCESSOR],
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> CountrySelectorComponent <span class="hljs-keyword">implements</span> ControlValueAccessor {
  countries = [
    { code: <span class="hljs-string">'IN'</span>, name: <span class="hljs-string">'India'</span> },
    { code: <span class="hljs-string">'US'</span>, name: <span class="hljs-string">'United States'</span> },
    { code: <span class="hljs-string">'GB-ENG'</span>, name: <span class="hljs-string">'England'</span> },
    { code: <span class="hljs-string">'NL'</span>, name: <span class="hljs-string">'Netherlands'</span> },
  ];
  selected!: <span class="hljs-built_in">string</span>;
  disabled = <span class="hljs-literal">false</span>;
  <span class="hljs-keyword">private</span> onTouched!: <span class="hljs-built_in">Function</span>;
  <span class="hljs-keyword">private</span> onChanged!: <span class="hljs-built_in">Function</span>;

  selectCountry(code: <span class="hljs-built_in">string</span>) {
    <span class="hljs-built_in">this</span>.onTouched(); <span class="hljs-comment">// &lt;-- mark as touched</span>
    <span class="hljs-built_in">this</span>.selected = code;
    <span class="hljs-built_in">this</span>.onChanged(code); <span class="hljs-comment">// &lt;-- call function to let know of a change</span>
  }

  writeValue(value: <span class="hljs-built_in">string</span>): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.selected = value ?? <span class="hljs-string">'IN'</span>;
  }
  registerOnChange(fn: <span class="hljs-built_in">any</span>): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.onChanged = fn; <span class="hljs-comment">// &lt;-- save the function</span>
  }
  registerOnTouched(fn: <span class="hljs-built_in">any</span>): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.onTouched = fn; <span class="hljs-comment">// &lt;-- save the function</span>
  }
  setDisabledState(isDisabled: <span class="hljs-built_in">boolean</span>) {
    <span class="hljs-built_in">this</span>.disabled = isDisabled;
  }
}
</code></pre>
<p>If the user has given an initial value to <code>server</code> in the form, we will get the initial value in the <code>writeValue()</code> method. We get the value and assign it to our local variable <code>selected</code> which manages the state.</p>
<p>When the user clicks on a different country, we mark the field as <code>touched</code> and then assign the value to the <code>selected</code> variable. The main part is we also call the <code>onChanged</code> method and pass the newly selected country code. This will set the new value as the form control's value.</p>
<p>By using the argument from <code>setDisabledState()</code> method we can implement the disabled state for our component. So If we trigger disable from the form using:</p>
<pre><code class="lang-ts"><span class="hljs-built_in">this</span>.form.get(<span class="hljs-string">'server'</span>).disable();
</code></pre>
<p>Doing the above will trigger a call to <code>setDisabledState()</code> method where the state <code>isDisabled</code> is passed, which is then assigned to a local variable <code>disabled</code>. Now we can use this local variable to add a class or disable the button.</p>
<pre><code class="lang-ts">setDisabledState(isDisabled: <span class="hljs-built_in">boolean</span>) {
    <span class="hljs-built_in">this</span>.disabled = isDisabled;
  }
</code></pre>
<p>That is all! We have successfully created a custom form control. Check the GitHub repo for  <a target="_blank" href="https://github.com/adisreyaj/ng-custom-form-elements/blob/main/src/app/components/country-selector/country-selector.component.ts">full code</a>.</p>
<h2 id="heading-communication-preferences-custom-form-control">Communication Preferences custom form control</h2>
<p>Now let's see how to implement the second custom form control in our form, which allows user to select their communication preferences.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625987263175/J7xXXrHQ_.png" alt="Communication preference custom form control" /></p>
<p>This is also a very simple component that has a bunch of checkboxes. We could have added this in the same parent component where the form is initialized. But by creating a separate component, we are making it more maintainable.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> COM_PREFERENCE_CONTROL_VALUE_ACCESSOR: Provider = {
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(<span class="hljs-function">() =&gt;</span> CommunicationPreferenceComponent),
  multi: <span class="hljs-literal">true</span>,
};

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-communication-preference'</span>,
  template: <span class="hljs-string">`&lt;div&gt;
    &lt;ul&gt;
      &lt;ng-container *ngFor="let item of options; index as i"&gt;
        &lt;li&gt;
          &lt;p&gt;{{ item?.label }}&lt;/p&gt;
          &lt;div&gt;
            &lt;ng-container *ngFor="let mode of item.modes; index as j"&gt;
              &lt;div&gt;
                &lt;input
                  type="checkbox"
                  [id]="item.label + mode.name"
                  [(ngModel)]="mode.enabled"
                  (ngModelChange)="handleChange(i, j, $event)" /&gt;
                &lt;label [for]="item.label + mode.name"&gt;{{ mode.name }}&lt;/label&gt;
              &lt;/div&gt;
            &lt;/ng-container&gt;
          &lt;/div&gt;
        &lt;/li&gt;
      &lt;/ng-container&gt;
    &lt;/ul&gt;
  &lt;/div&gt;`</span>,
  providers: [COM_PREFERENCE_CONTROL_VALUE_ACCESSOR],
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> CommunicationPreferenceComponent <span class="hljs-keyword">implements</span> ControlValueAccessor {
  options: CommunicationPreference[] = [];
  <span class="hljs-keyword">private</span> onTouched!: <span class="hljs-built_in">Function</span>;
  <span class="hljs-keyword">private</span> onChanged!: <span class="hljs-built_in">Function</span>;
  handleChange(itemIndex: <span class="hljs-built_in">number</span>, modeIndex: <span class="hljs-built_in">number</span>, change: <span class="hljs-built_in">any</span>) {
    <span class="hljs-built_in">this</span>.onTouched();
    <span class="hljs-built_in">this</span>.options[itemIndex].modes[modeIndex].enabled = change;
    <span class="hljs-built_in">this</span>.onChanged(<span class="hljs-built_in">this</span>.options);
  }

  writeValue(value: <span class="hljs-built_in">any</span>): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.options = value;
  }
  registerOnChange(fn: <span class="hljs-built_in">any</span>): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.onChanged = fn;
  }
  registerOnTouched(fn: <span class="hljs-built_in">any</span>): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.onTouched = fn;
  }
}
</code></pre>
<p>Again it's the same thing we are doing, we have an <code>options</code> variable that manages the local state of the component. When there is any value-change triggered by the form, we get the new value in the <code>writeValue</code> method, we update the local state with the changed value.
When the user makes any change, we update the local state and call the <code>onChanged</code> method and pass the updated state which updates the form as well.</p>
<p>Find the  <a target="_blank" href="https://github.com/adisreyaj/ng-custom-form-elements/blob/main/src/app/components/communication-preference/communication-preference.component.ts">complete code</a>  for the component in the repo.</p>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>Angular makes it really easy to implement custom form control using <code>ControlValueAccessor</code>. By implementing few methods, we can directly hook our component to a <code>Reactive</code> or <code>Template Driven</code> form with ease.</p>
<p>We can write all sorts of crazy form elements and use them without writing logic to handle communication between parent and child. Let the forms API do the magic for us.</p>
<p>We can also use this approach to break sections of the form into their own individual component. This way if the form is big/complex, we can break then down into smaller components that can be easily managed.</p>
<h2 id="heading-code-and-demo">Code and Demo</h2>
<iframe src="https://ng-custom-form-elements.vercel.app/" width="100%" height="800px"></iframe>


<ul>
<li><strong>Github</strong>: https://github.com/adisreyaj/ng-custom-form-elements</li>
<li><strong>Demo</strong>: https://ng-custom-form-elements.vercel.app/  </li>
</ul>
<h2 id="heading-connect-with-me">Connect with me</h2>
<ul>
<li><a target="_blank" href="https://twitter.com/AdiSreyaj">Twitter</a></li>
<li><a target="_blank" href="https://github.com/adisreyaj">Github</a></li>
<li><a target="_blank" href="https://www.linkedin.com/in/adithyasreyaj/">Linkedin</a></li>
<li><a target="_blank" href="https://cardify.adi.so">Cardify</a> - Dynamic SVG Images for Github Readmes</li>
</ul>
<p>Do add your thoughts in the comments section.
Stay Safe ❤️</p>
<p><a target="_blank" href="https://www.buymeacoffee.com/adisreyaj"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639498527478/IA3aJ9R0J.png" alt="Buy me a pizza" /></a></p>
]]></content:encoded></item><item><title><![CDATA[A simple but customizable accordion component in Angular]]></title><description><![CDATA[How to create a super simple accordion component in Angular. Using the power of content projection in Angular, we are going to build an accordion/expansion panel that can be customized in multiple ways.
Accordions
Accordions in general are vertically...]]></description><link>https://sreyaj.dev/customizable-accordion-component-angular</link><guid isPermaLink="true">https://sreyaj.dev/customizable-accordion-component-angular</guid><category><![CDATA[Angular]]></category><category><![CDATA[Angular 2]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Adithya Sreyaj]]></dc:creator><pubDate>Sat, 12 Jun 2021 06:18:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1623477714166/nynrOHXij.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>How to create a super simple accordion component in Angular. Using the power of content projection in Angular, we are going to build an accordion/expansion panel that can be customized in multiple ways.</p>
<h2 id="heading-accordions">Accordions</h2>
<p>Accordions in general are vertically stacked lists of headers when clicked reveals some content. Accordions or expandable sections are seen as part of almost all the UI libraries out there.</p>
<p> If we take a look at Angular material, we have <code>Expansion Panel</code> (<a target="_blank" href="https://material.angular.io/components/expansion/overview">ref</a>) and in Ng Bootstrap its called simple <code>Accordion</code> (<a target="_blank" href="https://ng-bootstrap.github.io/#/components/accordion/examples">ref</a>).</p>
<h2 id="heading-building-a-custom-accordion">Building a custom accordion</h2>
<p>What we are gonna build is a much simpler version of these. And as always, the whole idea behind this is to help you explore some possibilities of Angular. This blog post would be mostly about how we can use <code>Content Projection</code> (<a target="_blank" href="https://angular.io/guide/content-projection">ref</a>) in Angular to create reusable and customizable UI components.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1623437128898/xWRjwNSqQ.gif" alt="Accordion component in Angular" /></p>
<p>We are not going to rely on any 3rd party library for building our component. We are going to use <code>Directives</code>, <code>TemplateRef</code>, <code>Animation</code> etc in this post. </p>
<h2 id="heading-planning-it-out">Planning it out</h2>
<p>If we see the anatomy of an accordion component, we need a main parent container to accommodate all the different items inside. Each of the items will contain a header and a content part.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1623437875075/XwhQy8cGw.png" alt="Accordion Structure" />
The content part is completely dynamic and we don't have to worry about it. When it comes to the header, there will be:</p>
<ul>
<li>Default header with title and the toggle icon</li>
<li>Default header with custom title</li>
<li>Completely custom header</li>
</ul>
<h2 id="heading-building-accordion-component">Building Accordion component</h2>
<p>Let's start by creating a dedicated module for the component. Here are the items that we need to create along with the module:</p>
<ul>
<li>Accordion Parent Component</li>
<li>Accordion Item Directive</li>
<li>Accordion Header Directive</li>
<li>Accordion Title Directive</li>
<li>Accordion Content Directive</li>
</ul>
<pre><code class="lang-css"><span class="hljs-selector-tag">lib</span>/
├─ <span class="hljs-selector-tag">accordion</span>/
│  ├─ <span class="hljs-selector-tag">directives</span>/
│  │  ├─ <span class="hljs-selector-tag">accordion-item</span><span class="hljs-selector-class">.directive</span><span class="hljs-selector-class">.ts</span>
│  │  ├─ <span class="hljs-selector-tag">accordion-content</span><span class="hljs-selector-class">.directive</span><span class="hljs-selector-class">.ts</span>
│  │  ├─ <span class="hljs-selector-tag">accordion-title</span><span class="hljs-selector-class">.directive</span><span class="hljs-selector-class">.ts</span>
│  │  ├─ <span class="hljs-selector-tag">accordion-header</span><span class="hljs-selector-class">.directive</span><span class="hljs-selector-class">.ts</span>
│  ├─ <span class="hljs-selector-tag">accordion</span><span class="hljs-selector-class">.component</span><span class="hljs-selector-class">.html</span>
│  ├─ <span class="hljs-selector-tag">accordion</span><span class="hljs-selector-class">.component</span><span class="hljs-selector-class">.css</span>
│  ├─ <span class="hljs-selector-tag">accordion</span><span class="hljs-selector-class">.component</span><span class="hljs-selector-class">.ts</span>
│  ├─ <span class="hljs-selector-tag">accordion</span><span class="hljs-selector-class">.module</span><span class="hljs-selector-class">.ts</span>
</code></pre>
<p>Here's how the module will look like:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { CommonModule } <span class="hljs-keyword">from</span> <span class="hljs-string">"@angular/common"</span>;
<span class="hljs-keyword">import</span> { NgModule } <span class="hljs-keyword">from</span> <span class="hljs-string">"@angular/core"</span>;

<span class="hljs-keyword">import</span> { AccordionComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">"./accordion.component"</span>;
<span class="hljs-keyword">import</span> { AccordionItem } <span class="hljs-keyword">from</span> <span class="hljs-string">"./directives/accordion-item.directive"</span>;
<span class="hljs-keyword">import</span> { AccordionContent } <span class="hljs-keyword">from</span> <span class="hljs-string">"./directives/accordion-content.directive"</span>;
<span class="hljs-keyword">import</span> { AccordionTitle } <span class="hljs-keyword">from</span> <span class="hljs-string">"./directives/accordion-title.directive"</span>;
<span class="hljs-keyword">import</span> { AccordionHeader } <span class="hljs-keyword">from</span> <span class="hljs-string">"./directives/accordion-header.directive"</span>;

<span class="hljs-meta">@NgModule</span>({
  declarations: [
    AccordionComponent,
    AccordionItem,
    AccordionContent,
    AccordionTitle,
    AccordionHeader
  ],
  imports: [CommonModule],
  <span class="hljs-built_in">exports</span>: [
    AccordionComponent,
    AccordionItem,
    AccordionContent,
    AccordionTitle,
    AccordionHeader
  ]
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AccordionModule {}
</code></pre>
<p>We start with the directives first, as most of them will be used as selectors and will not have any logic inside them. We inject the <code>TemplateRef</code> so that we can get access to the template reference of the host elements where these directives are placed.</p>
<h4 id="heading-content-directive">Content Directive</h4>
<pre><code class="lang-ts"><span class="hljs-meta">@Directive</span>({
  selector: <span class="hljs-string">"[accordionContent]"</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AccordionContent {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">public</span> templateRef: TemplateRef&lt;<span class="hljs-built_in">any</span>&gt;</span>) {}
}
</code></pre>
<h4 id="heading-header-directive">Header Directive</h4>
<pre><code class="lang-ts"><span class="hljs-meta">@Directive</span>({
  selector: <span class="hljs-string">"[accordionHeader]"</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AccordionHeader {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">public</span> templateRef: TemplateRef&lt;<span class="hljs-built_in">any</span>&gt;</span>) {}
}
</code></pre>
<h4 id="heading-title-directive">Title Directive</h4>
<pre><code class="lang-ts"><span class="hljs-meta">@Directive</span>({
  selector: <span class="hljs-string">"[accordionTitle]"</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AccordionTitle {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">public</span> templateRef: TemplateRef&lt;<span class="hljs-built_in">any</span>&gt;</span>) {}
}
</code></pre>
<h4 id="heading-item-directive">Item Directive</h4>
<pre><code class="lang-ts"><span class="hljs-meta">@Directive</span>({
  selector: <span class="hljs-string">"accordion-item"</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AccordionItem {
  <span class="hljs-meta">@Input</span>() title = <span class="hljs-string">""</span>;
  <span class="hljs-meta">@Input</span>() disabled = <span class="hljs-literal">false</span>;
  <span class="hljs-meta">@ContentChild</span>(AccordionContent) content: AccordionContent;
  <span class="hljs-meta">@ContentChild</span>(AccordionTitle) customTitle: AccordionTitle;
  <span class="hljs-meta">@ContentChild</span>(AccordionHeader) customHeader: AccordionHeader;
}
</code></pre>
<p>In the item directive, we set some <code>@Input()</code> for getting data from the user. And We have taken the reference of the content, title, and the header using the <code>@ContentChild()</code> (<a target="_blank" href="https://angular.io/api/core/ContentChild">ref</a>) decorator.</p>
<p>This is how the title, content, and header will be passed in the template:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">accordion-item</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">ng-template</span> <span class="hljs-attr">accordionHeader</span>&gt;</span>ng-template&gt;
     <span class="hljs-tag">&lt;<span class="hljs-name">ng-template</span> <span class="hljs-attr">accordionTitle</span>&gt;</span>ng-template&gt;
     <span class="hljs-tag">&lt;<span class="hljs-name">ng-template</span> <span class="hljs-attr">accordionContent</span>&gt;</span>ng-template&gt;
<span class="hljs-tag">&lt;/<span class="hljs-name">accordion-item</span>&gt;</span>
</code></pre>
<p>Once we have the basic setup ready, the main component is the <code>AccordionComponent</code> or the parent component.</p>
<h4 id="heading-accordion-component">Accordion component</h4>
<p>We have to essentially manage only a single state for managing the expanded items.</p>
<pre><code class="lang-ts"><span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">"accordion"</span>,
  templateUrl: <span class="hljs-string">"./accordion.component.html"</span>,
  styleUrls: [<span class="hljs-string">"./accordion.component.css"</span>],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AccordionComponent {
  expanded = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>&lt;<span class="hljs-built_in">number</span>&gt;();
  <span class="hljs-comment">/**
   * Decides if the single item will be open at once or not.
   * In collapsing mode, toggling one would collapse others
   */</span>
  <span class="hljs-meta">@Input</span>() collapsing = <span class="hljs-literal">true</span>;

  <span class="hljs-meta">@ContentChildren</span>(AccordionItem) items: QueryList&lt;AccordionItem&gt;;

  <span class="hljs-comment">/**
   * Make the toggle function available to be called from
   * outside.
   * @param index - Index of the accordion item
   */</span>
  getToggleState = <span class="hljs-function">(<span class="hljs-params">index: <span class="hljs-built_in">number</span></span>) =&gt;</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.toggleState.bind(<span class="hljs-built_in">this</span>, index);
  };

  toggleState = <span class="hljs-function">(<span class="hljs-params">index: <span class="hljs-built_in">number</span></span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.expanded.has(index)) {
      <span class="hljs-built_in">this</span>.expanded.delete(index);
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.collapsing) {
        <span class="hljs-built_in">this</span>.expanded.clear();
      }
      <span class="hljs-built_in">this</span>.expanded.add(index);
    }
  };
}
</code></pre>
<p>A  <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set">Set</a> is used to maintain the state of currently expanded accordion items. A set guarantees distinct values. </p>
<pre><code class="lang-ts">  <span class="hljs-meta">@ContentChildren</span>(AccordionItem) items: QueryList&lt;AccordionItem&gt;;
</code></pre>
<p>This will give us the accordion items in the template which can be used to iterate and display the contents in the view.</p>
<p>An input <code>collapsing</code> is taken from the user which defines the behavior of the accordion. It tells the accordion whether to close other items when an item is expanded.</p>
<ul>
<li>collapsing = true will make sure there will be only one item open at a time</li>
<li>collapsing = false means multiple items can be open at once</li>
</ul>
<p>A function called <code>toggleState</code> is added which basically toggles the state of an item. We pass the index of the item, it will check and expand/collapse the item.</p>
<p>The <code>getToggleState</code> function is a special one which I will discuss in a bit.</p>
<h3 id="heading-accordion-component-template">Accordion component template</h3>
<p>Let's now see how this is all laid out.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"accordion"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> *<span class="hljs-attr">ngFor</span>=<span class="hljs-string">"let item of items;index as i"</span>
    <span class="hljs-attr">class</span>=<span class="hljs-string">"accordion__item"</span> [<span class="hljs-attr">class.disabled</span>]=<span class="hljs-string">"item.disabled"</span> [<span class="hljs-attr">class.active</span>]=<span class="hljs-string">"expanded.has(i)"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ng-container</span>
      [<span class="hljs-attr">ngTemplateOutlet</span>]=<span class="hljs-string">"(item?.customHeader?.templateRef || defaultHeader)"</span>
      [<span class="hljs-attr">ngTemplateOutletContext</span>]=<span class="hljs-string">"{$implicit: item, index: i, toggle: getToggleState(i)}"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">ng-container</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"accordion__content"</span> [<span class="hljs-attr">class.expanded</span>]=<span class="hljs-string">"expanded.has(i)"</span> [@<span class="hljs-attr">contentExpansion</span>]=<span class="hljs-string">"expanded.has(i) ? 'expanded':'collapsed'"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ng-container</span> *<span class="hljs-attr">ngTemplateOutlet</span>=<span class="hljs-string">"item?.content?.templateRef"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">ng-container</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">ng-template</span> #<span class="hljs-attr">defaultHeader</span> <span class="hljs-attr">let-item</span> <span class="hljs-attr">let-index</span>=<span class="hljs-string">"index"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">header</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"accordion__header"</span>
    (<span class="hljs-attr">click</span>)=<span class="hljs-string">"item.disabled ? {} :toggleState(index)"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ng-container</span> *<span class="hljs-attr">ngTemplateOutlet</span>=<span class="hljs-string">"item?.customTitle?.templateRef || defaultTitle"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">ng-container</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"accordion__toggle-btn"</span> [<span class="hljs-attr">disabled</span>]=<span class="hljs-string">"item.disabled"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">svg</span>
        <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.w3.org/2000/svg"</span>
        <span class="hljs-attr">viewBox</span>=<span class="hljs-string">"0 0 24 24"</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"24"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"24"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">path</span> <span class="hljs-attr">fill</span>=<span class="hljs-string">"none"</span> <span class="hljs-attr">d</span>=<span class="hljs-string">"M0 0h24v24H0z"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">path</span> <span class="hljs-attr">d</span>=<span class="hljs-string">"M12 13.172l4.95-4.95 1.414 1.414L12 16 5.636 9.636 7.05 8.222z"</span>/&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">svg</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ng-template</span> #<span class="hljs-attr">defaultTitle</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"accordion__title"</span>&gt;</span>{{item?.title}}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ng-template</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ng-template</span>&gt;</span>
</code></pre>
<p>We are just iterating over the <code>items</code> array (<a target="_blank" href="https://angular.io/api/core/QueryList">Querylist</a>), which basically is a list of the <code>accordion-items</code> passed inside our <code>accordion</code> component.</p>
<h3 id="heading-content-projection">Content Projection</h3>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">ng-container</span>
      [<span class="hljs-attr">ngTemplateOutlet</span>]=<span class="hljs-string">"(item?.customHeader?.templateRef || defaultHeader)"</span>
      [<span class="hljs-attr">ngTemplateOutletContext</span>]=<span class="hljs-string">"{$implicit: item, index: i, toggle: getToggleState(i)}"</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ng-container</span>&gt;</span>
</code></pre>
<p>If the user has provided a custom header, we use that template or we use our <code>defaultHeader</code>. We use something called <code>ngTemplateOutlet</code> (<a target="_blank" href="https://angular.io/api/common/NgTemplateOutlet">ref</a>) to project the template into the view.</p>
<p><code>ngTemplateOutletContext</code> is used to pass some information into the template. We pass:</p>
<ul>
<li><code>item</code> - the current accordion item.</li>
<li><code>index</code> - index of the accordion item (required for toggling it).</li>
<li><code>toggle</code> - a function that can be called to toggle the accordion item.</li>
</ul>
<p>The <code>toggle</code> property is a function that has the current context and the index bound to it. So whenever it's called, that particular item will automatically be toggled without passing the index value to the function. </p>
<p>Also, see that it's an <code>arrow</code> function, that is the reason we can call the <code>toggleState</code> function with the proper context (<code>this</code>).</p>
<pre><code class="lang-ts">getToggleState = <span class="hljs-function">(<span class="hljs-params">index: <span class="hljs-built_in">number</span></span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.toggleState.bind(<span class="hljs-built_in">this</span>, index);
};
</code></pre>
<p><strong>Note</strong>: If you are worried about using methods in the template, there are two ways to make this code better:</p>
<h4 id="heading-1-memoize-the-gettogglestate-method">1. Memoize the <code>getToggleState</code> method</h4>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { memoize } <span class="hljs-keyword">from</span> <span class="hljs-string">'lodash-es'</span>;

getToggleState = memoize(<span class="hljs-function">(<span class="hljs-params">index: <span class="hljs-built_in">number</span></span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Called'</span>);
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.toggleState.bind(<span class="hljs-built_in">this</span>, index);
})
</code></pre>
<h4 id="heading-2-use-custom-pipe">2. Use Custom Pipe</h4>
<pre><code class="lang-ts"><span class="hljs-meta">@Pipe</span>({
  name:<span class="hljs-string">'getToggleFunction'</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> TogglePipe <span class="hljs-keyword">implements</span> PipeTransform{
  transform(i: <span class="hljs-built_in">number</span>, toggleFn: <span class="hljs-built_in">Function</span>){
    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> toggleFn(i);
  }
}
</code></pre>
<p>and make necessary change the template:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">ng-container</span> 
  [<span class="hljs-attr">ngTemplateOutlet</span>]=<span class="hljs-string">"(item?.customHeader?.templateRef || defaultHeader)"</span>
  [<span class="hljs-attr">ngTemplateOutletContext</span>]=<span class="hljs-string">"{$implicit: item, index: i, toggle: i | getToggleFunction: toggleState}"</span>
&lt;/<span class="hljs-attr">ng-container</span>&gt;</span>
</code></pre>
<h3 id="heading-usage">Usage</h3>
<p>This is how we use the <code>ngTemplateOutletContext</code>: </p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">ng-template</span> #<span class="hljs-attr">defaultHeader</span> <span class="hljs-attr">let-item</span> <span class="hljs-attr">let-index</span>=<span class="hljs-string">"index"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">ng-template</span>&gt;</span>
</code></pre>
<p>When <code>let-item</code> is called, it refers to the implicit property that we attached (<code>{$implicit: item}</code>).  It just means that its the <code>default</code> value to use when the user didn't specify which property they want (see how <code>index</code> is queried).</p>
<p>By adding <code>let-index="index"</code>, we are assigning the index property to a variable called <code>index</code>. This would make the variable available to use within the template.</p>
<p>Finally, lets see how we can use the component. First thing is that the <code>AccordionModule</code> should be imported before it can be used.</p>
<p>Here are all the different ways you can use the component:</p>
<h4 id="heading-basic-usage">Basic usage</h4>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">accordion</span> [<span class="hljs-attr">collapsing</span>]=<span class="hljs-string">"collapsing"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">accordion-item</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"Super simple Accordion"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">ng-template</span> <span class="hljs-attr">accordionContent</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"p-4"</span>&gt;</span>
            A simple and customizable accordion component.
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">ng-template</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">accordion-item</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">accordion</span>&gt;</span>
</code></pre>
<h4 id="heading-with-custom-title">With Custom title</h4>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">accordion</span> [<span class="hljs-attr">collapsing</span>]=<span class="hljs-string">"collapsing"</span>&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">accordion-item</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ng-template</span> <span class="hljs-attr">accordionTitle</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"flex space-x-2"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Custom Title<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">ng-template</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">ng-template</span> <span class="hljs-attr">accordionContent</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"p-4"</span>&gt;</span>
              This is a simple implementation where title part is custom.
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">ng-template</span>&gt;</span>
     <span class="hljs-tag">&lt;/<span class="hljs-name">accordion-item</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">accordion</span>&gt;</span>
</code></pre>
<h4 id="heading-with-custom-header">With Custom Header</h4>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">accordion</span> [<span class="hljs-attr">collapsing</span>]=<span class="hljs-string">"collapsing"</span>&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">accordion-item</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"Super simple Accordion"</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">ng-template</span> <span class="hljs-attr">accordionHeader</span> <span class="hljs-attr">let-toggle</span>=<span class="hljs-string">"toggle"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"flex items-center justify-between px-4 h-12 bg-purple-200"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span> Custom Header <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>(with custom toggle button)<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"flex space-x-2"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">button</span> (<span class="hljs-attr">click</span>)=<span class="hljs-string">"toggle()"</span>&gt;</span>Toggle<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">ng-template</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">ng-template</span> <span class="hljs-attr">accordionContent</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"p-4"</span>&gt;</span>
              This is a <span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span>complete custom header<span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span> implementation.
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">ng-template</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">accordion-item</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">accordion</span>&gt;</span>
</code></pre>
<p>See how the <code>toggle</code> function is first defined <code>let-toggle="toggle"</code> and then used in the button <code>&lt;button (click)="toggle()"&gt;Toggle&lt;/button&gt;</code></p>
<p>This is how you can create a simple, but a customizable accordion component in angular.</p>
<h2 id="heading-bonus-animations">Bonus: Animations</h2>
<p>I have added Angular animations for animating the content when the user toggles the accordion item.</p>
<p>Only two things have to be added for this. First we need to defined our animation in the <code>@Component</code> decorator (<a target="_blank" href="https://angular.io/guide/animations#step-3-adding-the-animation-metadata-property">ref</a>):</p>
<pre><code class="lang-ts"><span class="hljs-meta">@Component</span>({
 <span class="hljs-comment">// --- removed for brevity</span>
  animations: [
    trigger(<span class="hljs-string">'contentExpansion'</span>, [
      state(<span class="hljs-string">'expanded'</span>, style({height: <span class="hljs-string">'*'</span>, opacity: <span class="hljs-number">1</span>, visibility: <span class="hljs-string">'visible'</span>})),
      state(<span class="hljs-string">'collapsed'</span>, style({height: <span class="hljs-string">'0px'</span>, opacity: <span class="hljs-number">0</span>, visibility: <span class="hljs-string">'hidden'</span>})),
      transition(<span class="hljs-string">'expanded &lt;=&gt; collapsed'</span>,
        animate(<span class="hljs-string">'200ms cubic-bezier(.37,1.04,.68,.98)'</span>)),
    ])
  ]
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AccordionComponent {}
</code></pre>
<p>and then in the template:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"accordion__content"</span> [@<span class="hljs-attr">contentExpansion</span>]=<span class="hljs-string">"expanded.has(i) ? 'expanded':'collapsed'"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ng-container</span> *<span class="hljs-attr">ngTemplateOutlet</span>=<span class="hljs-string">"item?.content?.templateRef"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">ng-container</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>We add the animation trigger to the <code>div</code> and pass the state based on the expanded condition. This gives us a smooth animation when toggling the panel.</p>
<p>All the styles that are required for the accordion are in the <code>accordion.component.css</code> file. TailwindCSS is used only for styling the main application.</p>
<h2 id="heading-code-and-demo">Code and Demo</h2>
<iframe src="https://codesandbox.io/embed/ng-accordion-ssscp?autoresize=1&amp;fontsize=14&amp;hidenavigation=1&amp;module=%2Fsrc%2Fapp%2Flib%2Faccordion%2Faccordion.component.ts&amp;theme=dark&amp;view=preview" style="width:100%;height:500px;border:0;border-radius:4px;overflow:hidden" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>

<p>Demo: https://ssscp.csb.app</p>
<p>Code: https://codesandbox.io/s/ng-accordion-ssscp</p>
<p>Code with Pipe: https://codesandbox.io/s/ng-accordion-optimized-49bxr</p>
<h2 id="heading-connect-with-me">Connect with me</h2>
<ul>
<li><a target="_blank" href="https://twitter.com/AdiSreyaj">Twitter</a></li>
<li><a target="_blank" href="https://github.com/adisreyaj">Github</a></li>
<li><a target="_blank" href="https://www.linkedin.com/in/adithyasreyaj/">Linkedin</a></li>
<li><a target="_blank" href="https://cardify.adi.so">Cardify</a> - Dynamic SVG Images for Github Readmes</li>
</ul>
<p>Do add your thoughts in the comments section.
Stay Safe ❤️</p>
<p><a target="_blank" href="https://www.buymeacoffee.com/adisreyaj"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639498527478/IA3aJ9R0J.png" alt="Buy me a pizza" /></a></p>
]]></content:encoded></item><item><title><![CDATA[Highlight text in paragraphs with a simple directive in Angular]]></title><description><![CDATA[How to highlight text in a paragraph with the help of directives in Angular. Especially helpful in highlighting text matching the search term. You could have come across this in your browser or IDE when you search for something, the matching items wi...]]></description><link>https://sreyaj.dev/highlight-text-in-angular-using-directives</link><guid isPermaLink="true">https://sreyaj.dev/highlight-text-in-angular-using-directives</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Angular]]></category><category><![CDATA[Angular 2]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Adithya Sreyaj]]></dc:creator><pubDate>Tue, 01 Jun 2021 04:23:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1622365240991/9ekX7bfFn.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>How to highlight text in a paragraph with the help of directives in Angular. Especially helpful in highlighting text matching the search term. You could have come across this in your browser or IDE when you search for something, the matching items will be highlighted to point you to the exact place of occurrence.</p>
<h2 id="text-highlighting">Text Highlighting</h2>
<p>Here is what we are going to build in this post. A very simple and straightforward highlight directive in Angular. We see something similar in chrome dev tools.</p>
<p>The idea is pretty simple. We just have to match the searched term and somehow wrap the matched text in a <code>span</code> or <code>mark</code> ( <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/mark">ref</a> ) tag so that we can style them later according to our needs.</p>
<h2 id="how-to-highlight-matched-text">How to highlight matched text?</h2>
<p>We are going to use <code>Regex</code> to find matches in our paragraph. Regex makes it very simple to do operations like this on strings. The directive should be ideally added only to elements with text in it.
This is what we are building:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1622313456863/JGswufIHm.png" alt="Highlight text directive" /></p>
<p>So let's plan out our directive.
The main input to the directive is the term that needs to be highlighted. So yeah, we will use <code>@Input()</code> to pass the term to our directive. I think that is pretty much what we need inside the directive</p>
<p>So now we need to get hold of the actual paragraph to search in. So there is an easy way to get the text from an <code>HTMLElement</code>. We can use the <code>textContent</code>( <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent">ref</a> ) which should give us the text to search in.</p>
<h2 id="building-the-highlight-directive">Building the Highlight directive</h2>
<p>As always, I would recommend you create a new module only for the directive. And If you really properly manage your codebase, you can consider creating it as a library within the project as well.</p>
<p>To keep things simple, we put our code in a <code>lib</code> folder:</p>
<pre><code class="lang-md">lib/
├── highlight/
│   ├── highlight.module.ts
│   ├── highlight.directive.ts
</code></pre>
<h3 id="highlight-module">Highlight Module</h3>
<p>This module would be simple declaring our directive and exporting it. Nothing much is needed here.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { CommonModule } <span class="hljs-keyword">from</span> <span class="hljs-string">"@angular/common"</span>;
<span class="hljs-keyword">import</span> { NgModule } <span class="hljs-keyword">from</span> <span class="hljs-string">"@angular/core"</span>;
<span class="hljs-keyword">import</span> { HighlightDirective } <span class="hljs-keyword">from</span> <span class="hljs-string">"./highligh.directive"</span>;

<span class="hljs-meta">@NgModule</span>({
  declarations: [HighlightDirective],
  imports: [CommonModule],
  <span class="hljs-built_in">exports</span>: [HighlightDirective]
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> HighlightModule {}
</code></pre>
<h3 id="highlight-directive">Highlight Directive</h3>
<p>Now that our setup is complete, we can start creating our directive where all our magic is going to happen.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> {
  Directive,
  ElementRef,
  HostBinding,
  Input,
  OnChanges,
  SecurityContext,
  SimpleChanges
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@angular/core"</span>;
<span class="hljs-keyword">import</span> { DomSanitizer, SafeHtml } <span class="hljs-keyword">from</span> <span class="hljs-string">"@angular/platform-browser"</span>;

<span class="hljs-meta">@Directive</span>({
  selector: <span class="hljs-string">"[highlight]"</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> HighlightDirective <span class="hljs-keyword">implements</span> OnChanges {
  <span class="hljs-meta">@Input</span>(<span class="hljs-string">"highlight"</span>) searchTerm: <span class="hljs-built_in">string</span>;
  <span class="hljs-meta">@Input</span>() caseSensitive = <span class="hljs-literal">false</span>;
  <span class="hljs-meta">@Input</span>() customClasses = <span class="hljs-string">""</span>;

  <span class="hljs-meta">@HostBinding</span>(<span class="hljs-string">"innerHtml"</span>)
  content: <span class="hljs-built_in">string</span>;
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> el: ElementRef, <span class="hljs-keyword">private</span> sanitizer: DomSanitizer</span>) {}

  ngOnChanges(changes: SimpleChanges) {
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.el?.nativeElement) {
      <span class="hljs-keyword">if</span> (<span class="hljs-string">"searchTerm"</span> <span class="hljs-keyword">in</span> changes || <span class="hljs-string">"caseSensitive"</span> <span class="hljs-keyword">in</span> changes) {
        <span class="hljs-keyword">const</span> text = (<span class="hljs-built_in">this</span>.el.nativeElement <span class="hljs-keyword">as</span> HTMLElement).textContent;
        <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.searchTerm === <span class="hljs-string">""</span>) {
          <span class="hljs-built_in">this</span>.content = text;
        } <span class="hljs-keyword">else</span> {
          <span class="hljs-keyword">const</span> regex = <span class="hljs-keyword">new</span> <span class="hljs-built_in">RegExp</span>(
            <span class="hljs-built_in">this</span>.searchTerm,
            <span class="hljs-built_in">this</span>.caseSensitive ? <span class="hljs-string">"g"</span> : <span class="hljs-string">"gi"</span>
          );
          <span class="hljs-keyword">const</span> newText = text.replace(regex, <span class="hljs-function">(<span class="hljs-params">match: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
            <span class="hljs-keyword">return</span> <span class="hljs-string">`&lt;mark class="highlight <span class="hljs-subst">${<span class="hljs-built_in">this</span>.customClasses}</span>"&gt;<span class="hljs-subst">${match}</span>&lt;/mark&gt;`</span>;
          });
          <span class="hljs-keyword">const</span> sanitzed = <span class="hljs-built_in">this</span>.sanitizer.sanitize(
            SecurityContext.HTML,
            newText
          );
          <span class="hljs-built_in">this</span>.content = sanitzed;
        }
      }
    }
  }
}
</code></pre>
<p>Let's do a code breakdown.</p>
<p>The first thing that we need is the Inputs in our directive. We only actually need the search term, but I have added some extra functionalities to our directive. We can provide <code>customClasses</code> for the highlighted text, and another flag 'caseSensitive` which will decide whether we have to match the case.</p>
<pre><code class="lang-ts"><span class="hljs-meta">@Input</span>(<span class="hljs-string">"highlight"</span>) searchTerm: <span class="hljs-built_in">string</span>;
<span class="hljs-meta">@Input</span>() caseSensitive = <span class="hljs-literal">false</span>;
<span class="hljs-meta">@Input</span>() customClasses = <span class="hljs-string">""</span>;
</code></pre>
<p>Next up we add a <code>HostBinding</code> ( <a target="_blank" href="https://angular.io/api/core/HostBinding">ref</a> ) which can be used to add value to a property on the host element.</p>
<pre><code><span class="hljs-variable">@HostBinding</span>(<span class="hljs-string">"innerHtml"</span>)
 <span class="hljs-attribute">content</span>: string;
</code></pre><p>We bind to the <code>innerHtml</code> ( <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML">ref</a> ) property of the host element. We can also do it in this way:</p>
<pre><code class="lang-ts"><span class="hljs-built_in">this</span>.el.nativeElement.innerHtml = <span class="hljs-string">'some text'</span>;
</code></pre>
<p>To get access to the host element, we inject <code>ElementRef</code> in the constructor, and also since we are going to be playing around with direct HTML manipulation, I have also injected <code>DomSanitizer</code> ( <a target="_blank" href="https://angular.io/api/platform-browser/DomSanitizer">ref</a> ) to sanitize the HTML before we inject it into the element.</p>
<p>So now we move on to the actual logic which we can write in the <code>ngOnChanges</code> ( <a target="_blank" href="https://angular.io/api/core/OnChanges">ref</a> ) lifecycle hook. So when our search term changes, we can update the highlights. The interesting part is:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> regex = <span class="hljs-keyword">new</span> <span class="hljs-built_in">RegExp</span>(<span class="hljs-built_in">this</span>.searchTerm,<span class="hljs-built_in">this</span>.caseSensitive ? <span class="hljs-string">"g"</span> : <span class="hljs-string">"gi"</span>);
<span class="hljs-keyword">const</span> newText = text.replace(regex, <span class="hljs-function">(<span class="hljs-params">match: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
     <span class="hljs-keyword">return</span> <span class="hljs-string">`&lt;mark class="highlight <span class="hljs-subst">${<span class="hljs-built_in">this</span>.customClasses}</span>"&gt;<span class="hljs-subst">${match}</span>&lt;/mark&gt;`</span>;
});
<span class="hljs-keyword">const</span> sanitzed = <span class="hljs-built_in">this</span>.sanitizer.sanitize(
    SecurityContext.HTML,
    newText
);
<span class="hljs-built_in">this</span>.content = sanitzed;
</code></pre>
<p>First, we set up the regex to help us find the matches. based on the <code>caseSensitive</code> condition we just add different Regex Flags:</p>
<ul>
<li>g - search for all matches.</li>
<li>gi -search for all matches while ignoring case.</li>
</ul>
<p>We just wrap the matches with <code>mark</code> tag using the <code>replace</code> ( <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace">ref</a> ) method on the string.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> newText = text.replace(regex, <span class="hljs-function">(<span class="hljs-params">match: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
     <span class="hljs-keyword">return</span> <span class="hljs-string">`&lt;mark class="highlight <span class="hljs-subst">${<span class="hljs-built_in">this</span>.customClasses}</span>"&gt;<span class="hljs-subst">${match}</span>&lt;/mark&gt;`</span>;
});
</code></pre>
<p>After that the newText, which is a HTML string needs to be sanitized before we can bind it to the innerHTML. We use the <code>sanitize</code> ( <a target="_blank" href="https://angular.io/api/platform-browser/DomSanitizer#sanitize">ref</a> ) method on the <code>DomSanitizer</code> class:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> sanitzed = <span class="hljs-built_in">this</span>.sanitizer.sanitize(
    SecurityContext.HTML,
    newText
);
</code></pre>
<p>Now we just assign the sanitized value to our <code>content</code> property which gets added to the <code>innerHTML</code> via <code>HostBinding</code>.</p>
<h2 id="usage">Usage</h2>
<p>This is how we can use it in our component. Make sure to import our <code>HighlightModule</code> to make our directive available for use in the component.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">p</span> [<span class="hljs-attr">highlight</span>]=<span class="hljs-string">"searchTerm"</span> [<span class="hljs-attr">caseSensitive</span>]=<span class="hljs-string">"true"</span> <span class="hljs-attr">customClasses</span>=<span class="hljs-string">"my-highlight-class"</span>&gt;</span>
      Lorem Ipsum has been the industry's standard dummy text ever since the
      1500s, when an unknown printer took a galley of type and scrambled it to
      make a type specimen book.
<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
</code></pre>
<p>That's all! We've successfully created a very simple text highlighter in Angular using directives. As always, please don't directly reuse the code above, try to optimize it and you can always add or remove features to it.</p>
<h2 id="demo-and-code">Demo and Code</h2>
<iframe src="https://codesandbox.io/embed/ng-highlight-11hii?autoresize=1&amp;fontsize=14&amp;hidenavigation=1&amp;module=%2Fsrc%2Fapp%2Flib%2Fhighlight%2Fhighligh.directive.ts&amp;theme=dark&amp;view=preview" style="width:100%;height:500px;border:0;border-radius:4px;overflow:hidden" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>

<p>CodeSandbox: https://codesandbox.io/s/ng-highlight-11hii</p>
<h2 id="connect-with-me">Connect with me</h2>
<ul>
<li><a target="_blank" href="https://twitter.com/AdiSreyaj">Twitter</a></li>
<li><a target="_blank" href="https://github.com/adisreyaj">Github</a></li>
<li><a target="_blank" href="https://www.linkedin.com/in/adithyasreyaj/">Linkedin</a></li>
<li><a target="_blank" href="https://cartella.sreyaj.dev">Cartella</a> - building at the moment</li>
</ul>
<p><a target="_blank" href="https://www.buymeacoffee.com/adisreyaj"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1618661389599/2B667-okT.png" alt="Buy me a pizza" /></a></p>
<p>Do add your thoughts in the comments section.
Stay Safe ❤️</p>
]]></content:encoded></item></channel></rss>