<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dmitry A. Efimenko</title>
    <description>The latest articles on DEV Community by Dmitry A. Efimenko (@dmitryefimenko).</description>
    <link>https://dev.to/dmitryefimenko</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F102777%2Fb647f80e-14b5-44e2-a3f5-3fb9a39742c6.jpg</url>
      <title>DEV Community: Dmitry A. Efimenko</title>
      <link>https://dev.to/dmitryefimenko</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dmitryefimenko"/>
    <language>en</language>
    <item>
      <title>[Boost]</title>
      <dc:creator>Dmitry A. Efimenko</dc:creator>
      <pubDate>Wed, 28 Jan 2026 04:26:05 +0000</pubDate>
      <link>https://dev.to/dmitryefimenko/-abf</link>
      <guid>https://dev.to/dmitryefimenko/-abf</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/brandontroberts" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F466270%2F57db4360-a676-4c59-9fd9-80d7265951aa.jpeg" alt="brandontroberts"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/brandontroberts/angular-skills-agent-skills-for-ai-assisted-development-3jgg" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Angular Skills: Agent Skills for AI-Assisted Development&lt;/h2&gt;
      &lt;h3&gt;Brandon Roberts ・ Jan 26&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#angular&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>angular</category>
    </item>
    <item>
      <title>Angular Animation Magic: Unlock the Power of the View Transition API</title>
      <dc:creator>Dmitry A. Efimenko</dc:creator>
      <pubDate>Wed, 30 Apr 2025 13:47:33 +0000</pubDate>
      <link>https://dev.to/dmitryefimenko/angular-animation-magic-unlock-the-power-of-the-view-transition-api-3c9g</link>
      <guid>https://dev.to/dmitryefimenko/angular-animation-magic-unlock-the-power-of-the-view-transition-api-3c9g</guid>
      <description>&lt;p&gt;This is a &lt;a href="https://www.angularspace.com/angular-animation-magic-unlock-the-power-of-the-view-transition-api/" rel="noopener noreferrer"&gt;cross-post of my article from www.angularspace.com&lt;/a&gt;. If you want to read the original, head there!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqsun7yhk2g0mc6glo76u.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqsun7yhk2g0mc6glo76u.jpg" alt="Angular Animation Magic: Unlock the Power of the View Transition API" width="800" height="614"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🪄 Introduction: A Glimpse of Magic
&lt;/h2&gt;

&lt;p&gt;When I first saw a presentation about the View Transition API, it blew my mind. It is truly magical!&lt;/p&gt;

&lt;p&gt;The core idea is that the browser captures a visual snapshot of DOM elements marked with the &lt;code&gt;view-transition-name&lt;/code&gt; CSS property before a DOM change. After the DOM change, the differences are animated using CSS Animations.&lt;/p&gt;

&lt;p&gt;To truly appreciate its capabilities, take a look at these examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://codepen.io/argyleink/full/VwBKjwj?ref=dev.to"&gt;Example 1: View Transitions like IsotopeJS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://view-transitions.chrome.dev/cards/spa/?ref=dev.to"&gt;Example 2: Adding and removing cards&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://live-transitions.pages.dev/?ref=dev.to"&gt;Example 3: Playlist app&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's a classic code example illustrating how to trigger a view transition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleClick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Fallback for browsers that don't support this API:&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startViewTransition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;updateTheDOMSomehow&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// With a View Transition:&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startViewTransition&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;updateTheDOMSomehow&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;updateTheDOMSomehow&lt;/code&gt;? Somehow?? And it will just work? Yep, it will. This highlights the remarkable flexibility of the API.&lt;/p&gt;

&lt;p&gt;I won't delve into the fundamentals of the View Transition API in this article. The &lt;a href="https://developer.chrome.com/docs/web-platform/view-transitions?ref=dev.to"&gt;official documentation&lt;/a&gt; provides excellent information if you're not already familiar with it. A basic understanding of Angular components, signals, and CSS is recommended.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚧 The Angular Challenge: Beyond Route Transitions
&lt;/h2&gt;

&lt;p&gt;Working with the View Transition API in Angular is already possible for navigating between routes. However, animating individual elements on a page &lt;a href="https://github.com/angular/angular/issues/55829?ref=dev.to"&gt;is not supported out of the box&lt;/a&gt;. How can this gap be bridged?&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚙️ Setting the Stage
&lt;/h3&gt;

&lt;p&gt;Begin by creating a basic Angular component to experiment with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;basic-demo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;styleUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./basic-demo.component.scss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;button (click)="toggle()"&amp;gt;Toggle position&amp;lt;/button&amp;gt;

    &amp;lt;div class="relative"&amp;gt;
      &amp;lt;div class="box" [class]="position()"&amp;gt;
        &amp;lt;span&amp;gt;{{ position() }}&amp;lt;/span&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ViewTransitionBasicDemoComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This component displays a box initially positioned on the left. Clicking the button toggles its visual position between the left and the right, while also displaying the current position within the box. The CSS has been omitted for brevity. Find the complete example on &lt;a href="https://stackblitz.com/edit/stackblitz-starters-wpq3wtdj?file=src%2Fmain.ts&amp;amp;ref=dev.to"&gt;StackBlitz&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8iu27xbqmzy5ragpcgmg.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8iu27xbqmzy5ragpcgmg.gif" alt="Angular Animation Magic: Unlock the Power of the View Transition API" width="756" height="136"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, attempt to animate the element using the View Transition API.&lt;/p&gt;

&lt;p&gt;First, add the following to the application's global styles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;view-transition-name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures that only elements explicitly marked with &lt;code&gt;view-transition-name&lt;/code&gt; will be animated.&lt;/p&gt;

&lt;p&gt;According to the View Transition API, two key elements are needed for the animation to work:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The element intended for animation must have the &lt;code&gt;view-transition-name&lt;/code&gt; CSS property set with a unique value.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;document.startViewTransition()&lt;/code&gt; function needs to be called. The provided callback function should update the DOM.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Applying the CSS property is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"box"&lt;/span&gt; &lt;span class="na"&gt;[class]=&lt;/span&gt;&lt;span class="s"&gt;"position()"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"view-transition-name: box"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;{{ position() }}&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, the second part presents a challenge in Angular.&lt;/p&gt;

&lt;p&gt;A naive attempt might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startViewTransition&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... and surprisingly, it works! The animation occurs. See it in action on &lt;a href="https://stackblitz.com/edit/stackblitz-starters-4rdswmgu?file=src%2Fmain.ts&amp;amp;ref=dev.to"&gt;StackBlitz&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvbpfm20ou475ix1q4cym.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvbpfm20ou475ix1q4cym.gif" alt="Angular Animation Magic: Unlock the Power of the View Transition API" width="764" height="134"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will see why this is rather surprising as we introduce a little bit more complexity to our example and talk more about SPA paradigm on rendering.&lt;/p&gt;

&lt;p&gt;Let's add a &lt;code&gt;computed&lt;/code&gt; property and another DOM element that would be shown depending on the new &lt;code&gt;computed&lt;/code&gt; property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`// omitted for brevity`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;button (click)="toggle()"&amp;gt;Toggle position&amp;lt;/button&amp;gt;

    &amp;lt;div class="box-container"&amp;gt;
      &amp;lt;div class="box" [class]="position()" style="view-transition-name: box"&amp;gt;
        &amp;lt;span&amp;gt;{{ position() }}&amp;lt;/span&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;

    @if (isCircleVisible()) {
      &amp;lt;div class="circle" style="view-transition-name: circle"&amp;gt;&amp;lt;/div&amp;gt;
    }
  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;isCircleVisible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startViewTransition&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, while toggling between "left" and "right", the &lt;em&gt;box&lt;/em&gt; still animates, but the &lt;em&gt;circle&lt;/em&gt; is not animated despite having &lt;code&gt;view-transition-name&lt;/code&gt; CSS property as seen in this &lt;a href="https://stackblitz.com/edit/stackblitz-starters-q8ee6g1l?file=src%2Fmain.ts&amp;amp;ref=dev.to"&gt;StackBlitz example&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg81frl36zpab62esgwrp.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg81frl36zpab62esgwrp.gif" alt="Angular Animation Magic: Unlock the Power of the View Transition API" width="756" height="238"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🔄 The SPA Paradigm: Data Updates vs. DOM Manipulation
&lt;/h3&gt;

&lt;p&gt;Recall that the &lt;code&gt;startViewTransition&lt;/code&gt; function's callback expects a function that updates the DOM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startViewTransition&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;updateTheDOMSomehow&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, in Single Page Applications (SPAs) like Angular, developers typically work with data (state) and rely on the framework to render the DOM in response to data changes. Direct DOM manipulation is generally avoided.&lt;/p&gt;

&lt;p&gt;So, the &lt;code&gt;updateTheDOMSomehow()&lt;/code&gt; part isn't directly available to a developer working with Angular. Instead, the developer would write the &lt;code&gt;updateTheDataSomehow()&lt;/code&gt; function, which doesn't directly align with the View Transition API's requirement.&lt;/p&gt;

&lt;p&gt;The animation works for the &lt;em&gt;box&lt;/em&gt; in the simpler case likely because Angular is fast enough to synchronize the state change and the DOM update within the execution of the callback function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startViewTransition&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Angular is quick enough to update DOM dependent on `position` here&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, the &lt;code&gt;computed&lt;/code&gt; property likely schedules a separate render tick that occurs outside the &lt;code&gt;startViewTransition&lt;/code&gt; callback. This explains why the &lt;em&gt;circle&lt;/em&gt; isn't animated.&lt;/p&gt;

&lt;p&gt;Furthermore, the &lt;em&gt;box&lt;/em&gt; animation might also become inconsistent. The animation might work when switching from left to right but fail from right to left. Removing the &lt;em&gt;circle&lt;/em&gt;-related code might resolve this inconsistency.&lt;/p&gt;

&lt;p&gt;The key takeaway is that Angular renders DOM changes at its own pace, influenced by various factors. The data can't simply be updated within the &lt;code&gt;startViewTransition&lt;/code&gt; callback with the expectation that the DOM would be ready immediately.&lt;/p&gt;

&lt;p&gt;There needs to be a way to ensure the DOM has been updated within that callback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startViewTransition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;angularUpdatedTheDOM&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- Need to do this&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🌉 Bridging the Gap between the View Transition API and Angular
&lt;/h3&gt;

&lt;p&gt;To adhere to the Single Responsibility Principle, encapsulate the view-transition-related logic within a dedicated service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;providedIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ViewTransitionService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stateChangeFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startViewTransition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;stateChangeFn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startViewTransition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;stateChangeFn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createRenderPromise&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- TODO&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Angular provides the &lt;code&gt;afterNextRender&lt;/code&gt; function, which is precisely what is needed to create the &lt;code&gt;createRenderPromise&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createRenderPromise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;injector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Injector&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;afterNextRender&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;injector&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, update the component to utilize this new service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;viewTransitionService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ViewTransitionService&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;isCircleVisible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;viewTransitionService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this service in place, all animations should now work as expected. See it running on &lt;a href="https://stackblitz.com/edit/stackblitz-starters-hamxeyrx?file=src%2Fmain.ts&amp;amp;ref=dev.to"&gt;StackBlitz&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu8wxm3q43g98za5dd2wz.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu8wxm3q43g98za5dd2wz.gif" alt="Angular Animation Magic: Unlock the Power of the View Transition API" width="756" height="238"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🚦 Handling Concurrent Transitions: A Potential Pitfall
&lt;/h3&gt;

&lt;p&gt;Let's refine our demo slightly. First, slow down all view transition animations to 3 seconds to observe the behavior more clearly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;::view-transition-group&lt;/span&gt;&lt;span class="o"&gt;(*)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation-duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, add another element - a &lt;em&gt;triangle&lt;/em&gt; - that will be animated somewhat independently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@if (isTriangleVisible()) {
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"triangle"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"view-transition-name: triangle"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For simplicity, update the component to show the &lt;em&gt;triangle&lt;/em&gt; after a short delay:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;isTriangleVisible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;viewTransitionService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;viewTransitionService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isTriangleVisible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isTriangleVisible&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See this implementation on &lt;a href="https://stackblitz.com/edit/stackblitz-starters-sn6zps7m?file=src%2Fmain.ts&amp;amp;ref=dev.to"&gt;StackBlitz&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Also add some logging to the &lt;code&gt;run&lt;/code&gt; method of the &lt;code&gt;ViewTransitionService&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stateChangeFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;start transition&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startViewTransition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;state change&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;stateChangeFn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createRenderPromise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;injector&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rendered&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Upon pressing the "toggle" button, an unexpected behavior is observed. The first two elements begin animating, but after 700ms, the animation abruptly stops. The animated elements jump to their final state and then the &lt;em&gt;triangle&lt;/em&gt; element starts its animation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj0ft3ddoapikzq7kt3y6.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj0ft3ddoapikzq7kt3y6.gif" alt="Angular Animation Magic: Unlock the Power of the View Transition API" width="754" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The console output shows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;start transition
state change
rendered
start transition
state change
rendered
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This behavior is actually by design. Currently, only one view-transition animation can run at any given time. If a new view-transition is initiated while another is in progress, the ongoing transition is canceled. While there's a proposal for &lt;a href="https://github.com/WICG/view-transitions/blob/main/scoped-transitions.md?ref=dev.to"&gt;Scoped View Transitions&lt;/a&gt;, it's not yet available.&lt;/p&gt;

&lt;p&gt;From the service's perspective, adding configuration options to handle this could be considered. For instance, the &lt;code&gt;run&lt;/code&gt; method could accept an options argument to define how an incoming animation should behave: (1) cancel the previous animation, (2) be skipped, or (3) wait for the previous animation to complete.&lt;/p&gt;

&lt;p&gt;While exploring these implementations could be valuable, there's another crucial scenario that needs to be discussed.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⏱️ A Case For Optimizing the Overlapping Transitions
&lt;/h3&gt;

