<?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[Frontend Engineering | Sabhya Verma]]></title><description><![CDATA[Articles about web technologies like Angular, React, CSS and Framer Motion to make the most out of them in your next web app]]></description><link>https://www.sabhya.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1667118120550/lZETaaBRy.png</url><title>Frontend Engineering | Sabhya Verma</title><link>https://www.sabhya.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Tue, 07 Apr 2026 20:30:56 GMT</lastBuildDate><atom:link href="https://www.sabhya.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How to Override Tailwind CSS Styles in a React Component]]></title><description><![CDATA[The Problem
Say you've built a <Button /> component in React that encapsulates your design system and perhaps some business logic to reuse across your React project using Tailwind CSS as the styling tool. It might look something like this:
export def...]]></description><link>https://www.sabhya.dev/how-to-override-tailwind-css-styles-in-a-react-component</link><guid isPermaLink="true">https://www.sabhya.dev/how-to-override-tailwind-css-styles-in-a-react-component</guid><category><![CDATA[Tailwind CSS]]></category><category><![CDATA[React]]></category><dc:creator><![CDATA[Sabhya Verma]]></dc:creator><pubDate>Sun, 30 Jun 2024 11:40:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1719747741353/16bc2375-5be5-40b7-9296-92ea83ee4884.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-the-problem">The Problem</h2>
<p>Say you've built a <code>&lt;Button /&gt;</code> component in React that encapsulates your design system and perhaps some business logic to reuse across your React project using Tailwind CSS as the styling tool. It might look something like this:</p>
<pre><code class="lang-apache"><span class="hljs-attribute">export</span> default function Button({ className, children, ...props }) {
  <span class="hljs-attribute">return</span> (
    <span class="hljs-section">&lt;button
      className={`py-4 px-6 text-white bg-blue-800 text-md hover:opacity-80 transition-opacity ${className}`}
      {...props}
    &gt;</span>
      {<span class="hljs-attribute">children</span>}
    <span class="hljs-section">&lt;/button&gt;</span>
  );
}
</code></pre>
<p>Great! So using this <code>&lt;Button /&gt;</code> anywhere in your project will render the button with some default styles, like the <code>bg-blue-800</code> background colour:</p>
<p><a target="_blank" href="https://codesandbox.io/p/devbox/tailwind-override-in-react-component-disabled-s3jvt3">https://codesandbox.io/p/devbox/tailwind-override-in-react-component-disabled-s3jvt3</a></p>
<iframe src="https://codesandbox.io/p/devbox/tailwind-override-in-react-component-s3jvt3?embed=1&amp;file=%2Fsrc%2FApp.tsx" style="width:100%;height:500px;border:0;border-radius:4px;overflow:hidden" sandbox=""></iframe>

<p>Notice, that we also support passing the <code>className</code> prop to this button component, for when there's an exceptional use-case where we'd want to tweak some button styles on an ad-hoc basis.</p>
<p>Consider the case, where we want to change the blue background colour to black. The <code>bg-black</code> Tailwind utility should help us do that. Wonderful, let's see what happens if we pass this in the <code>className</code> prop for the <code>&lt;Button /&gt;</code> component:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Button</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"bg-black"</span>&gt;</span>Styled button<span class="hljs-tag">&lt;/<span class="hljs-name">Button</span>&gt;</span>
</code></pre>
<p>And here's what the result looks like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1719745062064/a8cf6d81-5e46-4e0b-8401-ffdff57d3f77.png" alt class="image--center mx-auto" /></p>
<p>That's strange! The button still remains blue. Why didn't the <code>bg-black</code> class name override <code>bg-blue-800</code>?! After all, if we look at how the <code>className</code> prop is used on our <code>&lt;Button /&gt;</code> component, it's specified at the end of all the default Tailwind utilities:</p>
<pre><code class="lang-apache"><span class="hljs-attribute">className</span>={`py-<span class="hljs-number">4</span> px-<span class="hljs-number">6</span> text-white bg-blue-<span class="hljs-number">800</span> text-md hover:opacity-<span class="hljs-number">80</span> transition-opacity <span class="hljs-variable">${className}</span>`}
</code></pre>
<p>so the rendered HTML looks like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1719745392395/21575dd2-d09b-45e6-8a31-01c327ee26d0.png" alt class="image--center mx-auto" /></p>
<p>The first thought here is that since <code>bg-black</code> is used after <code>bg-blue-800</code>, it should take precedence.</p>
<p>However, this is not true. The order in which the class names appear in the <code>class</code> attribute of an HTML element does NOT matter. What matters is the order of these class rules defined in the cascading stylesheet instead.</p>
<p>In our case, Tailwind CSS includes the rules for all the utilities we enjoy using in our app when we include its stylesheets upon installing it. In our example the <code>index.css</code> file does this with:</p>
<pre><code class="lang-css"><span class="hljs-keyword">@tailwind</span> base;
<span class="hljs-keyword">@tailwind</span> components;
<span class="hljs-keyword">@tailwind</span> utilities;
</code></pre>
<p>So, now we know that in order for <code>bg-black</code> to take precedence over <code>bg-blue-800</code>, we need that style rule to appear later in the stylesheet, but we don't have control over it! How do we go around this?</p>
<h2 id="heading-the-solution">The Solution</h2>
<p>So, how do we override Tailwind CSS Styles in a React Component?</p>
<p>We can use a package called <a target="_blank" href="https://github.com/dcastil/tailwind-merge?tab=readme-ov-file#tailwind-merge">tailwind-merge</a>! This package exposes a <code>twMerge</code> helper that can help remove conflicting classes and prefer the <code>className</code> that you pass to a React component instead. Let's see how we can make use of this helper:</p>
<pre><code class="lang-apache"><span class="hljs-attribute">import</span> { twMerge } from <span class="hljs-string">"tailwind-merge"</span>;

<span class="hljs-attribute">export</span> default function Button({ className, children, ...props }) {
  <span class="hljs-attribute">return</span> (
    <span class="hljs-section">&lt;button
      className={twMerge(
        <span class="hljs-string">"py-4 px-6 text-white bg-blue-800 text-md hover:opacity-90 transition-opacity"</span>,
        className,
      )}
      {...props}
    &gt;</span>
      {<span class="hljs-attribute">children</span>}
    <span class="hljs-section">&lt;/button&gt;</span>
  );
}
</code></pre>
<p>We've updated our <code>&lt;Button /&gt;</code> component so the <code>className</code> is now the return value of the <code>twMerge</code> function imported from <code>"tailwind-merge"</code>, with the default button classes as the first argument and the provided <code>className</code> prop as the second argument. With this in place, using our <code>&lt;Button /&gt;</code> with the <code>className="bg-black"</code> prop now renders the button as intended:</p>
<p><a target="_blank" href="https://codesandbox.io/p/devbox/tailwind-override-in-react-component-tailwind-merge-d8nj7p?file=%2Fsrc%2FButton.component.tsx&amp;embed=1">https://codesandbox.io/p/devbox/tailwind-override-in-react-component-tailwind-merge-d8nj7p?file=%2Fsrc%2FButton.component.tsx&amp;embed=1</a></p>
<iframe src="https://codesandbox.io/p/devbox/tailwind-override-in-react-component-tailwind-merge-d8nj7p?file=%2Fsrc%2FButton.component.tsx&amp;embed=1" style="width:100%;height:500px;border:0;border-radius:4px;overflow:hidden" sandbox=""></iframe>

<h2 id="heading-bonus">Bonus</h2>
<p>If you use the <a target="_blank" href="https://www.npmjs.com/package/clsx">clsx</a> package for constructing classNames in React conditionally, a new helper function can help you combine <code>clsx</code> and <code>twMerge</code> and use it frequently across React components. Here's such a helper function called <code>cn</code> in TypeScript:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { <span class="hljs-keyword">type</span> ClassValue, clsx } <span class="hljs-keyword">from</span> <span class="hljs-string">'clsx'</span>
<span class="hljs-keyword">import</span> { twMerge } <span class="hljs-keyword">from</span> <span class="hljs-string">'tailwind-merge'</span>

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">cn</span>(<span class="hljs-params">...inputClasses: ClassValue[]</span>) </span>{
  <span class="hljs-keyword">return</span> twMerge(clsx(inputClasses))
}
</code></pre>
<p>So you can make use of this in the <code>&lt;Button /&gt;</code> component like so,</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { cn } <span class="hljs-keyword">from</span> <span class="hljs-string">"../cn.helper"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Button</span>(<span class="hljs-params">{ className, children, ...props }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    &lt;button
      className={cn(
        <span class="hljs-string">"py-4 px-6 text-white bg-blue-800 text-md hover:opacity-90 transition-opacity"</span>,
        className,
      )}
      {...props}
    &gt;
      {children}
    &lt;/button&gt;
  );
}
</code></pre>
<p>Thanks for reading!</p>
]]></content:encoded></item><item><title><![CDATA[Handling iOS Safari toolbar for full height web content]]></title><description><![CDATA[I recently ran into an issue where I had some content to display that spans the full height of the device and the code was using height: 100vh to achieve the same. When testing on iOS Safari, you'll see there's an issue with this:

As seen in the abo...]]></description><link>https://www.sabhya.dev/handling-ios-safari-toolbar-for-full-height-web-content</link><guid isPermaLink="true">https://www.sabhya.dev/handling-ios-safari-toolbar-for-full-height-web-content</guid><category><![CDATA[CSS]]></category><dc:creator><![CDATA[Sabhya Verma]]></dc:creator><pubDate>Thu, 11 Apr 2024 11:53:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1712836525742/71f6945a-5b7e-4867-a46c-5582ade2cc6c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I recently ran into an issue where I had some content to display that spans the full height of the device and the code was using <code>height: 100vh</code> to achieve the same. When testing on iOS Safari, you'll see there's an issue with this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1712834654939/b55a4a55-ef2f-47ac-a733-cba7b7f7c913.png" alt="100vh on iOS Safari adds a scroll bar and cuts off bottom content" class="image--center mx-auto" /></p>
<p>As seen in the above screenshots from Safari on an iPhone, using <code>100vh</code> introduces a scrollbar when the Safari toolbar is visible and cuts off the bottom content.</p>
<p>This is because the <code>vh</code> unit computes the viewport size without factoring in the toolbar height. Thus, the user either needs to scroll down for the address bar to automatically disappear or manually hide it for all content to be displayed.</p>
<p>Unfortunately, there's no way to programmatically hide the toolbar on iOS Safari using JavaScript at the time of writing. However, there is a solution for the content to adapt with the toolbar using the <code>dvh</code> unit.</p>
<p>The <code>dvh</code> unit stands for dynamic viewport height and enjoys <a target="_blank" href="https://caniuse.com/?search=dvh">support from all the latest browsers</a>. This unit factors in the viewport that's truly visible to users.</p>
<p>In case, you want to work with earlier versions of browsers that don't support <code>dvh</code>, you can make use of the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/@supports">@supports CSS rule</a>.</p>
<p>Here's an example of how you'd set up a class called <code>full-viewport-height</code> that adapts according to the browser viewport using the <code>dvh</code> unit but falls back to <code>vh</code> if the browser doesn't support it:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.full-viewport-height</span> {
  <span class="hljs-attribute">height</span>: <span class="hljs-number">100vh</span>;
}

<span class="hljs-keyword">@supports</span> (<span class="hljs-attribute">height:</span> <span class="hljs-number">100</span>dvh) {
  <span class="hljs-selector-class">.full-viewport-height</span> {
    <span class="hljs-attribute">height</span>: <span class="hljs-number">100</span>dvh;
  }
}
</code></pre>
<p>Using this <code>full-viewport-height</code> on our container now, the result looks as expected:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1712835646476/3ac4a7ea-1ee2-43f0-816b-6f884d2ba9c7.png" alt="100dvh on iphone safari adapts to full viewport height" class="image--center mx-auto" /></p>
<p>The content using <code>100dvh</code> now spans the whole height of the web page even if the Safari toolbar is visible and on scrolling down or hiding it, the content adapts to fill the whole page making sure your content always covers the viewport as expected.</p>
<p>You can find the code sandbox for the above examples at <a target="_blank" href="https://codesandbox.io/p/sandbox/100vh-3cs5zl">vh-sandbox</a> and <a target="_blank" href="https://codesandbox.io/p/sandbox/100dvh-gsgglx">dvh-sandbox</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Animating a list in React with Framer Motion]]></title><description><![CDATA[Introduction
Apple iOS is full of nifty animations and a great source of inspiration when thinking about motion in your next web app. Many of these take users from one view to another in a seamless transition instead of a jarring switch.
One such exa...]]></description><link>https://www.sabhya.dev/animating-a-list-in-react-with-framer-motion</link><guid isPermaLink="true">https://www.sabhya.dev/animating-a-list-in-react-with-framer-motion</guid><category><![CDATA[React]]></category><category><![CDATA[framer-motion]]></category><dc:creator><![CDATA[Sabhya Verma]]></dc:creator><pubDate>Sun, 08 Jan 2023 12:09:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1673177629034/31045c04-4dd1-45b6-98a8-63c5c1b3090e.gif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>Apple iOS is full of nifty animations and a great source of inspiration when thinking about motion in your next web app. Many of these take users from one view to another in a seamless transition instead of a jarring switch.</p>
<p>One such example is on clicking the Focus mode selection button in the Control Centre:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/wEagItAwuXI">https://youtu.be/wEagItAwuXI</a></div>
<p> </p>
<p>In this article, we'll re-build this animation in React with Framer Motion.</p>
<p>In case, you're not familiar with Framer Motion, I recommend checking out the article:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.sabhya.dev/animating-a-check-icon-in-react-with-framer-motion">https://www.sabhya.dev/animating-a-check-icon-in-react-with-framer-motion</a></div>
<p> </p>
<p>which goes over some basics of Framer Motion by creating a reusable animated check icon component.</p>
<h2 id="heading-demo">Demo</h2>
<p>Checkout the code and demo of the final animation we'll build as part of this article:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codesandbox.io/embed/apple-ios-control-centre-focus-mode-animation-in-react-with-framer-motion-rkevd6?autoresize=1&amp;fontsize=14&amp;hidenavigation=1&amp;module=%2Fsrc%2FApp.js&amp;theme=dark">https://codesandbox.io/embed/apple-ios-control-centre-focus-mode-animation-in-react-with-framer-motion-rkevd6?autoresize=1&amp;fontsize=14&amp;hidenavigation=1&amp;module=%2Fsrc%2FApp.js&amp;theme=dark</a></div>
<p> </p>
<h2 id="heading-layout-animation">Layout Animation</h2>
<p>After playing around with the demo above, did you notice how clicking on the Focus button takes you to the list of focus mode options in a smooth transition? This is a result of the Focus button "moving into" the list of options and becoming the Do Not Disturb button.</p>
<p>Wait, doesn't that require a lot of complex logic to make sure the animation happens performantly? It's the reason the <a target="_blank" href="https://aerotwist.com/blog/flip-your-animations/">FLIP technique</a> exists, but we want to keep our code declarative. That's where the <a target="_blank" href="https://www.framer.com/docs/layout-animations/">layout prop</a> provided by Framer Motion comes into play.</p>
<p>You start by adding the <code>layout</code> prop to a motion component, in this case, the motion button:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">motion.button</span> <span class="hljs-attr">layout</span>&gt;</span>Focus<span class="hljs-tag">&lt;/<span class="hljs-name">motion.button</span>&gt;</span>
</code></pre>
<p>Now, anytime a style change happens that affects this button's layout, Framer Motion will transition between the layout changes automatically in a performant way. Try clicking the <code>Toggle layout</code> button in the demo sandbox below and see it smoothly transition between two different locations. Then, try removing the <code>layout</code> prop from the button and see the difference!</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codesandbox.io/embed/framer-motion-button-layout-prop-demo-6bmrln?autoresize=1&amp;fontsize=14&amp;hidenavigation=1&amp;module=%2Fsrc%2FApp.js&amp;theme=dark">https://codesandbox.io/embed/framer-motion-button-layout-prop-demo-6bmrln?autoresize=1&amp;fontsize=14&amp;hidenavigation=1&amp;module=%2Fsrc%2FApp.js&amp;theme=dark</a></div>
<p> </p>
<p>By default, the <code>layout</code> prop animates any changes to the <code>size</code> and <code>position</code> of the motion component it's used on. This can, at times, lead to undesirable animations where the content can be squished or stretched when transitioning between layouts, especially when the size changes between the start and end state.</p>
<p>To circumvent this, the <code>layout</code> prop also accepts one of the following values:</p>
<ul>
<li><p><code>layout="size"</code> only animates the size changes in the motion component, for instance, the width and height.</p>
</li>
<li><p><code>layout="position"</code> only animates changes in the position of the motion component, as in our example.</p>
</li>
<li><p><code>layout="preserve-aspect"</code> only animates the size &amp; position if the aspect ratio remains the same between the transition, and just position if the ratio changes.</p>
</li>
</ul>
<p>In our use case, where we only want the position of the Focus button to transition to the Do Not Disturb button, we'll make use of the <code>layout="position"</code> prop.</p>
<p>Now that we know we only want the layout position to change and how it works, how do we create the transition between the two buttons? After all, we don't have a pre-calculated value to know where the Do Not Disturb button should render on the DOM. We want to be able to render the focus modes list component anywhere in the app while having the animation work as desired.</p>
<p>Well, Framer Motion provides yet another prop called <code>layoutId</code> to create <a target="_blank" href="https://www.framer.com/docs/layout-animations/#shared-layout-animations">shared layout animations</a>.</p>
<p>View the sandbox below to see how two different motion buttons are rendered separately in the DOM with their styling, but by providing the same <code>layoutId</code> to both of them, Framer Motion can transition between the two as they are removed or rendered in the DOM:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codesandbox.io/embed/framer-motion-button-layoutid-prop-demo-forked-nlgu21?autoresize=1&amp;fontsize=14&amp;hidenavigation=1&amp;module=%2Fsrc%2FApp.js&amp;theme=dark">https://codesandbox.io/embed/framer-motion-button-layoutid-prop-demo-forked-nlgu21?autoresize=1&amp;fontsize=14&amp;hidenavigation=1&amp;module=%2Fsrc%2FApp.js&amp;theme=dark</a></div>
<p> </p>
<h2 id="heading-variants-amp-stagger-animation">Variants &amp; Stagger Animation</h2>
<p>As we saw in the article about <a target="_blank" href="https://www.sabhya.dev/animating-a-check-icon-in-react-with-framer-motion">creating an animated check icon</a>, we can provide an <code>initial</code> and <code>animate</code> prop to a motion component to specify the states it should start with and end at, and Framer Motion automatically animates the transition between the states. In that article, we passed an object to these props specifying the styles for the two states but the <code>initial</code> and <code>animate</code> can also accept a string which should correspond to the name of a <a target="_blank" href="https://www.framer.com/docs/animation/#variants">Framer Motion Variant</a>.</p>
<p>A variant is an object with the variant name for the key and the animation properties for the values. They also prove to be a nice way to be more descriptive about animation states in Framer Motion.</p>
<p>For instance, here's the variant object defined for the focus modes list items:</p>
<pre><code class="lang-javascript">{
  <span class="hljs-attr">hidden</span>: {
    <span class="hljs-attr">y</span>: <span class="hljs-number">-10</span>,
    <span class="hljs-attr">opacity</span>: <span class="hljs-number">0</span>
  },
  <span class="hljs-attr">visible</span>: {
    <span class="hljs-attr">y</span>: <span class="hljs-number">0</span>,
    <span class="hljs-attr">opacity</span>: <span class="hljs-number">1</span>
  }
}
</code></pre>
<p>If we break this down, we see that:</p>
<ul>
<li><p>When the variant name is <code>hidden</code>, the element's opacity is 0 and it sits 10 pixels above its normal position</p>
</li>
<li><p>However, when the variant name is <code>visible</code>, the element's opacity is 1 and it sits at its normal position</p>
</li>
</ul>
<p>We can take this variant object and provide it to a motion <code>li</code> component which we will render for each focus mode:</p>
<pre><code class="lang-javascript">&lt;motion.li
  variants={{
    <span class="hljs-attr">hidden</span>: {
      <span class="hljs-attr">y</span>: <span class="hljs-number">-10</span>,
      <span class="hljs-attr">opacity</span>: <span class="hljs-number">0</span>
    },
    <span class="hljs-attr">visible</span>: {
      <span class="hljs-attr">y</span>: <span class="hljs-number">0</span>,
      <span class="hljs-attr">opacity</span>: <span class="hljs-number">1</span>
    }
  }}