&lt;p&gt;Let's reduce the timeout for starting the &lt;em&gt;triangle&lt;/em&gt; animation from &lt;code&gt;700ms&lt;/code&gt; to &lt;code&gt;2ms&lt;/code&gt;. Now, clicking the "Toggle" button will likely still result in the second animation canceling the first. However, a different console output might be seen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;start transition
start transition
state change
rendered
state change
rendered
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;createRenderPromise()&lt;/code&gt; method takes a small amount of time to complete, potentially slightly more than 2ms on some systems. This means that both view transitions are being started before Angular has a chance to render the changes from the first state update. This presents an opportunity for optimization. Below is a visualization of the timing of view transitions within the Angular rendering cycle:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv27ui4bhbkddoictzawu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv27ui4bhbkddoictzawu.png" alt="Angular Animation Magic: Unlock the Power of the View Transition API" width="800" height="218"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Initial Snapshot (Yellow):&lt;/strong&gt; This occurs when &lt;code&gt;document.startViewTransition&lt;/code&gt; is called. It likely involves capturing the initial snapshot of the relevant DOM elements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State Change and Render (Blue):&lt;/strong&gt; The view transition callback is executed. Here, the Angular state is updated, which triggers DOM changes. Then the render promise waits for Angular to reflect these changes in the DOM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Final Snapshot (Yellow):&lt;/strong&gt; Once the callback completes, the browser takes the final snapshot of the DOM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Animation (Red):&lt;/strong&gt; Finally, the browser performs the view transition animation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the initial scenario with a 700ms delay, the timeline of the two view transitions looked like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4nb5i8xl5m2glrbd6wu4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4nb5i8xl5m2glrbd6wu4.png" alt="Angular Animation Magic: Unlock the Power of the View Transition API" width="800" height="178"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, with the 2ms delay, the timeline looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmenrojeug77c7k7uncno.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmenrojeug77c7k7uncno.png" alt="Angular Animation Magic: Unlock the Power of the View Transition API" width="549" height="213"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As seen above, there is an overlap in the blue blocks where state changes are applied. In this case, a second view transition doesn't need to be initiated. Instead, the second state change should ideally be applied as part of the first view transition since Angular hasn't finished rendering yet:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F87xx04m596b9mszihar6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F87xx04m596b9mszihar6.png" alt="Angular Animation Magic: Unlock the Power of the View Transition API" width="525" height="152"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This might seem like a minor edge case, but it's a critical aspect of effectively handling view transitions in Angular.&lt;/p&gt;

&lt;p&gt;To address this, modify the &lt;code&gt;run&lt;/code&gt; method in the &lt;code&gt;ViewTransitionService&lt;/code&gt; to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Buffer each incoming &lt;code&gt;stateChangeFn&lt;/code&gt; without immediately executing it.&lt;/li&gt;
&lt;li&gt;If no view transition is currently active, start one. Within the view transition callback, execute all the buffered &lt;code&gt;stateChangeFn&lt;/code&gt; in order.&lt;/li&gt;
&lt;li&gt;If a view transition is already in progress:

&lt;ul&gt;
&lt;li&gt;if Angular did not complete rendering cycle yet, execute the buffered &lt;code&gt;stateChangeFn&lt;/code&gt; functions&lt;/li&gt;
&lt;li&gt;if Angular completed the rendering cycle and View Transition API started the animation, schedule another view transition &lt;code&gt;.run&lt;/code&gt; call once the current view transition completes&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Use a buffer to ensure that the state change functions aren't lost in case they come after Angular has completed the rendering cycle for the current view transition. This also ensures that state change functions are executed in the order they were received (FIFO).&lt;/p&gt;

&lt;p&gt;Find a simplified version of the &lt;code&gt;ViewTransitionService&lt;/code&gt; with these changes applied on &lt;a href="https://stackblitz.com/edit/stackblitz-starters-mn6y4v1i?file=src%2Fview-transition.service.ts&amp;amp;ref=dev.to"&gt;StackBlitz&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The code for this is becoming quite extensive, so for the remaining examples, I'll primarily provide references to StackBlitz for longer code snippets.&lt;/p&gt;

&lt;p&gt;With this updated service, pressing the "Toggle" button should now execute all animations smoothly:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx32vkx99vy01t8tm8hcw.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx32vkx99vy01t8tm8hcw.gif" alt="State 6 video-1" width="758" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🛠️ Refining the API: Moving Closer to the DOM
&lt;/h3&gt;

&lt;p&gt;While the &lt;code&gt;ViewTransitionService&lt;/code&gt; is functional, the developer experience can be further improved. Currently, every time something needs to be animated, the corresponding state change needs to be wrapped within &lt;code&gt;viewTransitionService.run(() =&amp;gt; { /* ... */ })&lt;/code&gt;. This can lead to scattering rendering logic throughout the application's business logic, making it less maintainable. Ideally, there should be a way to mark elements for view transitions directly within the DOM structure. Something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ng-container&lt;/span&gt; &lt;span class="na"&gt;*vt=&lt;/span&gt;&lt;span class="s"&gt;"stateProp"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"view-transition-name: box;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Element to animate - {{stateProp}}
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ng-container&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There needs to be a directive that observes changes to the provided &lt;code&gt;stateProp&lt;/code&gt;. Right before the state changes, call &lt;code&gt;viewTransitionService.run(() =&amp;gt; { /* ... */ })&lt;/code&gt; to allow the View Transition API to capture the "start" snapshot. The callback of the run method would then contain the actual state update.&lt;/p&gt;

&lt;h3&gt;
  
  
  🤔 The Challenge of Timing: Capturing the "Before" State
&lt;/h3&gt;

&lt;p&gt;This raises the question of how to intercept the moment precisely before the state change is reflected in the DOM. Angular's &lt;code&gt;ngOnChanges&lt;/code&gt; lifecycle hook is executed after the property has already changed, making it too late to reliably capture the initial state for the view transition.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://x.com/yjaaidi?ref=dev.to"&gt;Younes Jaaidi&lt;/a&gt; has conducted &lt;a href="https://marmicode.io/blog/angular-signals-and-custom-render-strategies?ref=dev.to"&gt;remarkable experiments&lt;/a&gt; exploring ways to intervene between the state change and the DOM update in Angular. However, his solution involves monkey-patching private Angular APIs. While his approach would provide a cleaner directive API - our API will opt for a less experimental approach.&lt;/p&gt;

&lt;p&gt;The directive will rely on an extra piece of state that will be responsible for modifying the DOM after &lt;code&gt;viewTransitionService.run&lt;/code&gt; is invoked.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✨ Introducing the *vt Directive: A Custom Rendering Strategy
&lt;/h3&gt;

&lt;p&gt;Here's a basic implementation of such a directive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Directive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[vt]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;standalone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ViewTransitionRenderer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;viewTransitionService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ViewTransitionService&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inject&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DocumentWithViewTransition&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DOCUMENT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;templateRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TemplateRef&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;viewContainerRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ViewContainerRef&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;cdr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ChangeDetectorRef&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;trackingData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;ngOnChanges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Changes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shouldAnimate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trackingData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstChange&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// assign these variables outside of the callback to avoid closure issues&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firstChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trackingData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstChange&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trackingData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startViewTransition&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;shouldAnimate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firstChange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currentValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;viewTransitionService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firstChange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currentValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;isFirstChange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;trackingData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$implicit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;trackingData&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isFirstChange&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;viewContainerRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createEmbeddedView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;templateRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cdr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detectChanges&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;$implicit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createContext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$implicit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this directive, the &lt;code&gt;trackingData&lt;/code&gt; input holds the value that will be rendered when the view transition is ready to proceed – specifically, within the callback of the &lt;code&gt;viewTransitionService.run&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;Inside the &lt;code&gt;.run&lt;/code&gt; callback, the input's value is assigned to the &lt;code&gt;$implicit&lt;/code&gt; context of the directive.&lt;/p&gt;

&lt;p&gt;The expectation is that the template within the &lt;code&gt;*vt&lt;/code&gt; directive will now use the directive's implicit context variable instead of the original state property passed to the directive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ng-container&lt;/span&gt; &lt;span class="na"&gt;*vt=&lt;/span&gt;&lt;span class="s"&gt;"stateProp; let state"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"view-transition-name: box;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Element to animate - {{state}}
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ng-container&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the usage of &lt;code&gt;{{state}}&lt;/code&gt; instead of &lt;code&gt;{{stateProp}}&lt;/code&gt; in the template.&lt;/p&gt;

&lt;p&gt;A slightly more convenient syntax can be used by re-assigning the same name as the original property to the &lt;code&gt;$implicit&lt;/code&gt; context variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ng-container&lt;/span&gt; &lt;span class="na"&gt;*vt=&lt;/span&gt;&lt;span class="s"&gt;"stateProp; let stateProp"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"view-transition-name: box;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Element to animate - {{stateProp}}
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ng-container&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach works, although its suitability as a best practice might be debatable. Thoughts?&lt;/p&gt;

&lt;p&gt;With this directive in place, the demo component can be updated to leverage the directive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ViewTransitionRenderer&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`...`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;isCircleVisible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;isTriangleVisible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isTriangleVisible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isTriangleVisible&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"toggle()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Toggle position&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"box-container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ng-container&lt;/span&gt; &lt;span class="na"&gt;*vt=&lt;/span&gt;&lt;span class="s"&gt;"position(); let position"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"box"&lt;/span&gt; &lt;span class="na"&gt;[class]=&lt;/span&gt;&lt;span class="s"&gt;"position"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"view-transition-name: box"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;{{ position }}&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ng-container&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;ng-container&lt;/span&gt; &lt;span class="na"&gt;*vt=&lt;/span&gt;&lt;span class="s"&gt;"isCircleVisible(); let isCircleVisible"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  @if (isCircleVisible) {
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"circle"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"view-transition-name: circle"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  }
&lt;span class="nt"&gt;&amp;lt;/ng-container&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;ng-container&lt;/span&gt; &lt;span class="na"&gt;*vt=&lt;/span&gt;&lt;span class="s"&gt;"isTriangleVisible(); let isTriangleVisible"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  @if (isTriangleVisible) {
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"triangle"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"view-transition-name: triangle"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  }
&lt;span class="nt"&gt;&amp;lt;/ng-container&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See this fully implemented on &lt;a href="https://stackblitz.com/edit/stackblitz-starters-bjrpkm3a?file=src%2Fmain.ts&amp;amp;ref=dev.to"&gt;StackBlitz&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx32vkx99vy01t8tm8hcw.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx32vkx99vy01t8tm8hcw.gif" alt="Angular Animation Magic: Unlock the Power of the View Transition API" width="758" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Below is a diagram showing the data flow between the consuming component, the &lt;code&gt;*vt&lt;/code&gt; directive, the &lt;code&gt;ViewTransitionService&lt;/code&gt; and the View Transition API:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6n1e761ugui1j8p8avkl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6n1e761ugui1j8p8avkl.png" alt="Angular Animation Magic: Unlock the Power of the View Transition API" width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🎯 Fine-Grained Control: Enabling View Transitions on Demand
&lt;/h3&gt;

&lt;p&gt;Let's modify the demo to trigger the animation for the triangle element separately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"toggleBox()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Toggle Box&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"toggleTriangle()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Toggle Triangle&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;toggleBox&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;toggleTriangle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isTriangleVisible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isTriangleVisible&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, open &lt;code&gt;Chrome Dev Tools &amp;gt; Animations&lt;/code&gt;, click the "Pause" button, and then click "Toggle Triangle." Navigate to the "Elements" tab and inspect the active view transitions. See something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;::view-transition
  ::view-transition-group(box)
  ::view-transition-group(circle)
  ::view-transition-group(triangle)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This indicates that even though only the &lt;em&gt;triangle&lt;/em&gt; is intended to be animated, the &lt;em&gt;box&lt;/em&gt; and &lt;em&gt;circle&lt;/em&gt; are also part of the view transition. While their state might remain unchanged, this can have several drawbacks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The timing of other animations could influence the view transition timing of the intended element.&lt;/li&gt;
&lt;li&gt;Having numerous active animation simultaneously might lead to interference between them.&lt;/li&gt;
&lt;li&gt;Debugging animations becomes more complex when all of them are active by default.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A mechanism is needed to selectively enable or disable view transitions based on the intent. This mechanism should be automated as much as possible.&lt;/p&gt;

&lt;p&gt;In the current demo, all three elements are always included in the view transition because they always have a &lt;code&gt;view-transition-name&lt;/code&gt; CSS property set. To disable a view transition on an element, this property needs to be set to &lt;code&gt;none&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Create another directive that works in conjunction with the &lt;code&gt;*vt&lt;/code&gt; directive. This directive will allow the desired &lt;code&gt;view-transition-name&lt;/code&gt; value to be specified only when an animation is active:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ng-container&lt;/span&gt; &lt;span class="na"&gt;*vt=&lt;/span&gt;&lt;span class="s"&gt;"isTriangleVisible(); let isTriangleVisible"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  @if (isTriangleVisible) {
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"triangle"&lt;/span&gt; &lt;span class="na"&gt;vtName=&lt;/span&gt;&lt;span class="s"&gt;"triangle"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  }
&lt;span class="nt"&gt;&amp;lt;/ng-container&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, this directive will apply the value &lt;code&gt;none&lt;/code&gt; to the &lt;code&gt;view-transition-name&lt;/code&gt; style. However, when the parent &lt;code&gt;*vt&lt;/code&gt; directive initiates a view transition, the &lt;code&gt;vtName&lt;/code&gt; directive will apply the specified value. Once the view transition completes, it will revert back to &lt;code&gt;none&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I won't include the full implementation code for this directive here to keep the article concise. If interested in the implementation details - find them on &lt;a href="https://github.com/DmitryEfimenko/ngspot/tree/main/packages/view-transition/package?ref=dev.to"&gt;Github&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧩 Advanced Use Cases: Animating an Item in the Lists and Customizing Transitions
&lt;/h3&gt;

&lt;p&gt;Before concluding, consider another common scenario.&lt;/p&gt;