&gt;
  Do Not Disturb
&lt;/motion.li&gt;
</code></pre>
<p>Alright, now that we've specified what styles we want for our list items corresponding to variant names <code>hidden</code> and <code>visible</code>, we need a way to determine when a variant gets applied.</p>
<p>Now's a good time to mention that <a target="_blank" href="https://www.framer.com/docs/animation/##propagation">variants propagate</a> from parent components to their children. This means if we set these variant names to the parent <code>ul</code> to be applied for the <code>initial</code> and <code>animate</code> props, these would automatically flow down to the child <code>li</code>. So, as soon as the <code>ul</code> mounts, it's initially using the <code>hidden</code> variant and animates to the <code>visible</code> variant, which is the same behaviour its children <code>li</code> follow:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">motion.ul</span>
  <span class="hljs-attr">initial</span>=<span class="hljs-string">"hidden"</span>
  <span class="hljs-attr">animate</span>=<span class="hljs-string">"visible"</span>
&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">variants</span>=<span class="hljs-string">{variantsFromTheSnippetAbove}</span>&gt;</span>Do Not Disturb<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">motion.ul</span>&gt;</span>
</code></pre>
<p>Not only are variants helpful to keep our animation states more readable and have the values propagate to children, but they also have another trick up their sleeves. Variants are helpful to <a target="_blank" href="https://www.framer.com/docs/animation/##orchestration">orchestrate animations</a>! This means we can set additional animation configurations on the parent element's <code>variants</code> prop to define the animation execution of its child motion components. This will become clearer by explaining the snippet below:</p>
<pre><code class="lang-javascript">&lt;motion.ul
  initial=<span class="hljs-string">"hidden"</span>
  animate=<span class="hljs-string">"visible"</span>
  variants={{
    <span class="hljs-attr">visible</span>: {
      <span class="hljs-attr">transition</span>: {
        <span class="hljs-attr">delayChildren</span>: <span class="hljs-number">0.2</span>,
        <span class="hljs-attr">staggerChildren</span>: <span class="hljs-number">0.05</span>
      }
    }
  }}
&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">variants</span>=<span class="hljs-string">{variantsFromTheSnippetAbove}</span>&gt;</span>Do Not Disturb<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span></span>
&lt;/motion.ul&gt;
</code></pre>
<p>We've added a new <code>variants</code> prop to the <code>ul</code> which defines a transition behaviour for the <code>visible</code> variant. It says that whenever the <code>visible</code> variant is applied, please delay the animation of the children by 0.2 seconds and stagger them with a delay of 0.05 seconds, so they render one by one, as it does on iOS.</p>
<p>Play around with the code sandbox below to view how variants help us create the stagger animation we want for our focus modes list:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codesandbox.io/embed/apple-ios-control-centre-focus-mode-animation-in-react-with-framer-motion-forked-7gdhuh?autoresize=1&amp;fontsize=14&amp;hidenavigation=1&amp;module=%2Fsrc%2FApp.js&amp;theme=dark">https://codesandbox.io/embed/apple-ios-control-centre-focus-mode-animation-in-react-with-framer-motion-forked-7gdhuh?autoresize=1&amp;fontsize=14&amp;hidenavigation=1&amp;module=%2Fsrc%2FApp.js&amp;theme=dark</a></div>
<p> </p>
<h2 id="heading-putting-it-all-together">Putting It All Together</h2>
<p>It's time to put the layout animation along with the variant stagger animation we've learnt to re-create the iOS animation.</p>
<p>We'll create the Focus button with the <code>layout="position"</code> prop and give it a <code>layoutId</code> of <code>focusModeButton</code>. This button's <code>onClick</code> event handler will update the state to display the focus modes list we'll create next.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">motion.button</span>
  <span class="hljs-attr">layout</span>=<span class="hljs-string">"position"</span>
  <span class="hljs-attr">layoutId</span>=<span class="hljs-string">"focusModeButton"</span>
  <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> setIsFocusModesListVisible(true)}
&gt;
  Focus
<span class="hljs-tag">&lt;/<span class="hljs-name">motion.button</span>&gt;</span>
</code></pre>
<p>To create the focus modes list, we'll use the <code>ul</code> and <code>li</code> elements along with Framer Motion's variants prop as we saw in the last section.</p>
<p>We can define the variants for the list container and the list items in separate constant objects as follows:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> LIST_CONTAINER_VARIANTS = {
  <span class="hljs-attr">visible</span>: {
    <span class="hljs-attr">transition</span>: {
      <span class="hljs-attr">delayChildren</span>: <span class="hljs-number">0.2</span>,
      <span class="hljs-attr">staggerChildren</span>: <span class="hljs-number">0.05</span>
    }
  }
};

<span class="hljs-keyword">const</span> LIST_ITEM_VARIANTS = {
  <span class="hljs-attr">hidden</span>: {
    <span class="hljs-attr">y</span>: <span class="hljs-number">-10</span>,
    <span class="hljs-attr">opacity</span>: <span class="hljs-number">0</span>
  },
  <span class="hljs-attr">visible</span>: {
    <span class="hljs-attr">y</span>: <span class="hljs-number">0</span>,
    <span class="hljs-attr">opacity</span>: <span class="hljs-number">1</span>
  }
};
</code></pre>
<p>These are the same animation configurations we saw earlier to re-create the focus modes list stagger animation. Finally, we'll apply these variants to the <code>ul</code> and <code>li</code> elements, while providing the first Do Not Disturb button, the same layout animation behaviour as we did for the Focus button, i.e., <code>layout="position"</code> and the same <code>layoutId</code> of <code>focusModeButton</code>:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">motion.ul</span>
  <span class="hljs-attr">variants</span>=<span class="hljs-string">{LIST_CONTAINER_VARIANTS}</span>
  <span class="hljs-attr">initial</span>=<span class="hljs-string">"hidden"</span>
  <span class="hljs-attr">animate</span>=<span class="hljs-string">"visible"</span>
&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">motion.li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">motion.button</span>
      <span class="hljs-attr">layout</span>=<span class="hljs-string">"position"</span>
      <span class="hljs-attr">layoutId</span>=<span class="hljs-string">"focusModeButton"</span>
    &gt;</span>
      Do Not Disturb
    <span class="hljs-tag">&lt;/<span class="hljs-name">motion.button</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">motion.li</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">motion.li</span> <span class="hljs-attr">variants</span>=<span class="hljs-string">{LIST_ITEM_VARIANTS}</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">motion.button</span>&gt;</span>Work<span class="hljs-tag">&lt;/<span class="hljs-name">motion.button</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">motion.li</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">motion.li</span> <span class="hljs-attr">variants</span>=<span class="hljs-string">{LIST_ITEM_VARIANTS}</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">motion.button</span>&gt;</span>Sleep<span class="hljs-tag">&lt;/<span class="hljs-name">motion.button</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">motion.li</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">motion.li</span> <span class="hljs-attr">variants</span>=<span class="hljs-string">{LIST_ITEM_VARIANTS}</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">motion.button</span>&gt;</span>Personal<span class="hljs-tag">&lt;/<span class="hljs-name">motion.button</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">motion.li</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">motion.ul</span>&gt;</span>
</code></pre>
<p>Using these two components, we can use React's state to toggle between the Focus mode button and the focus modes list, while Framer Motion handles the animation between the two:</p>
<pre><code class="lang-javascript">{isFocusModesListVisible ? (
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">FocusModesList</span> <span class="hljs-attr">onClose</span>=<span class="hljs-string">{()</span> =&gt;</span> setIsFocusModesListVisible(false)} /&gt;</span>
) : (
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">motion.button</span>
    <span class="hljs-attr">layout</span>=<span class="hljs-string">"position"</span>
    <span class="hljs-attr">layoutId</span>=<span class="hljs-string">"focusModeButton"</span>
    <span class="hljs-attr">className</span>=<span class="hljs-string">"FocusModeOpenButton"</span>
    <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> setIsFocusModesListVisible(true)}
  &gt;
    Focus
  <span class="hljs-tag">&lt;/<span class="hljs-name">motion.button</span>&gt;</span></span>
)}
</code></pre>
<p>With this, we end up with the final animation as we set out to build:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codesandbox.io/embed/apple-ios-control-centre-focus-mode-animation-in-react-with-framer-motion-rkevd6?autoresize=1&amp;fontsize=14&amp;hidenavigation=1&amp;module=%2Fsrc%2FApp.js&amp;theme=dark">https://codesandbox.io/embed/apple-ios-control-centre-focus-mode-animation-in-react-with-framer-motion-rkevd6?autoresize=1&amp;fontsize=14&amp;hidenavigation=1&amp;module=%2Fsrc%2FApp.js&amp;theme=dark</a></div>
<p> </p>
<p>Thanks for reading and stay tuned for more articles on the topic!</p>
]]></content:encoded></item><item><title><![CDATA[Animating a check icon in React with Framer Motion]]></title><description><![CDATA[Introduction
Both React and Framer Motion are incredible technologies that help us build next-level user interfaces in a declarative way.

If you're wondering what declarative programming is, I highly recommend checking out the article Imperative vs ...]]></description><link>https://www.sabhya.dev/animating-a-check-icon-in-react-with-framer-motion</link><guid isPermaLink="true">https://www.sabhya.dev/animating-a-check-icon-in-react-with-framer-motion</guid><category><![CDATA[React]]></category><category><![CDATA[framer-motion]]></category><category><![CDATA[heroicons]]></category><dc:creator><![CDATA[Sabhya Verma]]></dc:creator><pubDate>Wed, 05 Oct 2022 18:14:31 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1664967032130/z5WwUF-Rl.gif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>Both React and Framer Motion are incredible technologies that help us build next-level user interfaces in a declarative way.</p>
<blockquote>
<p>If you're wondering what declarative programming is, I highly recommend checking out the article <a target="_blank" href="https://ui.dev/imperative-vs-declarative-programming">Imperative vs Declarative Programming</a></p>
</blockquote>
<p>In this article, we'll create a reusable check icon component that animates by drawing itself in and out when it's mounted and unmounted by React. The animation utilises some core components from Framer Motion which are also described.</p>
<h2 id="heading-demo">Demo</h2>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codesandbox.io/embed/animating-a-check-icon-with-framer-motion-kqp8wm?fontsize=14&amp;hidenavigation=1&amp;module=%2Fsrc%2FApp.js&amp;theme=dark">https://codesandbox.io/embed/animating-a-check-icon-with-framer-motion-kqp8wm?fontsize=14&amp;hidenavigation=1&amp;module=%2Fsrc%2FApp.js&amp;theme=dark</a></div>
<p> </p>
<h2 id="heading-setting-up-the-icon-component">Setting up the icon component</h2>
<p>We'll use the <code>check</code> icon provided by <a target="_blank" href="https://heroicons.com/">hero icons</a>. Since we'll need to manipulate the <code>path</code> within the check icon <code>svg</code>, let's copy the JSX from hero icons into a new component <code>AnimatedCheckIcon</code>:</p>
<pre><code class="lang-jsx"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">AnimatedCheckIcon</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><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">fill</span>=<span class="hljs-string">"none"</span>
      <span class="hljs-attr">viewBox</span>=<span class="hljs-string">"0 0 24 24"</span>
      <span class="hljs-attr">strokeWidth</span>=<span class="hljs-string">{1.5}</span>
      <span class="hljs-attr">stroke</span>=<span class="hljs-string">"currentColor"</span>
    &gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">path</span>
        <span class="hljs-attr">strokeLinecap</span>=<span class="hljs-string">"round"</span>
        <span class="hljs-attr">strokeLinejoin</span>=<span class="hljs-string">"round"</span>
        <span class="hljs-attr">d</span>=<span class="hljs-string">"M4.5 12.75l6 6 9-13.5"</span>
      /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">svg</span>&gt;</span></span>
  );
}
</code></pre>
<p>Nothing special here at the moment (of course, apart from the beautiful icon by the hero icons team), so let's install framer motion in preparation for adding animations to this component</p>
<pre><code class="lang-bash">npm install framer-motion
</code></pre>
<h2 id="heading-animating-an-svg-path-with-framer-motion">Animating an SVG path with Framer Motion</h2>
<p>The first step to animate a <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path">path element</a> is converting it to a <code>motion.path</code> component. <a target="_blank" href="https://www.framer.com/docs/component/">Motion components</a> exist for all HTML and SVG elements and add animation super-powers to their DOM primitive counterparts.</p>
<p>These super-powers include defaults bundled with Framer Motion leading us to better looking and performant animations without worrying about the intricate details. The bare bones version to animate a motion component from one state to another requires passing the <code>initial</code> and <code>animate</code> prop specifying the styles for these states. Framer Motion then transitions between these states while providing satisfying defaults for its easing function and its duration at the least.</p>
<blockquote>
<p>Declarative programming at its best!</p>
</blockquote>
<p>Amongst these super-powers, is the ability to animate <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/pathLength">pathLength</a> for a <code>motion.path</code> component.</p>
<p>Let's take a look at what happens when we use <code>motion.path</code> in our <code>AnimatedCheckIcon</code> instead of <code>path</code> and specify the <code>initial</code> and <code>animate</code> props to take us from a <code>pathLength</code> of 0 to 1:</p>
<pre><code class="lang-jsx">&lt;motion.path
  strokeLinecap=<span class="hljs-string">"round"</span>
  strokeLinejoin=<span class="hljs-string">"round"</span>
  initial={{ <span class="hljs-attr">pathLength</span>: <span class="hljs-number">0</span> }}
  animate={{ <span class="hljs-attr">pathLength</span>: <span class="hljs-number">1</span> }}
  d=<span class="hljs-string">"M4.5 12.75l6 6 9-13.5"</span>