&lt;p&gt;Imagine a list of cards displayed in a column, driven by a &lt;code&gt;for&lt;/code&gt; loop. Each card has "up" and "down" buttons to move it within the list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Cards&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cards&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="nf"&gt;up&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cards&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;up&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;down&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;down&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@for (card of cards(); track card) {
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"card"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"up(card)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Up&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"down(card)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Down&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To achieve a basic animation of card movement, wrap the container in the &lt;code&gt;*vt&lt;/code&gt; directive and apply the &lt;code&gt;[vtName]&lt;/code&gt; directive to each card:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ng-container&lt;/span&gt; &lt;span class="na"&gt;*vt=&lt;/span&gt;&lt;span class="s"&gt;"cards(); let cards"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  @for (cardId of cards; track cardId) {
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"card"&lt;/span&gt; &lt;span class="na"&gt;[vtName]=&lt;/span&gt;&lt;span class="s"&gt;"'card-' + cardId"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"up(cardId)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Up&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"down(cardId)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Down&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  }
&lt;span class="nt"&gt;&amp;lt;/ng-container&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running this code will produce the expected animation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhgnqonezt550dgs1kt4u.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhgnqonezt550dgs1kt4u.gif" alt="Angular Animation Magic: Unlock the Power of the View Transition API" width="156" height="188"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, what if there is a requirement to customize the animation of the specific card that was clicked? That element's view transition can not be easily targeted because all cards have dynamically generated &lt;code&gt;view-transition-name&lt;/code&gt; values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;::view-transition-image-pair&lt;/span&gt;&lt;span class="o"&gt;(???)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;size-up-and-down&lt;/span&gt; &lt;span class="n"&gt;ease-in&lt;/span&gt; &lt;span class="m"&gt;0.5s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There needs to be a way to dynamically set a custom &lt;code&gt;view-transition-name&lt;/code&gt; on the clicked element.&lt;/p&gt;

&lt;p&gt;A new property and a method could be introduced in the &lt;code&gt;ViewTransitionService&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;activeViewTransitionNames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;setActiveViewTransitionNames&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeViewTransitionNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method would be expected to be called right before the view transition starts. Once the transition finishes, the &lt;code&gt;activeViewTransitionNames&lt;/code&gt; would be reset:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentViewTransition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;finished&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeViewTransitionNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this in place, a new directive: [vtNameForActive] can be introduced. This directive would check if the &lt;code&gt;activeViewTransitionNames&lt;/code&gt; signal contains the name provided to the &lt;code&gt;[vtName]&lt;/code&gt; directive on the same element. If it does, it would apply a specific view-transition-name (e.g., 'target-card'):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"card"&lt;/span&gt;
  &lt;span class="na"&gt;[vtName]=&lt;/span&gt;&lt;span class="s"&gt;"'card-' + cardId"&lt;/span&gt;
  &lt;span class="na"&gt;[vtNameForActive]=&lt;/span&gt;&lt;span class="s"&gt;"'target-card'"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Cards component would then be updated as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Cards&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;viewTransitionService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ViewTransitionService&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;cards&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="nf"&gt;up&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;viewTransitionService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setActiveViewTransitionNames&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`card-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cards&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;up&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;down&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;viewTransitionService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setActiveViewTransitionNames&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`card-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;down&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, target the clicked element with a specific CSS rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;::view-transition-image-pair&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;target-card&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;size-up-and-down&lt;/span&gt; &lt;span class="n"&gt;ease-in&lt;/span&gt; &lt;span class="m"&gt;0.5s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5euhycitkuoazn57ge33.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5euhycitkuoazn57ge33.gif" alt="Angular Animation Magic: Unlock the Power of the View Transition API" width="192" height="192"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Again, the implementation details of the &lt;code&gt;[vtNameForActive]&lt;/code&gt; directive are omitted for brevity. Find its implementation within the &lt;a href="https://github.com/DmitryEfimenko/ngspot/tree/main/packages/view-transition/package?ref=dev.to"&gt;@ngspot/view-transition&lt;/a&gt; package on Github.&lt;/p&gt;

&lt;p&gt;The full code for making this cards animation can be &lt;a href="https://github.com/DmitryEfimenko/ngspot/tree/main/packages/view-transition/demo/src/lib/view-transition-demo/ordering?ref=dev.to"&gt;found here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  📦 Further Abstraction: The @ngspot/view-transition Package
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg0pe51kgan0ppnfq5da2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg0pe51kgan0ppnfq5da2.png" alt="Angular Animation Magic: Unlock the Power of the View Transition API" width="272" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this article there was a deep dive into challenges of integrating View Transition API with Angular. All the services and directives mentioned here can be found in the &lt;a href="https://github.com/DmitryEfimenko/ngspot/tree/main/packages/view-transition/package?ref=dev.to"&gt;@ngspot/view-transition&lt;/a&gt; Package.&lt;/p&gt;

&lt;p&gt;This package provides several more convenient directives to simplify handling various edge cases. Two notable directives are &lt;code&gt;[vtNameForRouting]&lt;/code&gt; and &lt;code&gt;[vtNameForRouterLink]&lt;/code&gt;, which streamline the use of view transitions during route navigation.&lt;/p&gt;

&lt;p&gt;Explore the &lt;a href="https://dmitryefimenko.github.io/ngspot/view-transition/isotope?ref=dev.to"&gt;various demos&lt;/a&gt; showcasing the animations made possible in Angular through the View Transition API and the &lt;code&gt;@ngspot/view-transition&lt;/code&gt; package.&lt;/p&gt;

&lt;p&gt;As a final thought, I encourage the amazing Angular community to try out this package and provide feedback. I'm confident that there are still many undiscovered challenges and edge cases when integrating the View Transition API with Angular. By collaborating on the API and the tools within the &lt;code&gt;@ngspot/view-transition&lt;/code&gt; package, we can collectively simplify this powerful integration! 🙌&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6brjjcrtzalxye8x8683.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6brjjcrtzalxye8x8683.jpg" alt="Angular Animation Magic: Unlock the Power of the View Transition API" width="800" height="297"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>articles</category>
      <category>angular</category>
      <category>animation</category>
      <category>viewtransitionapi</category>
    </item>
    <item>
      <title>New in ngx-errors 4.0</title>
      <dc:creator>Dmitry A. Efimenko</dc:creator>
      <pubDate>Thu, 16 May 2024 23:21:07 +0000</pubDate>
      <link>https://dev.to/dmitryefimenko/new-in-ngx-errors-40-20hm</link>
      <guid>https://dev.to/dmitryefimenko/new-in-ngx-errors-40-20hm</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--a3Z0Wx-Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/272/1%2AUbm7YB6ifLYs41XLdJJtqA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--a3Z0Wx-Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/272/1%2AUbm7YB6ifLYs41XLdJJtqA.png" alt="logo" width="272" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.npmjs.com/package/@ngspot/ngx-errors"&gt;@ngspot/ngx-errors&lt;/a&gt; library makes it easy to provide validation error messages in apps. The 4.0 release includes 3 large breaking changes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The library has undergone a large internal implementation rewrite making use of Angular signals and is now expected to be used together with Angular &amp;gt;17.1.&lt;/li&gt;
&lt;li&gt;The library moved away from using NgModules. All directives in the library are now standalone. The directives are available in the consuming applications through exported the &lt;code&gt;NGX_ERRORS_DECLARATIONS&lt;/code&gt; variable.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;ngxError&lt;/code&gt; directive is now expected to be used as a structural directive. This causes a potential breaking change in behavior. In the previous version, the HTML element that the &lt;code&gt;ngxError&lt;/code&gt; directive was applied to was always in the DOM. When the error was supposed to be invisible, the &lt;code&gt;hidden&lt;/code&gt; attribute was applied to that HTML element. With the new behavior, the DOM associated with the &lt;code&gt;ngxError&lt;/code&gt; directive gets completely removed when the error is supposed to be invisible.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is an example of using the new version of ngx-errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NGX_ERRORS_DECLARATIONS&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@ngspot/ngx-errors&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;standalone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;FormsModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NGX_ERRORS_DECLARATIONS&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;input [(ngModel)]="email" #emailModel="ngModel" required type="email" /&amp;gt;

    &amp;lt;div [ngxErrors]="emailModel.control"&amp;gt;
      &amp;lt;div *ngxError="'required'"&amp;gt;Email is required&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnInit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since the new version of the library does not rely on NgModules, a utility function was introduced to provide library configuration object. The utility function can be used either at the application level or at the component level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;provideNgxErrorsConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@ngspot/ngx-errors&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;bootstrapApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AppComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;provideNgxErrorsConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;showErrorsWhenInput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dirty&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;showMaxErrors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For more info and usage examples, see the &lt;a href="https://github.com/DmitryEfimenko/ngspot/blob/main/packages/ngx-errors/package/README.md"&gt;README&lt;/a&gt; file. See the &lt;a href="https://dmitryefimenko.github.io/ngspot/ngx-errors"&gt;demo here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.npmjs.com/package/@ngspot/ngx-errors-material"&gt;@ngspot/ngx-errors-material&lt;/a&gt; 4.0 package has undergone a similar set of changes. The directives are now standalone and are available through the &lt;code&gt;NGX_ERRORS_MATERIAL_DECLARATIONS&lt;/code&gt; variable. These declarations include declarations from &lt;code&gt;NGX_ERRORS_DECLARATIONS&lt;/code&gt; so no need to import these separately.&lt;/p&gt;

&lt;p&gt;The new release also includes one major feature. When using the &lt;code&gt;ngxError&lt;/code&gt; directive inside of the &lt;code&gt;&amp;lt;mat-form-field&amp;gt;&lt;/code&gt; component, there is no need to include a parent &lt;code&gt;ngxErrors&lt;/code&gt; directive. The &lt;code&gt;&amp;lt;mat-form-field&amp;gt;&lt;/code&gt; component serves the purpose of &lt;code&gt;ngxErrors&lt;/code&gt; directive. &lt;/p&gt;

&lt;p&gt;See the example below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NGX_ERRORS_MATERIAL_DECLARATIONS&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@ngspot/ngx-errors-material&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;standalone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;ReactiveFormsModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;MatInputModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;NGX_ERRORS_MATERIAL_DECLARATIONS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;form [formGroup]="form"&amp;gt;
      &amp;lt;mat-form-field&amp;gt;
        &amp;lt;mat-label&amp;gt;Name&amp;lt;/mat-label&amp;gt;

        &amp;lt;input matInput formControlName="name" /&amp;gt;

        &amp;lt;mat-error *ngxError="'required'"&amp;gt;Name is required&amp;lt;/mat-error&amp;gt;
      &amp;lt;/mat-form-field&amp;gt;
    &amp;lt;/form&amp;gt;
  `&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;fb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;FormBuilder&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;control&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;validators&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Validators&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See the &lt;a href="https://dmitryefimenko.github.io/ngspot/ngx-errors-material"&gt;demo here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Happy programming!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👏 Special thanks to &lt;a class="mentioned-user" href="https://dev.to/anaboca"&gt;@anaboca&lt;/a&gt; for reviewing this article.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>errors</category>
      <category>libraries</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Accessing directives located in child components in Angular</title>
      <dc:creator>Dmitry A. Efimenko</dc:creator>
      <pubDate>Mon, 16 Oct 2023 16:14:42 +0000</pubDate>
      <link>https://dev.to/dmitryefimenko/accessing-directives-located-in-child-components-in-angular-1fl8</link>
      <guid>https://dev.to/dmitryefimenko/accessing-directives-located-in-child-components-in-angular-1fl8</guid>
      <description>&lt;h2&gt;
  
  
  📜 Scenario
&lt;/h2&gt;

&lt;p&gt;In my application I had a directive that would track unsaved changes in a template-driven form. I won't bother you with the implementation of this directive, but its interface was as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Directive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;form[appUnsavedChangesTracker]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;standalone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;exportAs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unsavedChangesTracker&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;UnsavedChangesTrackerDirective&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;isDirty&lt;/span&gt;&lt;span class="na"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;setInitialValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There were two components. First, the smart component, which would provide data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`app-smart-component`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;standalone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;FormComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;app-form-component
      [formModel]="formModel$ | async"
      (save)="saveData($event)"
    &amp;gt;
    &amp;lt;/app-form-component&amp;gt;
  `&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;SmartComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;apiService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ApiService&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;formModel$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nx"&gt;saveData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="c1"&gt;// when data saved successfully we need to access the&lt;/span&gt;
      &lt;span class="c1"&gt;// UnsavedChangesTrackerDirective and call setInitialValue method&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second, a dumb component containing the form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`app-form-component`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;standalone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;FormsModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;UnsavedChangesTrackerDirective&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;form appUnsavedChangesTracker (ngSubmit)="saveForm()"&amp;gt;
      &amp;lt;input [ngModel]="formModel.name" name="name" /&amp;gt;

      // other input controls removed for brievety

      &amp;lt;button type="submit"&amp;gt;Save&amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;
  `&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;FormComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;formModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormModel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;save&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;EventEmitter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;FormModel&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;ViewChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;NgForm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;ngForm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NgForm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;saveForm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ngForm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;save&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ngForm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you'd like to learn more about the Smart/Dumb component approach, watch &lt;a href="https://youtu.be/4WBV-7PJ0jM?si=yQ15bAytz4jezlhd"&gt;this video&lt;/a&gt; by Joshua Morony.&lt;/p&gt;

&lt;p&gt;Here we encounter a problem: the &lt;code&gt;UnsavedChangesTrackerDirective&lt;/code&gt; is attached to the &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt;, which is located in a child component. However, the parent smart component needs to access the &lt;code&gt;UnsavedChangesTrackerDirective&lt;/code&gt; to update the initial form value when data is saved to the server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--knTv6gmz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tlqfry7dw3azrwagooo0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--knTv6gmz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tlqfry7dw3azrwagooo0.png" alt="Diagram showing where the UnsavedChangesTrackerDirective is located relatively to the other components" width="800" height="321"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can't simply query for the &lt;code&gt;UnsavedChangesTrackerDirective&lt;/code&gt; directive via &lt;code&gt;@ViewChild()&lt;/code&gt; since it's not located in the view of the parent component. We need another way.&lt;/p&gt;

&lt;h2&gt;
  
  
  💡 Initial solution
&lt;/h2&gt;

&lt;p&gt;There is a saying coined in a story by Francis Bacon:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If the mountain will not come to Muhammad, then Muhammad must go to the mountain&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A reference to the directive located in the child component cannot be injected from the parent. However, a directive located in the child component can inject anything up the injection tree. So, we could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a dedicated service provided somewhere higher in the injection tree, for example, at the level of the parent component.&lt;/li&gt;
&lt;li&gt;Inject that new service into the directive located in the child component.&lt;/li&gt;
&lt;li&gt;Then the directive could "register" itself within the injected service.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BY1jkWTj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/crrf84sog1u147o2baas.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BY1jkWTj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/crrf84sog1u147o2baas.png" alt="A diagram showing where UnsavedChangesTrackerService would be injected" width="800" height="311"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, here's the service where the directive will "register" itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;UnsavedChangesTrackerService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;tracker$$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;BehaviorSubject&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;UnsavedChangesTrackerDirective&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;tracker$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tracker$$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asObservable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nx"&gt;setTracker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tracker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UnsavedChangesTrackerDirective&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tracker$$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tracker&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the directive can inject the service and "register" itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;UnsavedChangesTrackerDirective&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;UnsavedChangesTrackerService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setTracker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// rest of the functionality is removed for brievety&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how we used a &lt;code&gt;BehaviorSubject&lt;/code&gt; inside the service and "nexted" the instance of the directive into it. The reason for this is that the parent component, along with the provided service, will instantiate first, and the child component will instantiate later. Consequently, there will be a brief moment when the reference to the directive will be &lt;code&gt;undefined&lt;/code&gt;. Whether or not this is a problem for you depends on when the tracker directive is being accessed. Using a &lt;code&gt;BehaviorSubject&lt;/code&gt; eliminates this issue.&lt;/p&gt;

&lt;p&gt;Now, we can update the parent smart component to provide the &lt;code&gt;UnsavedChangesTrackerService&lt;/code&gt; and use the referenced directive from it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`app-smart-component`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;standalone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;CommonModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FormComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;UnsavedChangesTrackerService&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;app-form-component
      [formModel]="formModel$ | async"
      (save)="saveData($event)"
    &amp;gt;
    &amp;lt;/app-form-component&amp;gt;
  `&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;SmartComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;apiService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ApiService&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;unsavedChangesTrackerService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;UnsavedChangesTrackerService&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;formModel$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nx"&gt;saveData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="c1"&gt;// when data saved successfully we need to access the&lt;/span&gt;
      &lt;span class="c1"&gt;// UnsavedChangesTrackerDirective and call setInitialValue method&lt;/span&gt;
      &lt;span class="nx"&gt;withLatestFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unsavedChangesTrackerService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tracker$&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nx"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tracker&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;tracker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setInitialValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ⚡ Reusable solution
&lt;/h2&gt;

&lt;p&gt;In the previous example, we figured out how to access a directive used in a child component from a parent component. We achieved this by using a service to register a reference to the directive. This was possible because we had access to the directive's code and could customize it according to our needs. But what if we don't own the directive that we need access?&lt;/p&gt;

&lt;p&gt;In such cases, we can still employ a similar approach, but we must make the service more generic so that it can register any type of directive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;RefTrackerService&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;ref$$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;BehaviorSubject&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;ref$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ref$$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asObservable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;definedRef$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ref$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;setRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ref$$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;withDefinedRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Source&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;OperatorFunction&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source$&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;source$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;switchMap&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;definedRef$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a few additional features in this version compared to the one we saw earlier, especially the &lt;code&gt;withDefinedRef&lt;/code&gt; function. This function is quite similar to the built-in RxJS &lt;code&gt;withLatestFrom&lt;/code&gt; function, but don't worry about it for now. There will be an example of how it's used later.&lt;/p&gt;

&lt;p&gt;Now that we have a service, we need a method to set the reference to any directive in that service. Angular provides  a standard way to access references to directives or components from the template. In our case, we'll want to write something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt;
  &lt;span class="na"&gt;unsavedChangesTracker&lt;/span&gt;
  &lt;span class="na"&gt;#ref&lt;/span&gt;&lt;span class="err"&gt;="&lt;/span&gt;&lt;span class="na"&gt;unsavedChangesTracker&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;
  &lt;span class="na"&gt;[trackRef]=&lt;/span&gt;&lt;span class="s"&gt;"ref"&lt;/span&gt;
  &lt;span class="na"&gt;(ngSubmit)=&lt;/span&gt;&lt;span class="s"&gt;"saveForm()"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;[ngModel]=&lt;/span&gt;&lt;span class="s"&gt;"formModel.name"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class="c"&gt;&amp;lt;!-- other input controls removed for brievety --&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Save&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the &lt;code&gt;#ref&lt;/code&gt; variable. We're defining a variable called "ref" which will contain an instance of &lt;code&gt;UnsavedChangesTrackerDirective&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Also, observe the &lt;code&gt;[trackRef]="ref"&lt;/code&gt;. We'll create a custom directive that allows us to save that "ref" variable in the &lt;code&gt;RefTrackerService&lt;/code&gt;. Here's the "trackRef" directive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Directive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[trackRef]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;standalone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;TrackRefDirective&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;refTrackerService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;RefTrackerService&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="nx"&gt;trackRef&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;ngOnInit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refTrackerService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trackRef&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the parent component can be updated like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-smart-component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;standalone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;CommonModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FormComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;RefTrackerService&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
  &amp;lt;ng-container *ngIf="formModel$ | async as formModel"&amp;gt;
    &amp;lt;app-form-component
      [formModel]="formModel"
      (save)="saveData($event)"
    &amp;gt;
    &amp;lt;/app-form-component&amp;gt;
  &amp;lt;/ng-container&amp;gt;
  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;SmartComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;apiService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ApiService&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;refTrackerService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;RefTrackerService&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;UnsavedChangesTrackerDirective&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;formModel$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nx"&gt;saveData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;saveData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refTrackerService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;withDefinedRef&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="nx"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unsavedChangesTrackerDirective&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;unsavedChangesTrackerDirective&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setInitialValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final working code can be found on &lt;a href="https://stackblitz.com/edit/stackblitz-starters-squcfz?file=src%2Fmain.ts"&gt;StackBlitz&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  ️⚙️ A note on the architecture
&lt;/h2&gt;

&lt;p&gt;There's something I don't quite like about the code example above - the &lt;code&gt;saveData()&lt;/code&gt; method and everything within it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The component contains logic for saving data, which makes the component much "smarter" than it needs to be, and this will make it harder to unit-test.&lt;/li&gt;
&lt;li&gt;Additionally, there is some imperative code in  the &lt;code&gt;subscribe()&lt;/code&gt; call.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;To understand why I believe these are problematic areas, please read my next article: "Dumb Components Are Overrated" (coming soon).&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>javascript</category>
      <category>rxjs</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Going from Jest to Karma/Jasmine in an NX monorepo</title>
      <dc:creator>Dmitry A. Efimenko</dc:creator>
      <pubDate>Mon, 20 Mar 2023 12:01:36 +0000</pubDate>
      <link>https://dev.to/dmitryefimenko/going-from-jest-to-karmajasmine-in-an-nx-monorepo-ech</link>
      <guid>https://dev.to/dmitryefimenko/going-from-jest-to-karmajasmine-in-an-nx-monorepo-ech</guid>
      <description>&lt;h3&gt;
  
  
  🤔 I honestly don’t understand the hype around Jest.
&lt;/h3&gt;

&lt;p&gt;People often cite the following for switching from Karma/Jasmine to Jest:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;#1 Jest has better performance&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;#2 Jest is easier to configure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;#3 Jest has easier CI/CD integration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;#4 Jest has a nicer reporting format&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here’s what I think about each of these bullet points:&lt;/p&gt;

&lt;h4&gt;
  
  
  #1 Jest has better performance
&lt;/h4&gt;

&lt;p&gt;There is some merit to this statement. Jest does NOT run unit tests in a browser. Therefore Jest does not have to spend time starting a browser, which takes a few seconds. Unit tests might be faster because they run in this “mocked” browser environment.&lt;/p&gt;

&lt;p&gt;There are also random claims without any proof around the internet that Jest is 2 to 3 times faster than Jasmine. Maybe. Although there’s this open issue on Jest with a ton of upvotes (at the moment of writing this blog): “&lt;a href="https://github.com/facebook/jest/issues/6694"&gt;Jest performance is at best 2x slower than Jasmine, in our case 7x slower&lt;/a&gt;”. So…&lt;/p&gt;

&lt;h4&gt;
  
  
  #2 Jest is easier to configure
&lt;/h4&gt;

&lt;p&gt;Again, maybe. I haven’t personally run into any issues configuring a project to use Karma. Usually, the tooling (NX or Angular/CLI) does the whole job! Then modifying the configuration is a piece of cake — as I’ll show toward the end of the article.&lt;/p&gt;

&lt;h4&gt;
  
  
  #3 Jest has easier CI/CD integration
&lt;/h4&gt;

&lt;p&gt;This is a matter of configuration. All that is needed is to provide a configuration to run the unit tests in CI/CD via a headless browser. It’s really not difficult, especially with the countless resources online showing how to.&lt;/p&gt;

&lt;h4&gt;
  
  
  #4 Jest has a nicer reporting format
&lt;/h4&gt;

&lt;p&gt;This is, again, a matter of configuration. Don’t like the default reporter that comes out of the box for an Angular project? No problem, use a different one! I’ll show what I do for each of my projects in the section “ &lt;strong&gt;Configuring Karma Reporter&lt;/strong&gt; ” below.&lt;/p&gt;

&lt;h3&gt;
  
  
  👍 Why I like Karma/Jasmine
&lt;/h3&gt;

&lt;p&gt;I really don’t want to make this article into a “Jest vs Karma/Jasmine” thing. Like Jest? Fine! Use it! And no need to continue reading this!&lt;/p&gt;

&lt;p&gt;Personally, I like Karma/Jasmine. Here are a couple of reasons why:&lt;/p&gt;

&lt;h4&gt;
  
  
  Confidence
&lt;/h4&gt;

&lt;p&gt;In my opinion, even if Jest was faster than Jasmine, I do not think that is reason enough to switch to Jest.&lt;/p&gt;

&lt;p&gt;In contrast to Jest, Karma runs unit tests in the real browser — the same environment where the project that was built will run. This ensures that the unit tests run the code in the real environment. Thus, a developer gains better confidence in the quality of the code. This is worth a lot!&lt;/p&gt;

&lt;h4&gt;
  
  
  Stability
&lt;/h4&gt;

&lt;p&gt;I have recently run into an &lt;a href="https://github.com/angular/angular/issues/49110"&gt;issue related to Jest&lt;/a&gt;, which causes the unit tests to error out. This completely blocked me and was the tipping point for me to convert my project to Karma/Jasmine and write this article.&lt;/p&gt;

&lt;p&gt;Without further ado, here are the steps I took to do the conversion.&lt;/p&gt;

&lt;h3&gt;
  
  
  ➡️ Converting Jest to Karma/Jasmine
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;I created two separate NX workspaces.&lt;/li&gt;
&lt;li&gt;I added an Angular library to both of them. However, one was configured to have Jest as a test runner and another one had a Karma/Jasmine test runner.&lt;/li&gt;
&lt;li&gt;Then I looked at the differences and applied them to my project!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here are all the files that got changed for one project:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2T5fO6Tn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/384/1%2AW0WHqGprhYJZieiBjykkmg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2T5fO6Tn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/384/1%2AW0WHqGprhYJZieiBjykkmg.png" alt="" width="384" height="299"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Uninstall Jest-related dependencies from the project&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm uninstall @nrwl/jest @types/jest jest jest-environment-jsdom jest-preset-angular ts-jest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Install Karma/Jasmine dependencies&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install -D @types/jasmine@4.0.0 jasmine-core@4.2.0 jasmine-spec-reporter@7.0.0 karma@6.4.0 karma-chrome-launcher@3.1.0 karma-coverage@2.2.0 karma-jasmine@5.1.0 karma-jasmine-html-reporter@2.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice, that I’m installing specific versions of the packages. I need to make sure that all of these packages are compatible with each other. I know that these packages are the right versions because these were the versions installed by Nx. Let the other folks figure out the right version combination 😉.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adjust files in the root directory&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;nx.json — update unitTestRunner to “karma” and point things to “karma.conf.js” instead of jest.config.js&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wZoQ_E5r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2AtQccFjBhEA-RoXzlCWHpVg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wZoQ_E5r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2AtQccFjBhEA-RoXzlCWHpVg.png" alt="" width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;karma.conf.js — a new file copied from the fresh NX workspace&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adjust files in each individual project&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;/projects/myProject/jest.config.ts — removed&lt;/p&gt;

&lt;p&gt;/projects/myProject/src/test-setup.ts — removed&lt;/p&gt;

&lt;p&gt;/projects/myProject/karma.conf.js — copied from the fresh NX workspace&lt;/p&gt;

&lt;p&gt;/projects/myProject/project.json — update targets.test.executor to be @angular-devkit/build-angular:karma and update related options (copy from fresh NX workspace and update paths)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tgbrpI1j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2AZ48IQImwnq43b_25zWwF0g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tgbrpI1j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2AZ48IQImwnq43b_25zWwF0g.png" alt="" width="800" height="99"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;tsconfig.lib.json — update the exclude property (copy from fresh NX workspace)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fcVNLo6a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2A_p4jc_BoeXO5_cE0CprPUQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fcVNLo6a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2A_p4jc_BoeXO5_cE0CprPUQ.png" alt="" width="800" height="78"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;tsconfig.lib.prod.json — modify this file if your project is publishable. Remove exclude property.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ale_bTrf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2A0RCu_soxvVemvOS7eyCshg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ale_bTrf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2A0RCu_soxvVemvOS7eyCshg.png" alt="" width="800" height="148"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;tsconfig.spec.json — remove files and compilerOptions.module properties. Adjust compilerOptions.types property.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lSVLIwo6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2An0J7iiigoEyHtKQCxO-pHQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lSVLIwo6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2An0J7iiigoEyHtKQCxO-pHQ.png" alt="" width="800" height="123"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All-in-all — not that many changes. Somebody should write a schematic to do this automatically though😁.&lt;/p&gt;

&lt;p&gt;At this point, the configuration is exactly the same as in a fresh Nx workspace. Now, time to improve!&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚙️ Configuring the Karma Reporter
&lt;/h3&gt;

&lt;p&gt;In this section, it will be apparent to see how easy it is to work with the Karma configuration and how to improve the reporter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--j1VKzpDR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2AYIgOKfMbPKPnpoHnIhbbWw.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--j1VKzpDR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2AYIgOKfMbPKPnpoHnIhbbWw.jpeg" alt="" width="800" height="222"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By default, Karma is configured to use two reporters: ['progress', 'kjhtml'] .&lt;/p&gt;

&lt;p&gt;The kjhtml reporter is the reporter used in the browser. I usually remove it. I also switch the chrome browser option to ChromeHeadless option so that the browser window does not open altogether.&lt;/p&gt;

&lt;p&gt;The process reporter is the default reporter used in the terminal. I don’t quite like how it prints out things. I usually switch it out to the karma-mocha-reporter .&lt;/p&gt;

&lt;p&gt;Install the mocha reporter&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i -D karma-mocha-reporter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the karma.config.js&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uDmefUum--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2AUbxlqaj3irD4uJWXz96sZg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uDmefUum--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2AUbxlqaj3irD4uJWXz96sZg.png" alt="" width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s it!&lt;/p&gt;

&lt;h3&gt;
  
  
  📖 Summary
&lt;/h3&gt;

&lt;p&gt;I mostly wrote this article for my own sake — in case I’ll need to do this again in the future. But I hope it’ll help fellow programmers who face similar dilemmas too.&lt;/p&gt;

&lt;p&gt;👏 &lt;strong&gt;Special thanks to&lt;/strong&gt; &lt;a href="https://github.com/anaboca"&gt;&lt;strong&gt;Ana Boca&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;for reviewing this article!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>karma</category>
      <category>jest</category>
      <category>programming</category>
      <category>unittesting</category>
    </item>
    <item>
      <title>Have a Day at the Spa working on Your SPA with Remote Data</title>
      <dc:creator>Dmitry A. Efimenko</dc:creator>
      <pubDate>Wed, 20 Jul 2022 07:51:15 +0000</pubDate>
      <link>https://dev.to/dmitryefimenko/have-a-day-at-the-spa-working-on-your-spa-with-remote-data-5epo</link>
      <guid>https://dev.to/dmitryefimenko/have-a-day-at-the-spa-working-on-your-spa-with-remote-data-5epo</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qSAxF5Z---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A10_sVLy6NE2QVuszA0niVQ.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qSAxF5Z---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A10_sVLy6NE2QVuszA0niVQ.jpeg" alt="" width="880" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  📑 TLDR:
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Use the &lt;strong&gt;RemoteData&lt;/strong&gt; data structure from the &lt;a href="https://www.npmjs.com/package/@ngspot/remote-data"&gt;@ngspot/remote-data&lt;/a&gt; library to describe data being requested from an API.&lt;/li&gt;
&lt;li&gt;Use RxJS and a custom operator trackRemoteData from the &lt;a href="https://www.npmjs.com/package/@ngspot/remote-data-rx"&gt;@ngspot/remote-data-rx&lt;/a&gt; library for best results.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Have you ever written a component or a service where an HTTP call is made to request some data from an API and display it to the user? That’s mostly a rhetorical question — most applications do that. There are common scenarios that need to be addressed when dealing with remote data.&lt;/p&gt;

&lt;h4&gt;
  
  
  🤔 Naive Approach Example
&lt;/h4&gt;

&lt;p&gt;Many times, I’ve written components and services where HTTP calls are made to request some data. Previously, my approach used an imperative style of coding (before I learned the magical power of data streams).&lt;/p&gt;


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


&lt;p&gt;Although there are many issues with the code above (like manually subscribing, not using OnPush change detection, not using trackBy for the ngFor loop, a bug due to a potential racing condition, etc.), the code above works. Please ignore the imperfections for the time being. Most of them will be taken care of by the end of the article and the rest are omitted for simplicity.&lt;/p&gt;

&lt;p&gt;Next, I realized that the call to the API takes time and I need to display some loading template while the data is loading. So I added theisLoading property to keep track of that!&lt;/p&gt;


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


&lt;p&gt;Simple enough! But wait, what if the API returns an error? I want to display something to the user in this circumstance. I know how to handle this! So I introduced another property: error!&lt;/p&gt;


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


&lt;p&gt;Ufff, three properties to keep track of all the possible state options and a whole bunch of code to maintain these three properties?! And that’s just for one API call. What if there are multiple API calls?? What I have does not have all possible states either. There’s one more — a case when data has not been requested yet.&lt;/p&gt;

&lt;p&gt;In the example above, data gets loaded automatically when the component initializes, but that could be different. What if you want to display a prompt to the user with some instructions for the case when data has not yet been requested? That is a lot of spaghetti code!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JgMBEG3l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn-images-1.medium.com/max/498/0%2AlBdQo0Mvfsky32Vs.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JgMBEG3l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn-images-1.medium.com/max/498/0%2AlBdQo0Mvfsky32Vs.gif" alt="" width="498" height="278"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  💡 RemoteData to the Rescue!
&lt;/h4&gt;

&lt;p&gt;The spaghetti code of handling all the possible states can be solved with a data structure that encapsulates all these possible cases:&lt;/p&gt;


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


&lt;p&gt;You can get better type safety if you create a dedicated type for each of the states and then use a &lt;a href="https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#union-exhaustiveness-checking"&gt;TypeScript union feature&lt;/a&gt;.&lt;/p&gt;


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


&lt;p&gt;Now, I will create a few builder functions that return the RemoteData for each possible state of the request (1) not asked, (2) loading, (3) success, and (4) error.&lt;/p&gt;


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


&lt;p&gt;With all of this in place, here is the rewritten component:&lt;/p&gt;


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


&lt;p&gt;This is much cleaner! Only one property to maintain and this property handles all use-cases. The &lt;a href="https://www.npmjs.com/package/@ngspot/remote-data"&gt;@ngspot/remote-data&lt;/a&gt; library has essentially been rebuilt. Feel free to use it!&lt;/p&gt;

&lt;p&gt;But I can do better! Read on.&lt;/p&gt;

&lt;h4&gt;
  
  
  💪 Using the power of RxJS
&lt;/h4&gt;

&lt;p&gt;Remember the numerous issues mentioned at the beginning of the article?&lt;/p&gt;

&lt;p&gt;Among them is a bug related to a racing condition. If a user presses the button “Load products” many times quickly, then many requests will be fired up. The chances are, that due to the timing of the network, the responses for these requests will be returned out of order. The response for the request associated with the very first click could end up coming back last. This means that the UI might not display the freshest data.&lt;/p&gt;

&lt;p&gt;RxJS is perfect for handling asynchronous data streams. It has mechanisms for dealing with this sort of situation. In addition, it makes it easy to use OnPush change detection, which improves the performance of your application and can &lt;a href="https://youtu.be/tWy8zaWvkvk"&gt;improve the general quality of your components&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Without further ado, here is the rewritten component using reactive streams and the RemoteData data structure.&lt;/p&gt;


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


&lt;p&gt;This solution is much more robust. There are no manual subscriptions. Data is propagated to the template via reactive streams with theasync pipe, which allows the use of OnPush change detection. Finally, race conditions are handled via the switchMap operator, which automatically cancels any previous requests in flight and starts a new one.&lt;/p&gt;

&lt;p&gt;RxJS allows building a custom operator using multiple existing operators. That’s what I had done with the example above — I took the operators used to handle the RemoteData loading, success, and error cases and extracted these operators into a custom operator called trackRemoteData. Find the trackRemoteDataoperator under &lt;a href="https://www.npmjs.com/package/@ngspot/remote-data-rx"&gt;@ngspot/remote-data-rx&lt;/a&gt; library. There are a couple more bells and whistles built-in.&lt;/p&gt;

&lt;p&gt;With that, the code becomes even simpler.&lt;/p&gt;


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


&lt;h4&gt;
  
  
  🧡 Credit Where Credit is Due
&lt;/h4&gt;

&lt;p&gt;There are similar solutions for handling remote data out there. I have tried most of them, but none provided me with the exact feature-set I wanted. Here are a few of them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where it all began: “&lt;a href="http://blog.jenkster.com/2016/06/how-elm-slays-a-ui-antipattern.html"&gt;How Elm Slays a UI Antipattern&lt;/a&gt;”&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/ngx-remotedata"&gt;https://www.npmjs.com/package/ngx-remotedata&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/daiscog/ngx-http-request-state"&gt;https://github.com/daiscog/ngx-http-request-state&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nonetheless, these solutions inspired me to create the two libraries that I now use in most of my projects. I hope you find them helpful too.&lt;/p&gt;

&lt;p&gt;I wish you happy programming!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👏 Special thanks to&lt;/strong&gt; &lt;a href="https://github.com/anaboca"&gt;&lt;strong&gt;Ana Boca&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;for reviewing this article.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>rxjs</category>
      <category>javascript</category>
      <category>angular</category>
      <category>webdev</category>
    </item>
    <item>
      <title>What’s new in ngspot/ngx-errors 3.1.3?</title>
      <dc:creator>Dmitry A. Efimenko</dc:creator>
      <pubDate>Fri, 14 Jan 2022 09:31:19 +0000</pubDate>
      <link>https://dev.to/dmitryefimenko/whats-new-in-ngspotngx-errors-313-2njm</link>
      <guid>https://dev.to/dmitryefimenko/whats-new-in-ngspotngx-errors-313-2njm</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--a3Z0Wx-Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/272/1%2AUbm7YB6ifLYs41XLdJJtqA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--a3Z0Wx-Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/272/1%2AUbm7YB6ifLYs41XLdJJtqA.png" alt="" width="272" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🚀 Feature 1: integration with @angular/material
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/@ngspot/ngx-errors"&gt;@ngspot/ngx-errors&lt;/a&gt; is a library that makes it easy to provide validation error messages in apps. The 3.1.3 release takes this functionality a step further for applications that use @angular/material.&lt;/p&gt;

&lt;p&gt;Let's say that there is an application where the errors are configured to only show to the users when the form is submitted:&lt;/p&gt;


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


&lt;p&gt;… and there is a component that uses a FormGroup inside of a template that has an Angular Material input within the form field with corresponding validation error:&lt;/p&gt;


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


&lt;p&gt;Everything is straightforward, however, if the user types something in the input and clicks away, the input gets highlighted red, but the error message isn’t shown!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aZqZvfY5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://cdn-images-1.medium.com/max/406/1%2A9kTe-tP3wuSrU0FdNUpdpA.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aZqZvfY5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://cdn-images-1.medium.com/max/406/1%2A9kTe-tP3wuSrU0FdNUpdpA.gif" alt="" width="406" height="140"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It makes sense that the error message isn’t shown — the application was configured to show errors only when the form is submitted. So why is input getting highlighted prematurely? Turns out, Material Inputs has its &lt;a href="https://material.angular.io/components/input/overview#changing-when-error-messages-are-shown"&gt;own logic that determines when inputs are in an error state&lt;/a&gt;. By default, the input enters an error state when it’s “touched”. This is lucky because ngx-errors has the same default, but the moment the developer wants to customize behavior, things get out of sync.&lt;/p&gt;

&lt;p&gt;Version 3.1.3 synchronizes the input error state with the errors actually shown to the user. The input will only get highlighted if there are errors shown to the user.&lt;/p&gt;

&lt;h3&gt;
  
  
  🚀Feature 2: Custom error state matchers
&lt;/h3&gt;

&lt;p&gt;The @angular/material team has done a great job designing the API for &lt;code&gt;errorStateMatcher&lt;/code&gt;. This API has the ability to provide a global errorStateMatcher or override it via an @Input at the  level giving the developer a good amount of flexibility.&lt;/p&gt;

&lt;p&gt;@ngspot/ngxerrors also provides flexibility to the developer since it is possible to provide a global configuration for when to show the errors as well as override it at lower levels — in the component templates where ngxErrors and ngxError directives are used. Here’s an example of this functionality from the docs:&lt;/p&gt;


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


&lt;p&gt;The showWhen input would allow the developer to override the default configuration. However, in the previous versions, the developer could only use set values for the showWhen input: 'touched' 'dirty' 'touchedAndDirty' and 'formIsSubmitted' .&lt;/p&gt;

&lt;p&gt;With the release of version 3.1.3, the developer can now provide their configurations. &lt;a href="https://www.npmjs.com/package/@ngspot/ngx-errors#providing-custom-logic-for-displaying-errors"&gt;See the docs for more info&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  🚀Feature 3: new config option — showMaxErrors
&lt;/h3&gt;

&lt;p&gt;Finally, the icing on the cake — a new configuration option that enables developers to configure how many total errors are allowed to be displayed per the ngxErrors block at the same time.&lt;/p&gt;

&lt;p&gt;Imagine that the form control has multiple validation rules associated with it — a pretty common scenario. Here’s how the template would look:&lt;/p&gt;


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


&lt;p&gt;It is possible that these validation rules would be invalid at the same time, in which case, all error messages would be displayed to the user:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pyNfVd63--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/214/1%2AiO7pF3jNO7VXtAkrP2qMQA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pyNfVd63--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/214/1%2AiO7pF3jNO7VXtAkrP2qMQA.png" alt="" width="214" height="156"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This by itself does not sound like the end of the world. However, it gets ugly real fast if another input is added below the first one:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XABwhP-u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/211/1%2A8XYlIdzie8b45A6cb89eVA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XABwhP-u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/211/1%2A8XYlIdzie8b45A6cb89eVA.png" alt="" width="211" height="152"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The problem has to do with the implementation of the current styles. &lt;a href="https://github.com/angular/components/issues/4580"&gt;There’s been an open issue about it since 2017&lt;/a&gt;! And that’s why open source is great— people have been coming up with great workarounds for this issue. A developer could choose any of these or use the showMaxErrors configuration option of @ngspot/ngx-errors to only display one error at a time. This might be a good option if you’d like to ease the cognitive load on your users.&lt;/p&gt;

&lt;p&gt;I wish you happy programming!&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;👏 Special thanks to&lt;/strong&gt; &lt;a href="https://github.com/anaboca"&gt;&lt;strong&gt;Ana Boca&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;for reviewing, testing, and providing some of the code for this article.&lt;/strong&gt;
&lt;/h4&gt;

</description>
      <category>angular</category>
      <category>errors</category>
      <category>libraries</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Declarative Route Path Management in Angular Apps — Even Better Than Best Route Forward</title>
      <dc:creator>Dmitry A. Efimenko</dc:creator>
      <pubDate>Wed, 07 Jul 2021 06:49:25 +0000</pubDate>
      <link>https://dev.to/dmitryefimenko/declarative-route-path-management-in-angular-apps-even-better-than-best-route-forward-75e</link>
      <guid>https://dev.to/dmitryefimenko/declarative-route-path-management-in-angular-apps-even-better-than-best-route-forward-75e</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--f36-fPri--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/272/1%2AUUSQoadZ8xjbfibM6_Af1A.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--f36-fPri--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/272/1%2AUUSQoadZ8xjbfibM6_Af1A.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Declarative Route Path Management in Angular Apps — Even Better Than Best Route Forward
&lt;/h3&gt;

&lt;p&gt;When I read &lt;a href="https://medium.com/u/b889ae02aa26"&gt;Netanel Basal's&lt;/a&gt; article — “&lt;a href="https://netbasal.com/best-route-forward-declarative-route-path-management-in-angular-apps-a44c7e39c340"&gt;Best Route Forward — Declarative Route Path Management in Angular Apps&lt;/a&gt;”— I wanted to try out the solution to route path management described in the article right away in the apps I work on. The solution in Netanel’s article is intended to help with managing routes in large Angular apps. The idea is great! However, I quickly discovered that the solution does not quite work for the apps that have many feature modules with their own routes — i.e large apps. If these feature modules have their own lazy feature modules with their own routes, a single service class really does not cut it. Let me demonstrate what I mean using a simplified example.&lt;/p&gt;

&lt;p&gt;Here is an AppModule with the following routes:&lt;/p&gt;


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


&lt;p&gt;There are two lazy modules for routes “products” and “customers”. The Products module contains a feature module as well. Here are the associated feature route declarations:&lt;/p&gt;

&lt;p&gt;Products routes:&lt;/p&gt;


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


&lt;p&gt;EditModule routes:&lt;/p&gt;


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


&lt;p&gt;A class with methods, like in Netanel’s article, works great for a flat route structure:&lt;/p&gt;


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


&lt;p&gt;But what can be done with the routes for the lazy feature module? Below are three naive options that come to mind.&lt;/p&gt;

&lt;h3&gt;
  
  
  🤔 Naive Option #1
&lt;/h3&gt;

&lt;p&gt;Create methods only at the top-level disregarding the nested nature of routes:&lt;/p&gt;


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


&lt;p&gt;Here’s how it would be used:&lt;/p&gt;


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


&lt;p&gt;This approach has some clear downsides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The methods for a feature module are managed within the same class.&lt;/li&gt;
&lt;li&gt;The method names are long and repetitive.&lt;/li&gt;
&lt;li&gt;Each child route explicitly specifies the parent /products path.&lt;/li&gt;
&lt;li&gt;This will get really ugly for child routes of the edit feature module.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🤔 Naive Option #2
&lt;/h3&gt;

&lt;p&gt;Have the products method return an object to attempt to represent the nested nature of routes:&lt;/p&gt;


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


&lt;p&gt;Now, something like this can be typed:&lt;/p&gt;


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


&lt;p&gt;This feels a bit better, but there are still a few downsides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The methods for a feature module are managed within the same class.&lt;/li&gt;
&lt;li&gt;The root products route is lost.&lt;/li&gt;
&lt;li&gt;Each child route explicitly specifies the parent /products path.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🤔 Naive Option #3
&lt;/h3&gt;

&lt;p&gt;Create a separate class for products routes:&lt;/p&gt;


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


&lt;p&gt;This approach also lets the route be used like so:&lt;/p&gt;


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


&lt;p&gt;Now, the ability to manage child routes in separate files is gained, but &lt;strong&gt;the ability to use Angular’s dependency injection has been lost&lt;/strong&gt;! The following downsides still exist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The root products route is lost (could add a method root()?).&lt;/li&gt;
&lt;li&gt;The explicit use of this.parentPath does not feel DRY.&lt;/li&gt;
&lt;li&gt;the parentPath needs knowledge of where it is in the hierarchy of lazy feature routes. Otherwise resulting URL will be wrong.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  💪 RoutePathBuilder
&lt;/h3&gt;

&lt;p&gt;Long story short, I decided to create a solution that will keep all the benefits of Netanal’s solution and add the features I was looking for:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Original features&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A single source of truth for each path in the application&lt;/li&gt;
&lt;li&gt;Strong typings&lt;/li&gt;
&lt;li&gt;Access to Angular’s dependency injection&lt;/li&gt;
&lt;li&gt;Use of absolute links (meaning, the generated link is absolute)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;New features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Managing routes of feature modules via separate classes&lt;/li&gt;
&lt;li&gt;Use of property chaining to reflect the nested nature of the routes&lt;/li&gt;
&lt;li&gt;No explicit use of parentPath in method implementations. Use of relative URL parts for the assembly of the URLs.&lt;/li&gt;
&lt;li&gt;Flexible return type: to access either a &lt;code&gt;url&lt;/code&gt;, a &lt;code&gt;urlTree&lt;/code&gt; (useful for RouteGuards), or seamlessly &lt;code&gt;navigate()&lt;/code&gt; to the desired route&lt;/li&gt;
&lt;li&gt;A utility function to simplify the use of the &lt;code&gt;this.route.createUrlTree(commands)&lt;/code&gt; method&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Say hello to &lt;a href="https://www.npmjs.com/package/@ngspot/route-path-builder"&gt;@ngspot/route-path-builder&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The @ngspot/route-path-builder library consists of a single abstract class — &lt;code&gt;RoutePathBuilder&lt;/code&gt;. Here is how the new library will describe the routes using the hypothetical example from above.&lt;/p&gt;


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


&lt;p&gt;With this setup, inject the AppRoutes anywhere in the app and use it!&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;url&lt;/code&gt; and &lt;code&gt;urlFromCommands&lt;/code&gt; methods return an instance of the &lt;code&gt;AppUrl&lt;/code&gt; class. This class has the &lt;code&gt;url&lt;/code&gt; and &lt;code&gt;urlTree&lt;/code&gt; properties and a &lt;code&gt;navigate()&lt;/code&gt; method. With this in mind, here’s how the AppRoutes service can be used:&lt;/p&gt;


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


&lt;p&gt;Here’s how AppRoutes can be used in a route resolver:&lt;/p&gt;


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


&lt;p&gt;The RoutePathBuilder provides a &lt;code&gt;root()&lt;/code&gt; method that returns the AppUrl for the root path of a given feature module. For example:&lt;/p&gt;


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


&lt;p&gt;The RoutePathBuilder also exposes two protected properties — router and injector. The router is there as a convenient way to access the router in case it is needed without having to inject an extra service in the component or service. The injector is also there to avoid providing dependencies in the constructor. For example:&lt;/p&gt;


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


&lt;p&gt;Of course, dependencies can also be provided in the constructor, but in that case, Injector needs to be added to the dependencies and &lt;code&gt;super(injector)&lt;/code&gt; added the to the body of the constructor.&lt;/p&gt;

&lt;p&gt;Notice the use of { providedIn: 'any' } for the services that extend RoutePathBuilder. This means that a separate instance of that service will be created for each lazy feature module of the app. This is important because the injector should be the reference to the injector of that lazy module, not the injector of the root module. This way, accessing a service declared in the lazy feature module will not fail.&lt;/p&gt;

&lt;p&gt;I hope you find the &lt;a href="https://www.npmjs.com/package/@ngspot/route-path-builder"&gt;@ngspot/route-path-builder&lt;/a&gt; library helpful. I wish you happy navigating!&lt;/p&gt;

&lt;h4&gt;
  
  
  👏 &lt;strong&gt;Special thanks to&lt;/strong&gt; &lt;a href="https://github.com/anaboca"&gt;&lt;strong&gt;Ana Boca&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;for reviewing, testing, and providing some of the code for this article.&lt;/strong&gt;
&lt;/h4&gt;

&lt;h3&gt;
  
  
  🚀 In Case You Missed It
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/ngspot"&gt;@ngspot&lt;/a&gt; has more goodies! For example, &lt;a href="https://github.com/ngspot/ngx-errors"&gt;ngx-errors&lt;/a&gt; — an &lt;a href="https://twitter.com/angular/status/1355259422545752076"&gt;Angular-endorsed&lt;/a&gt; library for displaying validation messages in forms. More will be coming soon!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Follow me on&lt;/em&gt; &lt;a href="https://medium.com/@dmitrief"&gt;&lt;em&gt;Medium&lt;/em&gt;&lt;/a&gt;&lt;em&gt;or&lt;/em&gt; &lt;a href="https://twitter.com/dmitryaefimenko"&gt;&lt;em&gt;Twitter&lt;/em&gt;&lt;/a&gt;&lt;em&gt;to read more about Angular and JS!&lt;/em&gt;&lt;/p&gt;




</description>
      <category>angular</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>Lost in Translation… Strings</title>
      <dc:creator>Dmitry A. Efimenko</dc:creator>
      <pubDate>Wed, 21 Aug 2019 08:42:48 +0000</pubDate>
      <link>https://dev.to/dmitryefimenko/lost-in-translation-strings-57c9</link>
      <guid>https://dev.to/dmitryefimenko/lost-in-translation-strings-57c9</guid>
      <description>&lt;h4&gt;
  
  
  &lt;strong&gt;i18n for Server-Side Rendered Angular applications&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--n3E54_9z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2A8BLUuSp0XfCQHVl5vdY9Kg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--n3E54_9z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2A8BLUuSp0XfCQHVl5vdY9Kg.jpeg" alt="" width="800" height="246"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ev2aPWBY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/743/0%2ADnEpQN4H4d5ZxRW8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ev2aPWBY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/743/0%2ADnEpQN4H4d5ZxRW8.png" alt="" width="743" height="2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://medium.com/angular-in-depth"&gt;&lt;strong&gt;&lt;em&gt;AngularInDepth&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;&lt;em&gt;is moving away from Medium. More recent articles are hosted on the new platform&lt;/em&gt;&lt;/strong&gt; &lt;a href="https://indepth.dev/angular/"&gt;&lt;strong&gt;&lt;em&gt;inDepth.dev&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;&lt;em&gt;. Thanks for being part of indepth movement!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  🤔 The Background
&lt;/h4&gt;

&lt;p&gt;What does i18n stand for and why is there an “18” in the middle? Even as an engineer with more than ten years of experience in the field I had no idea until I looked it up. It’s the number of letters between “i” and “n” in the word “internationalization.” So, i18n is internationalization. Pretty neat. One of the &lt;a href="https://www.w3.org/International/questions/qa-i18n"&gt;definitions&lt;/a&gt; of i18n is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The design and development of a product, application or document content that &lt;strong&gt;enables&lt;/strong&gt; easy localization for target audiences that vary in culture, region, or language.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By following the i18n definition link above, we can see that there are multiple areas of development that i18n touches on. However, the area we’ll concentrate on in this article is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Separating localizable elements from source code or content, such that localized alternatives can be loaded or selected based on the user’s international preferences as needed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In essence, whatever should be displayed in different languages needs to be separated out from the meat of the code to enable its maintainability.&lt;/p&gt;

&lt;p&gt;In the article, we will explore how to implement our translation strings in a maintainable manner, enable the application to load only necessary resources, and allow browser memorization of the selected language. Then we will enable Server-Side Rendering (SSR) and solve issues encountered during enabling SSR in the Angular application.&lt;/p&gt;

&lt;p&gt;The article is split up in the following parts:&lt;/p&gt;

&lt;p&gt;Part 1. Setting the Scene&lt;/p&gt;

&lt;p&gt;Part 2. Adding SSR to the App&lt;/p&gt;

&lt;p&gt;Part 3. Solution 1 — Fix via Providing a Separate I18nModule for the Server&lt;/p&gt;

&lt;p&gt;Part 4. Solution 2 — Provide Everything in a Single Module&lt;/p&gt;

&lt;p&gt;Part 5. Improve Performance with TransferState&lt;/p&gt;

&lt;p&gt;Part 6. Are We There Yet?&lt;/p&gt;

&lt;p&gt;In the first part of this article, we will follow simple instructions for setting up an Angular application and adding i18n capabilities to it. Beginner-level developers may want to take a deep dive into Part 1. More advanced developers may glance at the code in the following sections and proceed to “Part 2. Adding SSR to the App” to find out what obstacles adding SSR will create and how to solve them.&lt;/p&gt;

&lt;h3&gt;
  
  
  📝 &lt;strong&gt;Part 1 of 6: Setting the Scene&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;For the purposes of this article, we’ll work with a bare-bones Angular application generated with &lt;a href="https://cli.angular.io/"&gt;&lt;em&gt;AngularCLI&lt;/em&gt;&lt;/a&gt;. To follow along with the article, we will generate an app using the command (assuming the Angular CLI installed globally):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ng new ssr-with-i18n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For the sake of the example let’s add a couple of components:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ng g c comp-a
ng g c comp-b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now, we will replace the contents of &lt;em&gt;app.component.html&lt;/em&gt; with these two components:&lt;/p&gt;


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



&lt;p&gt;*** The code up to this point is available &lt;a href="https://github.com/DmitryEfimenko/ssr-with-i18n/tree/step-1-a"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  🗺️ &lt;strong&gt;Let’s Add i18n&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;As with most things in coding, there are many ways to skin a cat. Originally, I wanted to use the framework-independent library &lt;a href="https://www.i18next.com/"&gt;&lt;em&gt;i18next&lt;/em&gt;&lt;/a&gt; with an Angular wrapper: &lt;a href="https://github.com/Romanchuk/angular-i18next"&gt;&lt;em&gt;angular-i18next&lt;/em&gt;&lt;/a&gt;. However, there is currently an &lt;a href="https://github.com/Romanchuk/angular-i18next/pull/11#issuecomment-364725022"&gt;unfortunate limitation&lt;/a&gt; with &lt;em&gt;angular-i18next&lt;/em&gt;: it’s not capable of switching language on the fly, which is a show-stopper for me.&lt;/p&gt;

&lt;p&gt;In this article, we’ll use a popular library: &lt;a href="https://github.com/ngx-translate/core"&gt;&lt;em&gt;ngx-translate&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; : The concepts of organizing modules and code described in this article do not apply just to ngx-translate. An application can use the new and shiny &lt;a href="https://netbasal.com/introducing-transloco-angular-internationalization-done-right-54710337630c"&gt;transloco&lt;/a&gt; library, which was released the date of writing this article (8/15/2019)&lt;em&gt;.&lt;/em&gt; The reader might even be trying to solve an issue that has nothing to do with translations. Therefore, this article is helpful for anybody who’s trying to solve a SSR related issue.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Using &lt;em&gt;ngx-translate&lt;/em&gt; will allow us to store our strings in separate JSON files (a file per language) where each string will be represented by a key-value pair. The key is a string identifier and the value is the translation of the string.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Install dependencies&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In addition to the core library, we’ll install the &lt;em&gt;http-loader&lt;/em&gt; library which will enable loading translations on-demand.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install @ngx-translate/core @ngx-translate/http-loader --save
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;2. Add the code&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The directions for the &lt;em&gt;ngx-translate&lt;/em&gt; package suggest adding relevant code directly to the AppModule. However, I think we can do better. Let’s create a separate module that will encapsulate i18n related logic.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ng g m i18n --module app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This will add a new file: /i18n/i18n.module.ts and reference it in the app.module.ts.&lt;/p&gt;

&lt;p&gt;Modify the i18n.module.ts file according to the &lt;a href="https://github.com/ngx-translate/core#configuration"&gt;documentation&lt;/a&gt;. The full code file is below:&lt;/p&gt;


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



&lt;p&gt;Nothing fancy is going on. We just added the TranslateModule and configured it to use the HttpClient to load translations. We exported TranslateModule as well to make the pipe transform available in the AppModule and in HTML templates. In the constructor, we specified available languages and used a function provided by &lt;em&gt;ngx-translate&lt;/em&gt; to get and use the browser’s default language.&lt;/p&gt;

&lt;p&gt;By default, the TranslateHttpLoader will load translations from the /assets/i18n/ folder, so let’s add a couple of files there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;/assets/i18n/en.json&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "compA": "Component A works",
  "compB": "Component B works"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;/assets/i18n/ru.json&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "compA": "Компонент А работает",
  "compB": "Компонент Б работает"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Note: we are using a single file per language. In more complex applications there’s nothing limiting us from creating files based on locale, e.g. en-US.json, en-Gb.json. These will be treated essentially as separate translations.&lt;/p&gt;

&lt;p&gt;With this configuration, we should be able to update our component templates to use the translation strings instead of hard-coded text.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// comp-a.component.html
&amp;lt;p&amp;gt;{{'compA' | translate}}&amp;lt;/p&amp;gt;

// comp-b.component.html
&amp;lt;p&amp;gt;{{'compB' | translate}}&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Run the application and notice that it’s using the translations from the en.json file. Let’s add a component that will let us switch between the two languages.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ng g c select-language --inlineStyle --inlineTemplate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Update the contents of the select-language.component.ts file.&lt;/p&gt;


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



&lt;p&gt;The &lt;em&gt;ngx-translate&lt;/em&gt; library allows us to switch languages via a simple translate.use() API call. It also allows us to determine the currently selected language by querying the translate.currentLang property.&lt;/p&gt;

&lt;p&gt;Insert the new component in the app.component.html file after the h1 tag.&lt;/p&gt;


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


&lt;p&gt;Run the application and see that the language can now be switched on the fly. Selecting a different language will request the appropriate .json file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bJI6QiB7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://cdn-images-1.medium.com/max/600/1%2AbW-KhAzpwLC9qcyQt72kew.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bJI6QiB7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://cdn-images-1.medium.com/max/600/1%2AbW-KhAzpwLC9qcyQt72kew.gif" alt="" width="600" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, if we select the language ru and refresh the browser, we’ll see that the page still loads with the language en selected. The browser does not have a mechanism for remembering the selected language. Let’s fix that.&lt;/p&gt;

&lt;h4&gt;
  
  
  🙄 &lt;strong&gt;Memorizing the Selected Language&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;The Angular community has made many &lt;a href="https://github.com/ngx-translate/core#plugins"&gt;plugins&lt;/a&gt; enhancing the functionality of the &lt;em&gt;ngx-translate&lt;/em&gt; package. One of them is exactly what we need —&lt;em&gt; &lt;/em&gt;&lt;a href="https://github.com/jgpacheco/ngx-translate-cache"&gt;&lt;em&gt;ngx-translate-cache&lt;/em&gt;&lt;/a&gt;. By following instructions, we’ll (1) install the package&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install ngx-translate-cache --save
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;and (2) use it inside of the I18nModule.&lt;/p&gt;


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



&lt;p&gt;Now, if we select the language ru and refresh the browser we’ll see that it remembered our choice. Notice, that we selected 'Cookie' as a place to store the selected language. The default selection for this option is 'LocalStorage' . However, LocalStorage is not accessible on the server. A big part of this article has to do with enabling SSR, so we’re being a little bit proactive here and storing the language selection in the place where a server can read it.&lt;/p&gt;

&lt;p&gt;Up until now there really was nothing special about this article. We simply followed instructions posted in relevant packages and encapsulated the i18n related logic in a separate module. Adding SSR has some tricky parts, so let’s take a closer look.&lt;/p&gt;

&lt;p&gt;*** The code up to this point is available &lt;a href="https://github.com/DmitryEfimenko/ssr-with-i18n/tree/step-1-b"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  💪 &lt;strong&gt;Part 2 of 6: Adding SSR to the App&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://juristr.com/blog/2019/08/thank-you-angular-cli-team/"&gt;Angular CLI is amazing&lt;/a&gt;! In particular, its schematics feature allows us to add new capabilities to the app using a simple command. In this case, we’ll run the following command to add SSR capabilities.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ng add [@nguniversal/express-engine](http://twitter.com/nguniversal/express-engine) --clientProject ssr-with-i18n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Running this command updated and added a few files.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7L5rj0yh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/228/1%2Ap68AZjJGpLaKNEl9eIeULg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7L5rj0yh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/228/1%2Ap68AZjJGpLaKNEl9eIeULg.png" alt="" width="228" height="248"&gt;&lt;/a&gt;files added/changed after ng add command&lt;/p&gt;

&lt;p&gt;If we look at the package.json file, we’ll see that now we have a few new scripts that we can execute. The two most important are: (1) build:ssr and (2) serve:ssr . Let’s run these commands and see what happens.&lt;/p&gt;

&lt;p&gt;Both commands run successfully. However, when we load the website in the browser, we get an error.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TypeError: Cannot read property 'match' of undefined
    at new I18nModule (C:\Source\Random\ssr-with-i18n\dist\server\main.js:113153:35)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A little bit of investigation reveals that the failing code is:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;browserLang.match(/en|ru/)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The browserLang variable is undefined, which means that the following line of code didn’t work:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const browserLang = translateCacheService.getCachedLanguage() || translate.getBrowserLang();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This happens because we’re trying to access browser-specific APIs during the server-side rendering. Even the name of the function — getBrowserLang suggests that this function won’t work on the server. We’ll come back to this issue, but for the time being, let’s patch it by hard-coding the value of the browserLang variable:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const browserLang = 'en';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Build and serve the application again. This time there is no error. In fact, if we look at the network tab of the Developer Tools we’ll see that SSR worked! However, the translations didn’t come through.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Zzp9jwWe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1018/1%2ALn7nyDVttOKvAgWxHTEfOQ.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Zzp9jwWe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1018/1%2ALn7nyDVttOKvAgWxHTEfOQ.jpeg" alt="" width="800" height="490"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s see why this is happening. Notice the factory function used in the TranslateModule to load translations: translateLoaderFactory . This function makes use of the HttpClient and knows how to load the JSON files containing translations from the browser. However, the factory function is not smart enough to know how to load these files while in the server environment.&lt;/p&gt;

&lt;p&gt;This brings us to the two issues we need to solve:&lt;/p&gt;

&lt;p&gt;PROBLEM 1. Being able to determine the correct language to load in both the client and the server environments (instead of hard-coding the value to en ).&lt;/p&gt;

&lt;p&gt;PROBLEM 2. Based on the environment, use the appropriate mechanism to load the JSON file containing translations.&lt;/p&gt;

&lt;p&gt;Now that the issues are identified, let’s examine different ways to solve these issues.&lt;/p&gt;
&lt;h3&gt;
  
  
  🤔 Evaluating Existing Options
&lt;/h3&gt;

&lt;p&gt;There are a few ways that we can make everything work. There is a closed issue in the ngx-translate repository related to enabling SSR — &lt;a href="https://github.com/ngx-translate/core/issues/754"&gt;issue #754&lt;/a&gt;. A few solutions to PROBLEMS 1 and 2 can be found there.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;Existing Solution 1. Fix via HttpInterceptor&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;One of the latest comments on issue #754 suggests using a solution found in the article “&lt;a href="https://itnext.io/angular-universal-how-to-add-multi-language-support-68d83f6dfc4d"&gt;Angular Universal: How to add multi language support?&lt;/a&gt;" to address the PROBLEM 2. Unfortunately, PROBLEM 1 is not addressed in the article. The author suggests a fix using the HttpInterceptor, which patches the requests to retrieve the JSON files while on the server.&lt;/p&gt;

&lt;p&gt;Even though the solution works, it feels a bit awkward to me to create an interceptor that will patch the path of the request. In addition, why should we be making an extra request (even though it’s local) when the server has access to the files through the file system? Let’s see what other options are available.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;Existing Solution 2. Fix via Importing JSON Files Directly&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;A few recent comments on the same &lt;a href="https://github.com/ngx-translate/core/issues/754"&gt;issue #754&lt;/a&gt; suggest importing the contents of JSON files straight into the file which defines our module. Then we can check which environment we’re running in and either use the default TranslateHttpLoader or a custom one, which uses the imported JSON. This approach suggests a way to handle PROBLEM 2 by checking the environment where the code is running: if (isPlatformBrowser(platform)). We’ll use a similar platform check later in the article.&lt;/p&gt;


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



&lt;p&gt;&lt;strong&gt;Please don’t do this!&lt;/strong&gt; By importing JSON files like shown above, they will end up in the browser bundle. The whole purpose of using &lt;em&gt;HttpLoader&lt;/em&gt; is that it will load the required language file &lt;strong&gt;on demand&lt;/strong&gt; making the browser bundle smaller.&lt;/p&gt;

&lt;p&gt;With this method, the translations for all the languages will be bundled together with the run-time JavaScript compromising performance.&lt;/p&gt;

&lt;p&gt;Although both existing solutions provide a fix for PROBLEM 2, they have their shortcomings. One results in unnecessary requests being made and another one compromises performance. Neither of them provides a solution for PROBLEM 1.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;🔋 A Better Way — Prerequisites&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;In the upcoming sections I’ll provide two separate solutions to the identified PROBLEMS. Both of the solutions will require the following prerequisites.&lt;/p&gt;

&lt;p&gt;Prerequisite 1. We need to install and use a dependency called &lt;a href="https://www.npmjs.com/package/cookie-parser"&gt;cookie-parser&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Prerequisite 2. Understand the Angular REQUEST injection token&lt;/p&gt;

&lt;h4&gt;
  
  
  Prerequisite 1. Why Do We Need cookie-parser?
&lt;/h4&gt;

&lt;p&gt;The &lt;em&gt;ngx-translate-cache&lt;/em&gt; library is in charge of creating a cookie in the client when a user selects the language. By default (although it can be configured) the cookie is named lang. In the upcoming solutions, we’ll need a way to access this cookie on the server. By default, we can access the information we need from the req.headers.cookie object in any of the Express request handlers. The value would look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lang=en; other-cookie=other-value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This property has all the information we need, but we need to parse the lang out. Although it’s simple enough, there is no need to reinvent the wheel since &lt;em&gt;cookie-parser&lt;/em&gt; is an Express middleware that does exactly what we need.&lt;/p&gt;

&lt;p&gt;Install the required dependencies.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install cookie-parser
npm install @types/cookie-parser -D
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Update the server.ts file to use the installed &lt;em&gt;cookie-parser&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import * as cookieParser from 'cookie-parser';
app.use(cookieParser());
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Under the hood, the cookie-parser will parse the Cookies and store them as a dictionary object under req.cookies.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "lang": "en",
  "other-cookie": "other-value"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Prerequisite 2. The Angular REQUEST Injection Token
&lt;/h4&gt;

&lt;p&gt;Now that we have a convenient way of accessing Cookies from the request object, we need to have access to the req object in the context of the Angular application. This can easily be done using the REQUEST injection token.&lt;/p&gt;


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



&lt;p&gt;Here’s the obvious fact: The REQUEST injection token is available under @nguniversal/express-engine/tokens. Here is a not so obvious fact: the type for the req object is the Request provided by type definitions of the express library.&lt;/p&gt;

&lt;p&gt;This is important and might trip us over. If this import is forgotten, the typescript will assume a different Request type from the Fetch API that’s available under lib.dom.d.ts. As a result, TypeScript will not have knowledge of req.cookies object and will underline it with red.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Now We Are Ready for the Solutions&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Please make a mental snapshot of the PART 2 Checkpoint below. We will use this code as a starting point for the next &lt;strong&gt;two&lt;/strong&gt; parts of this series where we’ll explore how to fix the two PROBLEMS outlined previously.&lt;/p&gt;

&lt;h4&gt;
  
  
  PART 2 Checkpoint
&lt;/h4&gt;

&lt;p&gt;*** The code up to this point is available &lt;a href="https://github.com/DmitryEfimenko/ssr-with-i18n/tree/step-2"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  👌 &lt;strong&gt;Part 3 of 6: Solution 1 — Fix via Providing a Separate I18nModule for the Server&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Currently, our application looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ftKxVHvu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/774/1%2ATNoEWLXiSPOnMROq4RsWmA.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ftKxVHvu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/774/1%2ATNoEWLXiSPOnMROq4RsWmA.jpeg" alt="" width="774" height="670"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The diagram above shows the path of code execution when the code runs in the browser (green) and when it runs in the server (grey). Notice that in the client-side path, the file that bootstraps the whole application (main.ts) imports the AppModule directly. On the server-side path, the main file imports a separate module, the AppServerModule, which in turn imports the AppModule. Also, notice that the I18nModule is a dependency of AppModule , which means that the code of I18nModule will be executed in both the client and in the server.&lt;/p&gt;

&lt;p&gt;In the solution below we’ll make the browser side look more like the server side. We’ll introduce a new module — the AppBrowserModule . That will be the module to be bootstrapped. We’ll also rename the I18nModule to I18nBrowserModule and move it into the imports of the AppBrowserModule . Finally, we’ll introduce a new module, the I18nServerModule, that will use file system access to load JSON files. This module will be imported inside of the AppServerModule. See the resulting structure below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wx4WGEYG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/774/1%2AjgOAemMEkVYcYUCVYV-INg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wx4WGEYG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/774/1%2AjgOAemMEkVYcYUCVYV-INg.jpeg" alt="" width="774" height="637"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Below is the code of the new I18nServerModule.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/media/1cbcd2e42cadd338186eca59d8015bfc/href"&gt;&lt;/a&gt;&lt;a href="https://medium.com/media/1cbcd2e42cadd338186eca59d8015bfc/href"&gt;https://medium.com/media/1cbcd2e42cadd338186eca59d8015bfc/href&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are two main things happening in the code above.&lt;/p&gt;

&lt;p&gt;First, we make use of the REQUEST injection token provided by Angular to get a hold of the full request object. We use the token to access the cookies object to find out what language the user selected in the browser. Knowing the language, we call the use method of the TranslateService class so that our website gets rendered in that language.&lt;/p&gt;

&lt;p&gt;Second, the action above will trigger our custom loading mechanism defined in the TranslateFsLoader class. In the class, we simply use standard node API to read files from the file system (fs).&lt;/p&gt;

&lt;h4&gt;
  
  
  Solution 1 Summary
&lt;/h4&gt;

&lt;p&gt;This solution completely separates the compilation path for the server from the compilation path for the browser. PROBLEM 1 is solved due to the translate.getBrowserLang() existing only in the I18nBrowserModule, which will never run in the server environment.&lt;/p&gt;

&lt;p&gt;PROBLEM 2 is similarly solved by each I18n Module — the Server and the Client modules — using their own Translation Loader mechanism — the TranslateFsLoader and TranslateHttpLoader respectively.&lt;/p&gt;

&lt;p&gt;I like this option because it comes with a clear separation between the code that runs on the server and the code that runs in the browser. Introducing the AppBrowserModule establishes the architecture for handling cases when the server- and client-side logic significantly differs.&lt;/p&gt;

&lt;p&gt;However, there is one more approach to tackle this task. Keep reading!&lt;/p&gt;

&lt;p&gt;*** The code up to this point is available &lt;a href="https://github.com/DmitryEfimenko/ssr-with-i18n/tree/step-3"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;👌&lt;/strong&gt; Part 4 of 6: Solution 2 — Provide Everything in a Single Module
&lt;/h3&gt;

&lt;p&gt;Now that we looked at Solution 1, let’s examine another way. In contrast to Solution 1, this solution does not require the creation of new modules. Instead, all code will be placed inside of the I18nModule. This can be achieved using the isPlatformBrowser function provided by the Angular framework.&lt;/p&gt;

&lt;p&gt;Let’s come back to the &lt;a href="https://github.com/DmitryEfimenko/ssr-with-i18n/tree/step-2"&gt;PART 2 Checkpoint&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git checkout step-2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we’ll make the I18nModule aware of the platform it’s running in and use the appropriate Loader depending on the environment — either the TranslateFsLoader created in the previous Part or the TranslateHttpLoader provided by the &lt;em&gt;ngx-translate&lt;/em&gt; library.&lt;/p&gt;

&lt;p&gt;Add the PLATFORM_ID to the deps of the translateLoaderFactory. This will allow us to select the loader in the factory depending on the current platform.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/media/57480582b6b38f9e3445e92012cc833a/href"&gt;&lt;/a&gt;&lt;a href="https://medium.com/media/57480582b6b38f9e3445e92012cc833a/href"&gt;https://medium.com/media/57480582b6b38f9e3445e92012cc833a/href&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, the factory function will use the appropriate Loader depending on the platform. Similar adjustments need to be done to the constructor of the I18nModule.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/media/f57dde99e7a0d9bd82007e641095259f/href"&gt;&lt;/a&gt;&lt;a href="https://medium.com/media/f57dde99e7a0d9bd82007e641095259f/href"&gt;https://medium.com/media/f57dde99e7a0d9bd82007e641095259f/href&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we try to build the application now we’ll get an error.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Module not found: Error: Can't resolve 'fs' in 'C:\ssr-with-i18n\src\app\i18n'
Module not found: Error: Can't resolve 'path' in 'C:\ssr-with-i18n\src\app\i18n'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s because the fs and the path dependencies, which are strictly Node dependencies, are now referenced in the file that’s compiled for the client-side environment.&lt;/p&gt;

&lt;p&gt;We, as developers, know that these server-side dependencies won’t be used because they are behind appropriate if statements, but the compiler does not know that.&lt;/p&gt;

&lt;p&gt;There is an easy fix for this issue as well. We can let our compiler know not to include these dependencies in the client-side bundle using a new &lt;a href="https://github.com/defunctzombie/package-browser-field-spec"&gt;browser&lt;/a&gt; field of the package.json file.&lt;/p&gt;

&lt;p&gt;Add the following to the package.json file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"browser": {
  "fs": false,
  "path": false
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, everything will compile and run exactly the same as with the previous solution.&lt;/p&gt;

&lt;h4&gt;
  
  
  Solution 2 Summary
&lt;/h4&gt;

&lt;p&gt;Both PROBLEM 1 and PROBLEM 2 are solved by separating the browser-specific code from the server-specific code via an &lt;code&gt;if&lt;/code&gt; statement that evaluates the current platform:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;isPlatformBrowser(this.platform)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that there is only a single path of compilation for both platforms, fs and path dependencies that are strictly node dependencies cause a compilation-time error when the build process compiles a browser bundle. This is solved by specifying these dependencies in the browser field of the package.json file and setting their values to false.&lt;/p&gt;

&lt;p&gt;I like this option because it’s simpler from the perspective of the consumer application. There’s no need to create additional modules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update: I just learned that platform checks considered an anti-pattern because “platform” in general is an ephemeral concept. Although this solution might seem simpler and works, the Solution 1 is preferred.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;*** The code up to this point is available &lt;a href="https://github.com/DmitryEfimenko/ssr-with-i18n/tree/step-4"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚡ Part 5 of 6: Improve Performance with TransferState
&lt;/h3&gt;

&lt;p&gt;If we run our app in its current state and take a look at the network tab of the Browser Developer Tools, we’ll see that after initial load the app will make a request to load the JSON file for the currently selected language.&lt;/p&gt;

&lt;p&gt;This does not make much sense since we’ve already loaded the appropriate language in the server.&lt;/p&gt;

&lt;p&gt;Making an extra request to load language translations that are already loaded might seem like it’s not a big issue worth solving. There probably are areas of an application that will result in a bigger bang for the buck in terms of performance tuning. See &lt;a href="https://christianlydemann.com/the-complete-guide-to-angular-load-time-optimization/"&gt;this article&lt;/a&gt; for more on this topic. However, for bigger applications, translation files might also get bigger. Therefore, the time to download and process them will also increase, at which point this would be an issue to solve.&lt;/p&gt;

&lt;p&gt;Thankfully, Angular Universal provides a tool to solve this issue with relatively little effort: &lt;a href="https://angular.io/api/platform-browser/TransferState"&gt;TransferState&lt;/a&gt;. When this solution is in use, the server will embed the data with the initial HTML sent to the client. The client will then be able to read this data without the need to ask the server.&lt;/p&gt;

&lt;h4&gt;
  
  
  Overview of the Workflow
&lt;/h4&gt;

&lt;p&gt;To make use of the TransferState feature, we need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Add the module provided by Angular for the server and for the client: ServerTransferStateModule and BrowserTransferStateModule&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On the server: set the data that we want to transfer under a specific key using API: transferState.set(key, value)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On the client: retrieve the data using API: transferState.get(key, defaultValue)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Our implementation
&lt;/h4&gt;

&lt;p&gt;First, let’s add the TransferState Modules to the imports:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/media/7b7148b58383b9719eaec180bd9e152a/href"&gt;&lt;/a&gt;&lt;a href="https://medium.com/media/7b7148b58383b9719eaec180bd9e152a/href"&gt;https://medium.com/media/7b7148b58383b9719eaec180bd9e152a/href&lt;/a&gt;&lt;a href="https://medium.com/media/31e53ac3e9a83071ec76e204cb710d4e/href"&gt;&lt;/a&gt;&lt;a href="https://medium.com/media/31e53ac3e9a83071ec76e204cb710d4e/href"&gt;https://medium.com/media/31e53ac3e9a83071ec76e204cb710d4e/href&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let’s make appropriate changes to the I18nModule. The snippet below shows the new code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/media/d5a367b6a1fca9fcb2745805b4eeb0cb/href"&gt;&lt;/a&gt;&lt;a href="https://medium.com/media/d5a367b6a1fca9fcb2745805b4eeb0cb/href"&gt;https://medium.com/media/d5a367b6a1fca9fcb2745805b4eeb0cb/href&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Second, the translateLoaderFactory will now look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/media/9ad6dccd3a56f382432251b962196f6f/href"&gt;&lt;/a&gt;&lt;a href="https://medium.com/media/9ad6dccd3a56f382432251b962196f6f/href"&gt;https://medium.com/media/9ad6dccd3a56f382432251b962196f6f/href&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;TranslateFsLoader will now make use of TransferState:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/media/dda5fd43cc24aac4a7d661c394464a85/href"&gt;&lt;/a&gt;&lt;a href="https://medium.com/media/dda5fd43cc24aac4a7d661c394464a85/href"&gt;https://medium.com/media/dda5fd43cc24aac4a7d661c394464a85/href&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How does it exactly transfer the state? During the server-side rendering, the framework will include the data in the HTML  tag. See this in the image below.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;img src="https://cdn-images-1.medium.com/max/800/1*gM-kQXfp9HZ8uKQVAFFOeQ.gif" alt=""&amp;gt;&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;Once the client-side bundle bootstraps, it will be able to access this data.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;Now we need to allow the client-side Loader to make use of the transferred data. Currently, our loader factory function simply returns the TranslateHttpLoader. We’ll have to create a custom loader that will also be capable of handling the transfer state.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;Let’s create a new file to hold the custom loader class. The new loader will look like the one below.&amp;lt;/p&amp;gt;

&amp;lt;iframe src="" width="0" height="0" frameborder="0" scrolling="no"&amp;gt;&amp;lt;a href="https://medium.com/media/7bd1ed9f614d712f15a711f0fbb50b1d/href"&amp;gt;https://medium.com/media/7bd1ed9f614d712f15a711f0fbb50b1d/href&amp;lt;/a&amp;gt;&amp;lt;/iframe&amp;gt;

&amp;lt;p&amp;gt;Update the translateLoaderFactory to use the new Loader:&amp;lt;/p&amp;gt;

&amp;lt;iframe src="" width="0" height="0" frameborder="0" scrolling="no"&amp;gt;&amp;lt;a href="https://medium.com/media/41c6140cb6605119da07a80e4923a843/href"&amp;gt;https://medium.com/media/41c6140cb6605119da07a80e4923a843/href&amp;lt;/a&amp;gt;&amp;lt;/iframe&amp;gt;
&amp;lt;h4&amp;gt;
  &amp;lt;a name="transferstate-summary" href="#transferstate-summary" class="anchor"&amp;gt;
  &amp;lt;/a&amp;gt;
  TransferState Summary
&amp;lt;/h4&amp;gt;

&amp;lt;p&amp;gt;Using TransferState allowed us to avoid loading data from the browser that was already loaded on the server.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;Now, if we run the application we’ll see that there is no unneeded request to the JSON file for the currently selected language in the network tab.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;*** The code up to this point is available &amp;lt;a href="https://github.com/DmitryEfimenko/ssr-with-i18n/tree/step-5"&amp;gt;here&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;h3&amp;gt;
  &amp;lt;a name="‍-‍-part-6-of-6-are-we-there-yet" href="#‍-‍-part-6-of-6-are-we-there-yet" class="anchor"&amp;gt;
  &amp;lt;/a&amp;gt;
  ‍ &amp;lt;strong&amp;gt;🤷‍&amp;lt;/strong&amp;gt; Part 6 of 6: Are We There Yet?
&amp;lt;/h3&amp;gt;

&amp;lt;p&amp;gt;Whether you’ve chosen to go with the Solution 1 or 2, it seems like it’s all working now! Let’s close the Browser Developer Tools and enjoy the feeling of accomplishment after a lot of work.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;Just to make sure everything is working correctly, let’s update our JSON files and add “!!!” at the end of all translation strings to celebrate. Build and start the application. Refresh the page and then… scratch your head. The “!!!” aren’t there. What happened?&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;While the Browser Developer Tools panel was open, the browser would clear cache on each page reload. It means the browser would download fresh JSON files each time. The moment we closed the Developer Tools panel, the browser started caching our assets. Even though we’ve changed the contents of the JSON files, the browser does not know about it.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;But how does the browser know to load fresh JavaScript and CSS files every time? That’s because Angular’s build scripts append a unique set of characters (hash) to the file name.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;img src="https://cdn-images-1.medium.com/max/245/1*XB942Ub-LqWpWxmxB6QTgA.png" alt=""&amp;gt;&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;This hash changes every time when the contents of the file change. We need to implement a similar feature for our JSON files.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;Luckily, the implementation is straightforward. Let’s create /scripts folder and a new file there: hash-translations.js.&amp;lt;/p&amp;gt;

&amp;lt;iframe src="" width="0" height="0" frameborder="0" scrolling="no"&amp;gt;&amp;lt;a href="https://medium.com/media/32fd5b74173dff06fc4e537b704a7181/href"&amp;gt;https://medium.com/media/32fd5b74173dff06fc4e537b704a7181/href&amp;lt;/a&amp;gt;&amp;lt;/iframe&amp;gt;

&amp;lt;p&amp;gt;For this script to work, we need to install a new dependency.&amp;lt;br&amp;gt;
&amp;lt;/p&amp;gt;
&amp;lt;div class="highlight"&amp;gt;&amp;lt;pre class="highlight plaintext"&amp;gt;&amp;lt;code&amp;gt;npm install md5 -D
&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;The script file above defines two important variables: the source directory and the destination directory.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;First, the script will clean up the destination directory if it was not empty. Then the script will look at the source path specified, read the JSON files, and generate hashes using md5 based on the files’ contents.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;Once the hashes are generated, the script will write a copy of each file in the destination directory, but this time with the hash in the file name.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;Finally, the script will generate the map.json file and put it in the destination directory as well. This file will let us pick the correct hashed file based on the locale. The contents of this file will look like the following:&amp;lt;br&amp;gt;
&amp;lt;/p&amp;gt;
&amp;lt;div class="highlight"&amp;gt;&amp;lt;pre class="highlight plaintext"&amp;gt;&amp;lt;code&amp;gt;{
  "en": "[hash-for-file-1]",
  "ru": "[hash-for-file-2]"
}
&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;Add an entry to the package.json file under the scripts field that will let us execute the file we created.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;Also, update the start and build:ssr scripts to run this new script:&amp;lt;/p&amp;gt;

&amp;lt;iframe src="" width="0" height="0" frameborder="0" scrolling="no"&amp;gt;&amp;lt;a href="https://medium.com/media/2df8bcbf65d2dbba2109d4bf140b4de7/href"&amp;gt;https://medium.com/media/2df8bcbf65d2dbba2109d4bf140b4de7/href&amp;lt;/a&amp;gt;&amp;lt;/iframe&amp;gt;

&amp;lt;p&amp;gt;Go ahead and run the new script to see the results. Note, that there is no need to check-in auto-generated files into the repository since they will be changing often. Add an entry to the .gitignore file.&amp;lt;br&amp;gt;
&amp;lt;/p&amp;gt;
&amp;lt;div class="highlight"&amp;gt;&amp;lt;pre class="highlight plaintext"&amp;gt;&amp;lt;code&amp;gt;src/assets/i18n/autogen/*
&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;Finally, update the Translation Loaders to serve auto-generated files.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;The path to the file that we need to load looks like this: ./assets/i18n/autogen/${lang}.${hash}.json. The ./assets/i18n/autogen/ part is the prefix. The .${hash}.json is the suffix. Both of these variables need to be customized in order to make use of the auto-generated files.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;Here’s how we can handle the prefix change for both loaders.&amp;lt;/p&amp;gt;

&amp;lt;iframe src="" width="0" height="0" frameborder="0" scrolling="no"&amp;gt;&amp;lt;a href="https://medium.com/media/6cfe640a5436c57d5cb120ab36a9fc53/href"&amp;gt;https://medium.com/media/6cfe640a5436c57d5cb120ab36a9fc53/href&amp;lt;/a&amp;gt;&amp;lt;/iframe&amp;gt;

&amp;lt;p&amp;gt;The suffix would have to be handled in the getTranslation method of each loader since we need access to the lang variable.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;First, we need to get a hold of the auto-generated map.json file.&amp;lt;br&amp;gt;
&amp;lt;/p&amp;gt;
&amp;lt;div class="highlight"&amp;gt;&amp;lt;pre class="highlight plaintext"&amp;gt;&amp;lt;code&amp;gt;const i18nMap = require('../../assets/i18n/autogen/map.json');
&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;We use a require syntax because this file might only be available during compilation.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;The TranslateBrowserLoader will have the following changes:&amp;lt;/p&amp;gt;

&amp;lt;iframe src="" width="0" height="0" frameborder="0" scrolling="no"&amp;gt;&amp;lt;a href="https://medium.com/media/02c508c6edaab4549ef58ce28bf4ce4e/href"&amp;gt;https://medium.com/media/02c508c6edaab4549ef58ce28bf4ce4e/href&amp;lt;/a&amp;gt;&amp;lt;/iframe&amp;gt;

&amp;lt;p&amp;gt;For the TranslateFsLoader , it is a one-line code change.&amp;lt;/p&amp;gt;

&amp;lt;iframe src="" width="0" height="0" frameborder="0" scrolling="no"&amp;gt;&amp;lt;a href="https://medium.com/media/fa8925109b6c4b65794ce68d957ba02c/href"&amp;gt;https://medium.com/media/fa8925109b6c4b65794ce68d957ba02c/href&amp;lt;/a&amp;gt;&amp;lt;/iframe&amp;gt;

&amp;lt;p&amp;gt;Compile and run the app. Everything will work as expected and the browser will load updated translation files when it needs to.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;*** The final code is available &amp;lt;a href="https://github.com/DmitryEfimenko/ssr-with-i18n"&amp;gt;here&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;h3&amp;gt;
  &amp;lt;a name="article-summary" href="#article-summary" class="anchor"&amp;gt;
  &amp;lt;/a&amp;gt;
  👏 Article Summary
&amp;lt;/h3&amp;gt;

&amp;lt;p&amp;gt;In this article, we built a maintainable solution for managing application translation strings via separate JSON files. We utilized a popular library — &amp;lt;em&amp;gt;ngx-translate&amp;lt;/em&amp;gt;. We’ve also looked at the current solutions for integrating this functionality with Server-Side Rendered applications provided by the community. We talked about the weaknesses of these solutions and provided better options. Finally, we implemented a few of the advanced features, such as: (1) memorization of the selected language via Cookies, (2) utilizing State Transfer to avoid unnecessary HTTP requests to the server, and (3) breaking cache for the translation files.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Special thanks to&amp;lt;/strong&amp;gt; &amp;lt;a href="http://www.linkedin.com/in/anaboca"&amp;gt;&amp;lt;strong&amp;gt;Ana Boca&amp;lt;/strong&amp;gt;&amp;lt;/a&amp;gt; &amp;lt;strong&amp;gt;and&amp;lt;/strong&amp;gt; &amp;lt;a href="https://github.com/abashmak"&amp;gt;&amp;lt;strong&amp;gt;Alex Bashmakov&amp;lt;/strong&amp;gt;&amp;lt;/a&amp;gt; &amp;lt;strong&amp;gt;for reviewing, testing, and providing some of the code for this article.&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;h4&amp;gt;
  &amp;lt;a name="call-to-action" href="#call-to-action" class="anchor"&amp;gt;
  &amp;lt;/a&amp;gt;
  Call to Action
&amp;lt;/h4&amp;gt;

&amp;lt;p&amp;gt;I always enjoy feedback, so please 👏, 📝, and subscribe to AngularInDepth publication.&amp;lt;/p&amp;gt;

&amp;lt;hr&amp;gt;
&lt;/p&gt;

</description>
      <category>serversiderendering</category>
      <category>i18n</category>
      <category>webdev</category>
      <category>angular</category>
    </item>
  </channel>
</rss>