/&gt;
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1664971846134/p9trK_J9P.gif" alt="motion-path-animate.gif" /></p>
<p>Great! We're already animating the check icon drawing itself in.</p>
<p>Similarly, if we switch up the initial and animate pathLengths to go from 1 to 0, we'll see the check icon erase itself:</p>
<pre><code class="lang-jsx">&lt;motion.path
  strokeLinecap=<span class="hljs-string">"round"</span>
  strokeLinejoin=<span class="hljs-string">"round"</span>
  initial={{ <span class="hljs-attr">pathLength</span>: <span class="hljs-number">1</span> }}
  animate={{ <span class="hljs-attr">pathLength</span>: <span class="hljs-number">0</span> }}
  d=<span class="hljs-string">"M4.5 12.75l6 6 9-13.5"</span>
/&gt;
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1664972711176/ikCEn6Puu.gif" alt="motion-path-erase.gif" /></p>
<blockquote>
<p>The transition duration in the GIFs has been increased from the default value for clarity but is not displayed in the code snippets as we'll be touching on that later in the article</p>
</blockquote>
<p>Okay, so we now know how to animate the <code>pathLength</code> but what happens if we mount/unmount this component? Let's render this component based on some state that we can toggle with a button. So we can now conditionally render our <code>&lt;AnimatedCheckIcon /&gt;</code> like so:</p>
<pre><code class="lang-jsx">{isCheckIconVisible &amp;&amp; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AnimatedCheckIcon</span> /&gt;</span></span>}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1664973671721/NdhntJaFc.gif" alt="motion-path-animation-initial.gif" /></p>
<p>We can see the path now animate when the component mounts but how do we specify an animation to run when it un-mounts?</p>
<h2 id="heading-adding-an-unmount-animation-with-animate-presence">Adding an unmount animation with Animate Presence</h2>
<p>Framer motion provides the <a target="_blank" href="https://www.framer.com/docs/animate-presence/">AnimatePresence</a> component that helps us define animations that happen when a component is removed from the DOM by React.</p>
<p>This can be done by wrapping the component to be removed within <code>&lt;AnimatePresence /&gt;</code> and adding the <code>exit</code> prop to the motion component defining its animation behaviour.</p>
<p>To make use of AnimatePresence, first let's add an <code>isVisible: boolean</code> prop to our <code>&lt;AnimatedCheckIcon /&gt;</code> component that conditionally renders our icon based on the value provided:</p>
<pre><code class="lang-jsx"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">AnimatedCheckIcon</span>(<span class="hljs-params">{ isVisible = true }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    isVisible &amp;&amp; (
      <span class="xml"><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">fill</span>=<span class="hljs-string">"none"</span>
        <span class="hljs-attr">viewBox</span>=<span class="hljs-string">"0 0 24 24"</span>
        <span class="hljs-attr">strokeWidth</span>=<span class="hljs-string">{1.5}</span>
        <span class="hljs-attr">stroke</span>=<span class="hljs-string">"currentColor"</span>
      &gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">motion.path</span>
          <span class="hljs-attr">strokeLinecap</span>=<span class="hljs-string">"round"</span>
          <span class="hljs-attr">strokeLinejoin</span>=<span class="hljs-string">"round"</span>
          <span class="hljs-attr">initial</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">pathLength:</span> <span class="hljs-attr">0</span> }}
          <span class="hljs-attr">animate</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">pathLength:</span> <span class="hljs-attr">1</span> }}
          <span class="hljs-attr">transition</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">duration:</span> <span class="hljs-attr">0.5</span> }}
          <span class="hljs-attr">d</span>=<span class="hljs-string">"M4.5 12.75l6 6 9-13.5"</span>
        /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">svg</span>&gt;</span></span>
    )
  );
}
</code></pre>
<p>We now need to nest our conditionally rendered icon within <code>&lt;AnimatePresence /&gt;</code>:</p>
<pre><code class="lang-jsx"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">AnimatedCheckIcon</span>(<span class="hljs-params">{ isVisible = true }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AnimatePresence</span>&gt;</span>
      {isVisible &amp;&amp; (
        <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">fill</span>=<span class="hljs-string">"none"</span>
          <span class="hljs-attr">viewBox</span>=<span class="hljs-string">"0 0 24 24"</span>
          <span class="hljs-attr">strokeWidth</span>=<span class="hljs-string">{1.5}</span>
          <span class="hljs-attr">stroke</span>=<span class="hljs-string">"currentColor"</span>
        &gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">motion.path</span>
            <span class="hljs-attr">strokeLinecap</span>=<span class="hljs-string">"round"</span>
            <span class="hljs-attr">strokeLinejoin</span>=<span class="hljs-string">"round"</span>
            <span class="hljs-attr">initial</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">pathLength:</span> <span class="hljs-attr">0</span> }}
            <span class="hljs-attr">animate</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">pathLength:</span> <span class="hljs-attr">1</span> }}
            <span class="hljs-attr">transition</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">duration:</span> <span class="hljs-attr">0.5</span> }}
            <span class="hljs-attr">d</span>=<span class="hljs-string">"M4.5 12.75l6 6 9-13.5"</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">AnimatePresence</span>&gt;</span></span>
  );
}
</code></pre>
<blockquote>
<p>It's necessary that <code>&lt;AnimatePresence&gt;</code> component is outside of the <code>isVisible &amp;&amp;</code> condition as that allows Framer Motion to workaround React's limitation of not providing enough data to animate an element when it's being removed from the React tree</p>
</blockquote>
<p>With our component wrapped in AnimatePresence, we can now add the <code>exit</code> prop to our <code>motion.path</code>:</p>
<pre><code class="lang-jsx">&lt;motion.path
  strokeLinecap=<span class="hljs-string">"round"</span>
  strokeLinejoin=<span class="hljs-string">"round"</span>
  initial={{ <span class="hljs-attr">pathLength</span>: <span class="hljs-number">0</span> }}
  animate={{ <span class="hljs-attr">pathLength</span>: <span class="hljs-number">1</span> }}
  exit={{ <span class="hljs-attr">pathLength</span>: <span class="hljs-number">0</span> }}
  d=<span class="hljs-string">"M4.5 12.75l6 6 9-13.5"</span>
/&gt;
</code></pre>
<p>We specify exit animation to complete with <code>pathLength: 0</code> so the icon erases itself as we saw before. In summary, the props:</p>
<pre><code class="lang-jsx">initial={{ <span class="hljs-attr">pathLength</span>: <span class="hljs-number">0</span> }}
animate={{ <span class="hljs-attr">pathLength</span>: <span class="hljs-number">1</span> }}
exit={{ <span class="hljs-attr">pathLength</span>: <span class="hljs-number">0</span> }}
</code></pre>
<p>declare that the component should transition from <code>pathLength</code> 0 to 1 when it mounts and 1 to 0 when it un-mounts.</p>
<p>If we connect the toggle state for rendering our icon from earlier but use the <code>isVisible</code> prop as follows:</p>
<pre><code class="lang-jsx">{<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AnimatedCheckIcon</span> <span class="hljs-attr">isVisible</span>=<span class="hljs-string">{isCheckIconVisible}</span> /&gt;</span></span>}
</code></pre>
<p>we now see the unmount animation too:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1664979441467/um1_U1woq.gif" alt="motion-path-moun-unmount-animation.gif" /></p>
<h2 id="heading-disabling-the-initial-mount-animation">Disabling the initial mount animation</h2>
<p>At this point, whenever our <code>&lt;AnimatedCheckIcon /&gt;</code> component first mounts, it will animate itself based on the value of the <code>isVisible</code> prop. This might be the behaviour that's desired within a certain use case but perhaps not in others.</p>
<p>Thankfully, the <code>&lt;AnimatePresence /&gt;</code> component exposes the <code>initial</code> prop that accepts a boolean value to handle this concern. Passing <code>false</code> suppresses the first mount animation while <code>true</code>, which is the default, runs the animation on the initial mount. Adding it as a prop to our icon component, to mimic this behaviour, our component interface now looks like the following:</p>
<pre><code class="lang-jsx"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">AnimatedCheckIcon</span>(<span class="hljs-params">{ initial = true, isVisible }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AnimatePresence</span> <span class="hljs-attr">initial</span>=<span class="hljs-string">{initial}</span>&gt;</span>
      {/* Rest of the component */}
    <span class="hljs-tag">&lt;/<span class="hljs-name">AnimatePresence</span>&gt;</span></span>
  )
}
</code></pre>
<h2 id="heading-fine-tuning-the-animation">Fine-tuning the animation</h2>
<p>Our animation is now set up but we can play around with yet another prop for Motion Components – <a target="_blank" href="https://www.framer.com/docs/transition/">transition</a> – to make it even better! The <code>transition</code> prop can be used to fine-tune how the animation values go from one state to the other.</p>
<p>We'll make use of 3 properties within transition:</p>
<h3 id="heading-1-type">1. Type</h3>
<p>The <code>type</code> property accepts multiple options but two worth highlighting are <code>spring</code> and <code>tween</code>. Spring animations use <a target="_blank" href="https://www.framer.com/docs/transition/#spring">spring physics</a> to transition between animation states while Tween is a duration-based type and can be customised with an easing function. Framer Motion picks the most appropriate defaults based on the styles being animated which in our case would be tween.</p>
<h3 id="heading-2-duration">2. Duration</h3>
<p>The <code>duration</code> property specifies how long the transition takes to go from the initial to the animated state, specified in seconds. The default duration for tween animations is 0.3 seconds.</p>
<h3 id="heading-3-ease">3. Ease</h3>
<p>The <code>ease</code> property accepts an <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/easing-function">easing function</a> to customise how fast the values change while animating over the specified duration. We'll use the built-in <code>ease-out</code> easing when the check icon mounts and <code>ease-in</code> when it un-mounts. The ease-out function starts quick but ends in a relaxed fashion while the ease-in animation starts in a relaxed fashion and ends quick. Try reverting these values in the sandbox to see how they affect our animation!</p>
<p>Putting these customisations together:</p>
<pre><code class="lang-javascript">&lt;motion.path
  initial={{ <span class="hljs-attr">pathLength</span>: <span class="hljs-number">0</span> }}
  animate={{ <span class="hljs-attr">pathLength</span>: <span class="hljs-number">1</span> }}
  exit={{ <span class="hljs-attr">pathLength</span>: <span class="hljs-number">0</span> }}
  transition={{
    <span class="hljs-attr">type</span>: <span class="hljs-string">"tween"</span>,
    <span class="hljs-attr">duration</span>: <span class="hljs-number">0.3</span>,
    <span class="hljs-attr">ease</span>: isVisible ? <span class="hljs-string">"easeOut"</span> : <span class="hljs-string">"easeIn"</span>
  }}
  strokeLinecap=<span class="hljs-string">"round"</span>
  strokeLinejoin=<span class="hljs-string">"round"</span>
  d=<span class="hljs-string">"M4.5 12.75l6 6 9-13.5"</span>
/&gt;
</code></pre>
<p>we get the animation from the demo!</p>
<h2 id="heading-our-final-reusable-animated-check-icon-component">Our final reusable animated check icon component</h2>
<p>So, here's what we end up with as our reusable component that you can drop into your project for that well-polished micro-interaction:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { AnimatePresence, motion } <span class="hljs-keyword">from</span> <span class="hljs-string">"framer-motion"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">AnimatedCheckIcon</span>(<span class="hljs-params">{ initial = true, isVisible }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AnimatePresence</span> <span class="hljs-attr">initial</span>=<span class="hljs-string">{initial}</span>&gt;</span>
      {isVisible &amp;&amp; (
        <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">fill</span>=<span class="hljs-string">"none"</span>
          <span class="hljs-attr">viewBox</span>=<span class="hljs-string">"0 0 24 24"</span>
          <span class="hljs-attr">strokeWidth</span>=<span class="hljs-string">{1.5}</span>
          <span class="hljs-attr">stroke</span>=<span class="hljs-string">"currentColor"</span>
          <span class="hljs-attr">className</span>=<span class="hljs-string">"CheckIcon"</span>
        &gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">motion.path</span>
            <span class="hljs-attr">initial</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">pathLength:</span> <span class="hljs-attr">0</span> }}
            <span class="hljs-attr">animate</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">pathLength:</span> <span class="hljs-attr">1</span> }}
            <span class="hljs-attr">exit</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">pathLength:</span> <span class="hljs-attr">0</span> }}
            <span class="hljs-attr">transition</span>=<span class="hljs-string">{{</span>
              <span class="hljs-attr">type:</span> "<span class="hljs-attr">tween</span>",
              <span class="hljs-attr">duration:</span> <span class="hljs-attr">0.3</span>,
              <span class="hljs-attr">ease:</span> <span class="hljs-attr">isVisible</span> ? "<span class="hljs-attr">easeOut</span>" <span class="hljs-attr">:</span> "<span class="hljs-attr">easeIn</span>"
            }}
            <span class="hljs-attr">strokeLinecap</span>=<span class="hljs-string">"round"</span>
            <span class="hljs-attr">strokeLinejoin</span>=<span class="hljs-string">"round"</span>
            <span class="hljs-attr">d</span>=<span class="hljs-string">"M4.5 12.75l6 6 9-13.5"</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">AnimatePresence</span>&gt;</span></span>
  );
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1664982658821/KXjFME99o.gif" alt="animating-check-icon-path-framer-motion.gif" /></p>
<p>In case, you're curious about the toggle button animation on the demo sandbox, it uses <a target="_blank" href="https://www.framer.com/docs/gestures/">Framer Motion gestures</a> to animate its <code>scale</code> on hover and tap. You can check out the <a target="_blank" href="https://codesandbox.io/s/animating-a-check-icon-with-framer-motion-kqp8wm?file=/src/App.js">code sandbox</a> for the full code and stay tuned for more articles on the topic.</p>
<p>Thanks for reading!</p>
]]></content:encoded></item><item><title><![CDATA[Setting up Segment Analytics in Angular with environment files]]></title><description><![CDATA[Introduction
I recently finished working on an Angular web app and it was time to set up analytics to better understand the user experience it delivers. My choice of analytics provider was Segment as it allows setting up a single analytics source wit...]]></description><link>https://www.sabhya.dev/setting-up-segment-analytics-in-angular-with-environment-files</link><guid isPermaLink="true">https://www.sabhya.dev/setting-up-segment-analytics-in-angular-with-environment-files</guid><category><![CDATA[Segment analytics]]></category><category><![CDATA[Angular]]></category><category><![CDATA[analytics]]></category><category><![CDATA[Google Analytics]]></category><dc:creator><![CDATA[Sabhya Verma]]></dc:creator><pubDate>Sun, 02 Oct 2022 19:11:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1664737683156/Z_YUA-K8D.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>I recently finished working on an Angular web app and it was time to set up analytics to better understand the user experience it delivers. My choice of analytics provider was <a href="https://segment.com/">Segment</a> as it allows setting up a single analytics source within the app and feed the data to various other <a href="https://segment.com/docs/connections/destinations/catalog/">destinations</a> like Google Analytics and Amplitude.</p>
<p>Having decided on the analytics service to use, I now wanted to integrate it within Angular and test it across different app environments. For the app, I had three environments - dev, staging and production. By specifying different analytics keys for each environment, it prevents skewing live analytics while allowing us to test our integration in a non-production environment.</p>
<h2 id="heading-including-the-segment-analytics-tracking-snippet">Including the Segment analytics tracking snippet</h2>
<p>Setting up Segment starts with including their <a href="https://segment.com/docs/connections/sources/catalog/libraries/website/javascript/quickstart/#step-2-add-the-segment-snippet">tracking snippet</a> in our <code>index.html</code>:</p>
<pre><code class="lang-javascript">&lt;script&gt;
  !<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{<span class="hljs-keyword">var</span> analytics=<span class="hljs-built_in">window</span>.analytics=<span class="hljs-built_in">window</span>.analytics||[];<span class="hljs-keyword">if</span>(!analytics.initialize)<span class="hljs-keyword">if</span>(analytics.invoked)<span class="hljs-built_in">window</span>.console&amp;&amp;<span class="hljs-built_in">console</span>.error&amp;&amp;<span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Segment snippet included twice."</span>);<span class="hljs-keyword">else</span>{analytics.invoked=!<span class="hljs-number">0</span>;analytics.methods=[<span class="hljs-string">"trackSubmit"</span>,<span class="hljs-string">"trackClick"</span>,<span class="hljs-string">"trackLink"</span>,<span class="hljs-string">"trackForm"</span>,<span class="hljs-string">"pageview"</span>,<span class="hljs-string">"identify"</span>,<span class="hljs-string">"reset"</span>,<span class="hljs-string">"group"</span>,<span class="hljs-string">"track"</span>,<span class="hljs-string">"ready"</span>,<span class="hljs-string">"alias"</span>,<span class="hljs-string">"debug"</span>,<span class="hljs-string">"page"</span>,<span class="hljs-string">"once"</span>,<span class="hljs-string">"off"</span>,<span class="hljs-string">"on"</span>,<span class="hljs-string">"addSourceMiddleware"</span>,<span class="hljs-string">"addIntegrationMiddleware"</span>,<span class="hljs-string">"setAnonymousId"</span>,<span class="hljs-string">"addDestinationMiddleware"</span>];analytics.factory=<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">e</span>)</span>{<span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{<span class="hljs-keyword">var</span> t=<span class="hljs-built_in">Array</span>.prototype.slice.call(<span class="hljs-built_in">arguments</span>);t.unshift(e);analytics.push(t);<span class="hljs-keyword">return</span> analytics}};<span class="hljs-keyword">for</span>(<span class="hljs-keyword">var</span> e=<span class="hljs-number">0</span>;e&lt;analytics.methods.length;e++){<span class="hljs-keyword">var</span> key=analytics.methods[e];analytics[key]=analytics.factory(key)}analytics.load=<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">key,e</span>)</span>{<span class="hljs-keyword">var</span> t=<span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">"script"</span>);t.type=<span class="hljs-string">"text/javascript"</span>;t.async=!<span class="hljs-number">0</span>;t.src=<span class="hljs-string">"https://cdn.segment.com/analytics.js/v1/"</span> + key + <span class="hljs-string">"/analytics.min.js"</span>;<span class="hljs-keyword">var</span> n=<span class="hljs-built_in">document</span>.getElementsByTagName(<span class="hljs-string">"script"</span>)[<span class="hljs-number">0</span>];n.parentNode.insertBefore(t,n);analytics._loadOptions=e};analytics._writeKey=<span class="hljs-string">"YOUR_WRITE_KEY"</span>;analytics.SNIPPET_VERSION=<span class="hljs-string">"4.15.2"</span>;
  analytics.load(<span class="hljs-string">"YOUR_WRITE_KEY"</span>);
  analytics.page();
  }}();
&lt;/script&gt;
</code></pre>
<p>Note the following lines of code towards the end:</p>
<pre><code class="lang-javascript">  analytics._writeKey=<span class="hljs-string">"YOUR_WRITE_KEY"</span>
  analytics.load(<span class="hljs-string">"YOUR_WRITE_KEY"</span>);
  analytics.page();
</code></pre>
<p>We'll remove these lines from the snippet and handle them ourselves in the Angular app, so the new tracking code we include in our <code>index.html</code> becomes:</p>
<pre><code class="lang-javascript">&lt;script&gt;
  !<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{<span class="hljs-keyword">var</span> analytics=<span class="hljs-built_in">window</span>.analytics=<span class="hljs-built_in">window</span>.analytics||[];<span class="hljs-keyword">if</span>(!analytics.initialize)<span class="hljs-keyword">if</span>(analytics.invoked)<span class="hljs-built_in">window</span>.console&amp;&amp;<span class="hljs-built_in">console</span>.error&amp;&amp;<span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Segment snippet included twice."</span>);<span class="hljs-keyword">else</span>{analytics.invoked=!<span class="hljs-number">0</span>;analytics.methods=[<span class="hljs-string">"trackSubmit"</span>,<span class="hljs-string">"trackClick"</span>,<span class="hljs-string">"trackLink"</span>,<span class="hljs-string">"trackForm"</span>,<span class="hljs-string">"pageview"</span>,<span class="hljs-string">"identify"</span>,<span class="hljs-string">"reset"</span>,<span class="hljs-string">"group"</span>,<span class="hljs-string">"track"</span>,<span class="hljs-string">"ready"</span>,<span class="hljs-string">"alias"</span>,<span class="hljs-string">"debug"</span>,<span class="hljs-string">"page"</span>,<span class="hljs-string">"once"</span>,<span class="hljs-string">"off"</span>,<span class="hljs-string">"on"</span>,<span class="hljs-string">"addSourceMiddleware"</span>,<span class="hljs-string">"addIntegrationMiddleware"</span>,<span class="hljs-string">"setAnonymousId"</span>,<span class="hljs-string">"addDestinationMiddleware"</span>];analytics.factory=<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">e</span>)</span>{<span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{<span class="hljs-keyword">var</span> t=<span class="hljs-built_in">Array</span>.prototype.slice.call(<span class="hljs-built_in">arguments</span>);t.unshift(e);analytics.push(t);<span class="hljs-keyword">return</span> analytics}};<span class="hljs-keyword">for</span>(<span class="hljs-keyword">var</span> e=<span class="hljs-number">0</span>;e&lt;analytics.methods.length;e++){<span class="hljs-keyword">var</span> key=analytics.methods[e];analytics[key]=analytics.factory(key)}analytics.load=<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">key,e</span>)</span>{<span class="hljs-keyword">var</span> t=<span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">"script"</span>);t.type=<span class="hljs-string">"text/javascript"</span>;t.async=!<span class="hljs-number">0</span>;t.src=<span class="hljs-string">"https://cdn.segment.com/analytics.js/v1/"</span> + key + <span class="hljs-string">"/analytics.min.js"</span>;<span class="hljs-keyword">var</span> n=<span class="hljs-built_in">document</span>.getElementsByTagName(<span class="hljs-string">"script"</span>)[<span class="hljs-number">0</span>];n.parentNode.insertBefore(t,n);analytics._loadOptions=e};analytics.SNIPPET_VERSION=<span class="hljs-string">"4.15.2"</span>;}}();
&lt;/script&gt;
</code></pre>
<p>This allows us to share the "base" Segment tracking snippet in all app environments while specifying a <code>SEGMENT_WRITE_KEY</code> specific to each.</p>
<h2 id="heading-specifying-a-different-key-for-each-environment">Specifying a different key for each environment</h2>
<p>Now, how do we go about specifying a key for each environment?</p>
<p>At the time of writing, when creating a new app with the Angular CLI using <code>ng new</code>, it creates two environment files in the <code>environments</code> folder by default:</p>
<ol>
<li><code>environment.ts</code>: environment variables for local development</li>
<li><code>environment.prod.ts</code>: environment variables for the production build when running <code>ng build --configuration production</code> or the default <code>ng build</code> command</li>
</ol>
<p>So, we can use these files to specify different analytics keys for Segment:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// file environment.ts:</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> environment = {
  <span class="hljs-attr">production</span>: <span class="hljs-literal">false</span>,
  <span class="hljs-attr">analytics</span>: {
    <span class="hljs-attr">segmentWriteKey</span>: <span class="hljs-string">'DEV_SEGMENT_WRITE_KEY'</span>
  }
};
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-comment">// file environment.prod.ts:</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> environment = {
  <span class="hljs-attr">production</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">analytics</span>: {
    <span class="hljs-attr">segmentWriteKey</span>: <span class="hljs-string">'PRODUCTION_SEGMENT_WRITE_KEY'</span>
  }
};
</code></pre>
<p>Having specified the different keys, we need to initiate Segment analytics with the correct key. This can be done in our <code>main.ts</code> file which is responsible for bootstrapping our Angular app. Before the <code>platformBrowserDynamic().bootstrapModule(AppModule)</code> call that loads our app, we can initialise Segment by adding back <code>analytics._writeKey="YOUR_WRITE_KEY"</code> and <code>analytics.load("YOUR_WRITE_KEY");</code> we removed from the tracking snippet earlier but replace <code>"YOUR_WRITE_KEY"</code> with our environment variable <code>segmentWriteKey</code>.</p>
<p>Trying out this line of code as is though, makes TypeScript unhappy with the following error:</p>
<pre><code class="lang-bash">Cannot find name <span class="hljs-string">'analytics'</span>. ts(2304)
</code></pre>
<p>Well, this means that analytics isn't defined anywhere, but with the way Segment works, it should be part of the <code>window</code> object. So, let's try changing it to <code>window.analytics.load(environment.analytics.segmentWriteKey)</code>. This gives us a different error:</p>
<pre><code class="lang-bash">Property <span class="hljs-string">'analytics'</span> does not exist on <span class="hljs-built_in">type</span> <span class="hljs-string">'Window &amp; typeof globalThis'</span>. ts(2339)
</code></pre>
<blockquote>
<p>A different error message, some progress!</p>
</blockquote>
<p>This is because TypeScript still does not know if the <code>analytics</code> object exists or the interface it provides within the <code>window</code> object. Thankfully, we can strongly type the analytics object by installing its types as a dev dependency using:</p>
<pre><code class="lang-bash">npm install --save-dev @types/segment-analytics
</code></pre>
<p>With the types installed, there's one last thing we need to do, to make TypeScript happy with using the <code>analytics</code> object anywhere within our code. That bit is making sure we include the segment-analytics namespace within our <code>tsconfig.app.json</code> <a href="https://www.typescriptlang.org/tsconfig#types">types</a> property like so:</p>
<pre><code class="lang-json"><span class="hljs-comment">// file: tsconfig.app.json</span>

{
  <span class="hljs-attr">"extends"</span>: <span class="hljs-string">"./tsconfig.json"</span>,
  <span class="hljs-attr">"compilerOptions"</span>: {
    <span class="hljs-attr">"outDir"</span>: <span class="hljs-string">"./out-tsc/app"</span>,
    <span class="hljs-attr">"types"</span>: [<span class="hljs-string">"segment-analytics"</span>]
  },
  <span class="hljs-attr">"files"</span>: [
    <span class="hljs-string">"src/main.ts"</span>,
    <span class="hljs-string">"src/polyfills.ts"</span>
  ],
  <span class="hljs-attr">"include"</span>: [
    <span class="hljs-string">"src/**/*.d.ts"</span>
  ]
}
</code></pre>
<p>Remember to restart your dev server with <code>ng serve</code> after updating <code>tsconfig.app.json</code>!</p>
<p>With this in place, we can now call all functions exposed by the analytics object anywhere in our code and enjoy all of TypeScript's goodness when doing so. Our final <code>main.ts</code> file should now look like:</p>
<pre><code class="lang-ts"><span class="hljs-comment">// file: main.ts</span>

<span class="hljs-keyword">import</span> { enableProdMode } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { platformBrowserDynamic } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/platform-browser-dynamic'</span>;

<span class="hljs-keyword">import</span> { AppModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'./app/app.module'</span>;
<span class="hljs-keyword">import</span> { environment } <span class="hljs-keyword">from</span> <span class="hljs-string">'./environments/environment'</span>;

<span class="hljs-keyword">if</span> (environment.production) {
  enableProdMode();
}

<span class="hljs-comment">// typing this as any since the types we installed don't include _writeKey</span>
(analytics <span class="hljs-keyword">as</span> <span class="hljs-built_in">any</span>)._writeKey = environment.analytics.segmentWriteKey
analytics.load(environment.analytics.segmentWriteKey)

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> <span class="hljs-built_in">console</span>.error(err));
</code></pre>
<h2 id="heading-tracking-page-changes">Tracking page changes</h2>
<p>Our final task is to now bring back the <code>analytics.page()</code> call we removed from the snippet earlier. Segment analytics includes this call in the tracking snippet assuming its usage in a traditional Multi Page Application (MPA) where we load a new <code>index.html</code> file for each page and thus invoke a new <code>analytics.page()</code> call.</p>
<p>With Angular though, this is a different story as we're building a Single Page Application (SPA) that only loads a single <code>index.html</code> file and handles page changes via JavaScript. So, <a href="https://segment.com/blog/tracking-single-page-application/">we need to take control of the analytics page calls</a>.</p>
<p>This is a perfect use-case for <a href="https://angular.io/api/router/Event">Angular Router Events</a> as it allows us to hook into the lifecycle of the Angular router and subscribe to page changes. With this, any time the Angular Router loads a new page, we can hook into that event, and make a call to <code>analytics.page()</code>. We'll make use of the <a href="https://angular.io/api/router/NavigationStart">NavigationStart</a> event, so we trigger the call as soon as a page is changed. </p>
<p>To be able to listen to router events, we first need to inject the Angular Router in our <code>app.component.ts</code>:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { Router } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;

<span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> _router: Router</span>) {}
</code></pre>
<p>Using the injected router we can <code>subscribe</code> to all its events:</p>
<pre><code class="lang-ts"><span class="hljs-built_in">this</span>._router.events.subscribe(<span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(event)
})
</code></pre>
<p>Since we're interested in the <code>NavigationStart</code> event only, we can add a check with <code>instanceof</code>:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { NavigationStart } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;

<span class="hljs-keyword">if</span> (event <span class="hljs-keyword">instanceof</span> NavigationStart) {
  <span class="hljs-built_in">console</span>.log(event)
}
</code></pre>
<p>If we replace <code>console.log(event)</code> in the above snippet with <code>analytics.page(event.url)</code>, we can trigger an analytics page call, each time Angular Router changes a page. Putting all of these pieces together and remembering to unsubscribe from our subscription when this component destroys to prevent memory leaks, we get:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// file: app.component.ts</span>

<span class="hljs-keyword">import</span> { Subscription } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>;
<span class="hljs-keyword">import</span> { NavigationStart, Router } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;
<span class="hljs-keyword">import</span> { Component, OnDestroy, OnInit } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-root'</span>,
  templateUrl: <span class="hljs-string">'./app.component.html'</span>,
  styleUrls: [<span class="hljs-string">'./app.component.css'</span>]
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppComponent <span class="hljs-keyword">implements</span> OnInit, OnDestroy {
  routerEventSubscription: Subscription | <span class="hljs-literal">undefined</span>;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> _router: Router</span>) {}

  <span class="hljs-keyword">public</span> ngOnInit(): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.routerEventSubscription = <span class="hljs-built_in">this</span>._router.events.subscribe(<span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
      <span class="hljs-keyword">if</span> (event <span class="hljs-keyword">instanceof</span> NavigationStart) {
        analytics.page(event.url)
      }
    });
  }

  <span class="hljs-keyword">public</span> ngOnDestroy(): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.routerEventSubscription?.unsubscribe()
  }
}
</code></pre>
<p>That sets us up for using Segment analytics in our Angular app! 🎉 </p>
<p>For tracking particular events, like clicks, make use of the <code>analytics.track()</code> call which would also now be strongly typed thanks to the <code>@types/segment-analytics</code> package we set up.</p>
<h2 id="heading-bonus-setting-up-the-staging-environment">Bonus – setting up the staging environment</h2>
<p>As an added bonus, let's look at setting up a staging (or QA) environment for the app. We'll make use of the <code>configuration</code> and <code>fileReplacement</code> properties in <code>angular.json</code> which allow us to create a new build target and <a href="https://angular.io/guide/workspace-config#additional-build-and-test-options">specify specific file replacements at compile time</a>.</p>
<p>To do this, we'll duplicate the <code>production</code> configuration, rename the key to <code>staging</code> and within <code>fileReplacements</code>, specify <code>environment.ts</code> to be replaced with <code>environment.staging.ts</code> when we build the app for staging:</p>
<pre><code class="lang-json"><span class="hljs-string">"staging"</span>: {
  <span class="hljs-attr">"budgets"</span>: [
    {
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"initial"</span>,
      <span class="hljs-attr">"maximumWarning"</span>: <span class="hljs-string">"500kb"</span>,
      <span class="hljs-attr">"maximumError"</span>: <span class="hljs-string">"1mb"</span>
    },
    {
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"anyComponentStyle"</span>,
      <span class="hljs-attr">"maximumWarning"</span>: <span class="hljs-string">"2kb"</span>,
      <span class="hljs-attr">"maximumError"</span>: <span class="hljs-string">"4kb"</span>
    }
  ],
  <span class="hljs-attr">"fileReplacements"</span>: [
    {
      <span class="hljs-attr">"replace"</span>: <span class="hljs-string">"src/environments/environment.ts"</span>,
      <span class="hljs-attr">"with"</span>: <span class="hljs-string">"src/environments/environment.staging.ts"</span>
    }
  ],
  <span class="hljs-attr">"outputHashing"</span>: <span class="hljs-string">"all"</span>
},
</code></pre>
<p>Remember to create the <code>environment.staging.ts</code> file! Our new configuration will now be used when building the app with <code>ng build --configuration staging</code></p>
<blockquote>
<p><a href="https://github.com/A7xSV/segment-analytics-with-angular">View the code from this article on GitHub</a></p>
</blockquote>
<p>Thanks for reading!</p>
]]></content:encoded></item></channel></rss>