<?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: Gérôme Grignon</title>
    <description>The latest articles on DEV Community by Gérôme Grignon (@geromegrignon).</description>
    <link>https://dev.to/geromegrignon</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%2F174508%2Ff06aa36d-92c5-4ce8-a2a7-e53ee09170e1.png</url>
      <title>DEV Community: Gérôme Grignon</title>
      <link>https://dev.to/geromegrignon</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/geromegrignon"/>
    <language>en</language>
    <item>
      <title>Angular 21.1 Release: What's New and How to Use It</title>
      <dc:creator>Gérôme Grignon</dc:creator>
      <pubDate>Thu, 15 Jan 2026 11:41:46 +0000</pubDate>
      <link>https://dev.to/geromegrignon/angular-211-release-whats-new-and-how-to-use-it-21ea</link>
      <guid>https://dev.to/geromegrignon/angular-211-release-whats-new-and-how-to-use-it-21ea</guid>
      <description>&lt;p&gt;Angular 21.1 is now available, delivering meaningful improvements to Signal Forms, Control Flow syntax, Router APIs, and developer tooling. This release focuses on developer experience refinements and fills important gaps in the existing APIs.&lt;/p&gt;

&lt;p&gt;In this guide, we'll break down each feature, explain when you should use it, and provide practical code examples to help you adopt these changes in your projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in This Release
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Signal Forms&lt;/strong&gt;: New &lt;code&gt;formField&lt;/code&gt; directive and form focus capabilities&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control Flow&lt;/strong&gt;: Multi-case support in &lt;code&gt;@switch&lt;/code&gt; blocks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image Loaders&lt;/strong&gt;: Custom transformations for major CDN providers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Router&lt;/strong&gt;: &lt;code&gt;isActive&lt;/code&gt; as a Signal, experimental route cleanup, and Navigation API integration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developer Tooling&lt;/strong&gt;: New stability debugger for SSR/hydration debugging&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Template Expressions&lt;/strong&gt;: Support for spread operators and rest arguments&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Signal Forms Improvements
&lt;/h2&gt;

&lt;p&gt;Angular's Signal Forms API continues to mature with this release, addressing real-world usage patterns and developer feedback.&lt;/p&gt;

&lt;h3&gt;
  
  
  The New formField Directive
&lt;/h3&gt;

&lt;p&gt;Angular 21.1 introduces the &lt;code&gt;formField&lt;/code&gt; directive as the preferred way to bind Signal Form Controls to form elements. While the existing &lt;code&gt;field&lt;/code&gt; directive continues to work, the Angular team recommends adopting &lt;code&gt;formField&lt;/code&gt; going forward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why the change?&lt;/strong&gt; The name &lt;code&gt;field&lt;/code&gt; proved too generic, potentially conflicting with other libraries or custom directives in your application. The &lt;code&gt;formField&lt;/code&gt; directive provides a more explicit, Angular-specific namespace.&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="c"&gt;&amp;lt;!-- Previous approach (still works, but will be deprecated) --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;[field]=&lt;/span&gt;&lt;span class="s"&gt;"loginForm.email"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Recommended approach --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;[formField]=&lt;/span&gt;&lt;span class="s"&gt;"loginForm.email"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under the hood, both directives share the same implementation. Looking at Angular's internal code reveals they're literally interchangeable options:&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;readonly&lt;/span&gt; &lt;span class="nx"&gt;formFieldBindings&lt;/span&gt;&lt;span class="p"&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="k"&gt;readonly &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Field&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;FormField&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Migration tip&lt;/strong&gt;: While both options work today, expect a migration schematic in a future release to automate the transition. Start using &lt;code&gt;formField&lt;/code&gt; in new code now to minimize future migration work.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: The &lt;code&gt;formField&lt;/code&gt; directive was technically introduced in Angular 21.0.8, but this release ensures full compiler support and parity with the &lt;code&gt;field&lt;/code&gt; directive.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Programmatically Focus a Form
&lt;/h3&gt;

&lt;p&gt;A common UX pattern is focusing the first input field when a form loads or after validation errors. Angular 21.1 makes this straightforward with the new &lt;code&gt;focusBoundControl()&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;Previously, you needed to manually target specific fields using &lt;code&gt;viewChild&lt;/code&gt; or template references. Now you can focus the first bound control of an entire 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="c1"&gt;// Focus the first field in the form&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;loginForm&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;focusBoundControl&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 finds the first element bound via &lt;code&gt;field&lt;/code&gt; or &lt;code&gt;formField&lt;/code&gt; directive and focuses it—perfect for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Form initialization&lt;/li&gt;
&lt;li&gt;After form reset&lt;/li&gt;
&lt;li&gt;When validation fails (focus the first invalid field)&lt;/li&gt;
&lt;li&gt;Accessibility improvements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks to &lt;a href="https://www.linkedin.com/in/matthieuriegler/" rel="noopener noreferrer"&gt;Matthieu Riegler&lt;/a&gt; for clarifying the intended use case for this feature!&lt;/p&gt;

&lt;h3&gt;
  
  
  Signal Forms Bug Fixes
&lt;/h3&gt;

&lt;p&gt;This release addresses several edge cases and missing capabilities in Signal Forms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/angular/angular/commit/89c37f1f7f93ec3746479c73b87b948a6e93dcaa" rel="noopener noreferrer"&gt;allow custom controls to require dirty input&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/angular/angular/commit/82edf18427b1fcf7e63cb3ac930dfa1d065a25f1" rel="noopener noreferrer"&gt;allow custom controls to require hidden input&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/angular/angular/commit/1a4c3eb1d09a5db57a07ea5ed593cbe3e47e8125" rel="noopener noreferrer"&gt;allow custom controls to require pending input&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/angular/angular/commit/e7d99f02cba503aa7a30f71d388aef353205fff7" rel="noopener noreferrer"&gt;clean up abort listener after timeout&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/angular/angular/commit/cb09fb8308a7c94cca9af6074ef523ce094d5f67" rel="noopener noreferrer"&gt;support custom controls with non signal-based models&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/angular/angular/commit/282220d032a64d32c466bb37057c6f91df39bfb3" rel="noopener noreferrer"&gt;Support readonly arrays in signal forms&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Control Flow: Multi-Case Switch Blocks
&lt;/h2&gt;

&lt;p&gt;One of the most requested Control Flow features is here: you can now combine multiple &lt;code&gt;@case()&lt;/code&gt; conditions for a single block in &lt;code&gt;@switch&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before Angular 21.1
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@switch(status) {
  @case('pending') {
    &lt;span class="nt"&gt;&amp;lt;status-badge&amp;gt;&lt;/span&gt;Waiting&lt;span class="nt"&gt;&amp;lt;/status-badge&amp;gt;&lt;/span&gt;
  }
  @case('processing') {
    &lt;span class="nt"&gt;&amp;lt;status-badge&amp;gt;&lt;/span&gt;Waiting&lt;span class="nt"&gt;&amp;lt;/status-badge&amp;gt;&lt;/span&gt;
  }
  @case('completed') {
    &lt;span class="nt"&gt;&amp;lt;status-badge&amp;gt;&lt;/span&gt;Done&lt;span class="nt"&gt;&amp;lt;/status-badge&amp;gt;&lt;/span&gt;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  With Angular 21.1
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@switch(status) {
  @case('pending')
  @case('processing') {
    &lt;span class="nt"&gt;&amp;lt;status-badge&amp;gt;&lt;/span&gt;Waiting&lt;span class="nt"&gt;&amp;lt;/status-badge&amp;gt;&lt;/span&gt;
  }
  @case('completed') {
    &lt;span class="nt"&gt;&amp;lt;status-badge&amp;gt;&lt;/span&gt;Done&lt;span class="nt"&gt;&amp;lt;/status-badge&amp;gt;&lt;/span&gt;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This eliminates code duplication and makes your templates more readable—especially useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Grouping similar states (loading, pending, processing)&lt;/li&gt;
&lt;li&gt;Handling multiple enum values with the same UI&lt;/li&gt;
&lt;li&gt;Reducing template maintenance burden&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Image Loader Enhancements
&lt;/h2&gt;

&lt;p&gt;If you're using Angular's &lt;code&gt;NgOptimizedImage&lt;/code&gt; directive with a CDN provider, you now have more control over image transformations. Angular 21.1 adds custom transformation support for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare&lt;/strong&gt; Images&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloudinary&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ImageKit&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Imgix&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means you can apply provider-specific transformations (like format conversion, quality adjustments, or effects) while still benefiting from Angular's optimized image loading.&lt;/p&gt;

&lt;p&gt;For implementation details and configuration examples, see the &lt;a href="https://angular.dev/guide/image-optimization#configuring-an-image-loader-for-ngoptimizedimage" rel="noopener noreferrer"&gt;official NgOptimizedImage documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enhanced Template Expression Support
&lt;/h2&gt;

&lt;p&gt;Angular's template compiler now supports additional JavaScript expression features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rest arguments&lt;/strong&gt; in function calls&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spread elements&lt;/strong&gt; in arrays&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spread expressions&lt;/strong&gt; in objects
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@let merged = {...defaultConfig, ...userConfig};
@let combined = {a: 1, ...partialObject, b: 2};
@let nested = {config: {...{nested: {...deepConfig}}}};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;A word of caution&lt;/strong&gt;: While these features are now supported, they can make templates harder to read and debug. Consider keeping complex logic in your component class and exposing simple, computed values to templates. Templates should primarily handle presentation, not data transformation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging Application Stability
&lt;/h2&gt;

&lt;p&gt;Server-Side Rendering (SSR) and hydration issues often manifest as applications that never "stabilize"—preventing hydration from completing or causing unexpected behavior.&lt;/p&gt;

&lt;p&gt;Angular 21.1 introduces &lt;code&gt;provideStabilityDebugging()&lt;/code&gt; to help diagnose these issues:&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;provideStabilityDebugging&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="s2"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ApplicationConfig&lt;/span&gt; &lt;span class="o"&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="c1"&gt;// Add during development to debug stability issues&lt;/span&gt;
    &lt;span class="nf"&gt;provideStabilityDebugging&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 utility is provided by default in dev mode when using &lt;code&gt;provideClientHydration&lt;/code&gt;. You can also add it manually to the application providers for use in production bundles or when using SSR without hydration, for example. The feature logs information to the console if the application takes longer than expected to stabilize.&lt;/p&gt;

&lt;p&gt;For detailed usage instructions, see &lt;a href="https://angular.dev/guide/hydration#debugging-application-stability" rel="noopener noreferrer"&gt;Debugging Application Stability&lt;/a&gt; in the official documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Router Improvements
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Route Provider Cleanup (Experimental)
&lt;/h3&gt;

&lt;p&gt;A long-standing behavior in Angular Router: providers defined at the route level were never destroyed when navigating away. This could lead to memory leaks and unexpected state persistence.&lt;/p&gt;

&lt;p&gt;Angular 21.1 introduces an experimental feature to automatically clean up route-level injectors:&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;provideRouter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;withExperimentalAutoCleanupInjectors&lt;/span&gt;&lt;span class="p"&gt;,&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="s2"&gt;@angular/router&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ApplicationConfig&lt;/span&gt; &lt;span class="o"&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;provideRouter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;withExperimentalAutoCleanupInjectors&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;When to use this&lt;/strong&gt;: If your routes define providers that hold significant state or resources, enabling this feature ensures proper cleanup. Test thoroughly, as this changes the lifecycle behavior your application may depend on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Router.isActive as a Signal
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;Router.isActive&lt;/code&gt; method is now a computed Signal, enabling fine-grained reactivity for navigation state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Old approach (deprecated)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isActive&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;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isActive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&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="c1"&gt;// New Signal-based approach&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isActive&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;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isActive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&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="c1"&gt;// Returns Signal&amp;lt;boolean&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Bundle size note&lt;/strong&gt;: This feature adds approximately 1.3KB to your bundle, but it's tree-shakable. Applications not using &lt;code&gt;Router.isActive&lt;/code&gt; or &lt;code&gt;RouterLinkActive&lt;/code&gt; won't pay this cost.&lt;/p&gt;

&lt;p&gt;If you're implementing custom route reuse strategies, you can now integrate with this new behavior. See the &lt;a href="https://angular.dev/guide/routing/customizing-route-behavior#creating-a-custom-route-reuse-strategy" rel="noopener noreferrer"&gt;custom route reuse strategies documentation&lt;/a&gt; for details.&lt;/p&gt;

&lt;h3&gt;
  
  
  Platform Navigation API Integration (Experimental)
&lt;/h3&gt;

&lt;p&gt;The browser's &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Navigation_API" rel="noopener noreferrer"&gt;Navigation API&lt;/a&gt; provides a modern approach to handling navigation. Angular 21.1 exposes experimental integration:&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;withExperimentalPlatformNavigation&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="s2"&gt;@angular/router&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ApplicationConfig&lt;/span&gt; &lt;span class="o"&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;provideRouter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;withExperimentalPlatformNavigation&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 experimental feature allows Angular Router to leverage browser-native navigation handling for potentially improved performance and better integration with browser features like the back/forward cache.&lt;/p&gt;

&lt;h2&gt;
  
  
  SSR API Parity
&lt;/h2&gt;

&lt;h3&gt;
  
  
  createApplication Improvements
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;createApplication&lt;/code&gt; function now accepts a &lt;a href="https://angular.dev/api/platform-browser/BootstrapContext" rel="noopener noreferrer"&gt;BootstrapContext&lt;/a&gt; parameter, bringing it to parity with &lt;code&gt;bootstrapApplication&lt;/code&gt;. This matters when you're building custom SSR solutions or test harnesses that need fine-grained control over application creation.&lt;/p&gt;

&lt;p&gt;Additionally, &lt;code&gt;createApplication&lt;/code&gt; now works with JIT-compiled components, removing a previous limitation that required AOT compilation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Angular 21.1 delivers practical improvements across the framework.&lt;/p&gt;

&lt;p&gt;The Signal Forms API continues to mature, Control Flow becomes more expressive, and developer tooling improves. These incremental improvements compound over time, making Angular applications easier to build and maintain.&lt;/p&gt;

&lt;p&gt;For tracking Angular features across versions, check out our &lt;a href="https://www.angular.courses//caniuse/features" rel="noopener noreferrer"&gt;Can I Use - Features&lt;/a&gt; tool, and stay updated with the latest releases through &lt;a href="https://www.angular.courses//releases/insights" rel="noopener noreferrer"&gt;Release Insights&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Understanding takeUntilDestroyed in Angular</title>
      <dc:creator>Gérôme Grignon</dc:creator>
      <pubDate>Mon, 18 Aug 2025 07:38:35 +0000</pubDate>
      <link>https://dev.to/geromegrignon/understanding-takeuntildestroyed-in-angular-3kih</link>
      <guid>https://dev.to/geromegrignon/understanding-takeuntildestroyed-in-angular-3kih</guid>
      <description>&lt;p&gt;Angular applications often rely on Observables for handling asynchronous operations, such as HTTP requests, user events, or real-time data streams. Managing the lifecycle of these subscriptions is crucial to prevent memory leaks and unexpected behavior, especially as components and services are created and destroyed.&lt;/p&gt;

&lt;p&gt;One of the most effective tools for this in modern Angular is the &lt;strong&gt;takeUntilDestroyed&lt;/strong&gt; operator, introduced in the &lt;code&gt;@angular/core/rxjs-interop&lt;/code&gt; package.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is takeUntilDestroyed?
&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%2Fwww.dolmen.tools%2Fcdn%2Fcaniuse%2Fwidget%2Fangular%2FtakeUntilDestroyed" 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%2Fwww.dolmen.tools%2Fcdn%2Fcaniuse%2Fwidget%2Fangular%2FtakeUntilDestroyed" alt="Angular takeUntilDestroyed compatibility" width="1800" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;takeUntilDestroyed is an RxJS operator that automatically completes an Observable when the calling context (such as a component, directive, or service) is destroyed. This means you no longer need to manually unsubscribe from Observables in your ngOnDestroy lifecycle hook, takeUntilDestroyed handles it for you.&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;takeUntilDestroyed&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;@angular/core/rxjs-interop&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="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;someService&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;SomeService&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;someService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;takeUntilDestroyed&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// handle 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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the component is destroyed, the subscription is automatically cleaned up, preventing memory leaks and side effects from lingering subscriptions. This is especially important for Observables that emit multiple values over time, such as those from interval, Subject, or real-time data sources, as opposed to HTTP requests with HttpClient, which complete automatically after a single emission.&lt;/p&gt;

&lt;p&gt;It does not mean you should not care about the unsubscription of HTTP Requests: Letting them live after the component destruction could lead to some side effects you want to avoid.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage outside of the injection content
&lt;/h2&gt;

&lt;p&gt;If you need to use it outside of an injection context, you can inject a DestroyRef and pass it explicitly:&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;DestroyRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inject&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;@angular/core&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;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;someService&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;SomeService&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;destroyRef&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;DestroyRef&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;getData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="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;someService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;takeUntilDestroyed&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;destroyRef&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="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;
  
  
  Best practices
&lt;/h2&gt;

&lt;p&gt;When data from your observable is meant to be used only in a template, prefer relying on the async pipe to subscribe. Angular will automatically clean the subscription when the component is destroyed.&lt;/p&gt;

</description>
      <category>angular</category>
    </item>
    <item>
      <title>Angular: Beyond the Fog #2</title>
      <dc:creator>Gérôme Grignon</dc:creator>
      <pubDate>Tue, 20 May 2025 12:24:16 +0000</pubDate>
      <link>https://dev.to/geromegrignon/angular-beyond-the-fog-2-44fg</link>
      <guid>https://dev.to/geromegrignon/angular-beyond-the-fog-2-44fg</guid>
      <description>&lt;p&gt;Since I created &lt;a href="https://www.angular.courses/angie" rel="noopener noreferrer"&gt;Angular Docs AI&lt;/a&gt; assistant (powered by Kapa.ia), some questions have been left unanswered due to some level of uncertainty as the AI model faced some limitations.&lt;/p&gt;

&lt;p&gt;Discover some of these questions and how a personal understanding of Angular, its ecosystem, and the community helps to provide better answers!&lt;/p&gt;

&lt;h3&gt;
  
  
  How does Angular Docs AI chat work?
&lt;/h3&gt;

&lt;p&gt;Angular Docs AI sources are &lt;a href="https://angular.dev" rel="noopener noreferrer"&gt;angular.dev&lt;/a&gt;, &lt;a href="https://github.com/angular/angular/discussions" rel="noopener noreferrer"&gt;Angular GitHub Discussions&lt;/a&gt; and &lt;a href="https://github.com/angular/angular/issues" rel="noopener noreferrer"&gt;Angular GitHub Issues&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;These sources are refreshed weekly.&lt;/p&gt;

&lt;p&gt;Test it out: &lt;a href="https://angular.courses/angie" rel="noopener noreferrer"&gt;Angular Docs AI&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it possible to set the OnPush strategy for all components at once?
&lt;/h3&gt;

&lt;p&gt;Angular only allows you to set the OnPush strategy for a component if you set it explicitly in the component metadata.&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="s2"&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;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./app.component.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;changeDetection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ChangeDetectionStrategy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OnPush&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppComponent&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can set the OnPush strategy as default for new components in your angular.json file but there is no way to toggle the strategy for all components at once.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"schematics"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@schematics/angular:component"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"changeDetection"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OnPush"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Was Angular Can I Use developed with AI?
&lt;/h3&gt;

&lt;p&gt;No, &lt;a href="https://www.angular.courses/caniuse" rel="noopener noreferrer"&gt;Angular Can I Use&lt;/a&gt; data was generated by manually exploring multiple sources (Angular documentation, blog posts, and source code) to find proper information. That was one of the key motivations behind creating this project: to have a reliable source of information.&lt;/p&gt;

&lt;p&gt;AI could have helped, but it would have faced some limitations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where is the style guide content?
&lt;/h3&gt;

&lt;p&gt;The official style guide is available at &lt;a href="https://angular.dev/style-guide" rel="noopener noreferrer"&gt;angular.dev/style-guide&lt;/a&gt;.&lt;br&gt;
Its content will be updated with the Angular 20 release, make it simpler to focus on Angular and up to date based on latest Angular API changes.&lt;br&gt;
Some of these changes will impact how we name files generated by Angular CLI &lt;code&gt;ng generate&lt;/code&gt; commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use Signal input in 17.0.9?
&lt;/h3&gt;

&lt;p&gt;No it's been introduced in 17.1.0 as developer preview, you can check &lt;a href="https://www.angular.courses/caniuse?search=input" rel="noopener noreferrer"&gt;Angular Can I use&lt;/a&gt; for release information.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can you write me a song about Angular?
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;I'm sorry, but the knowledge sources do not contain any information or examples about explaining Angular in song form. They only provide technical and descriptive details about Angular and its features. If you would like a technical explanation or have another question about Angular, please let me know!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Angular Docs AI has been built to be helpful and informative, never intending to be used for creative purposes, but if you want to write a song about Angular, I'm sure you will find a way to do it.&lt;/p&gt;

&lt;p&gt;But you are lucky, &lt;a href="https://www.linkedin.com/in/yjaaidi/" rel="noopener noreferrer"&gt;Younes Jaaidi&lt;/a&gt; just created a song about Angular, check it out: &lt;a href="https://www.youtube.com/watch?v=D-Xc3Uo_s6s" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=D-Xc3Uo_s6s&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use standalone components in Angular 13?
&lt;/h3&gt;

&lt;p&gt;No, standalone components were introduced in Angular 14, as an experimental feature, then stabilized in Angular 15.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is httpressource safe to use?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;httpResource&lt;/code&gt; has been introduced as an experimental feature in Angular 19.2.0.&lt;br&gt;
It means it might never become stable at all or have significant breaking changes.&lt;/p&gt;

&lt;p&gt;You can already use it in your projects, but I'd discourage using it in production.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>angular</category>
    </item>
    <item>
      <title>Angular: Beyond the fog #1</title>
      <dc:creator>Gérôme Grignon</dc:creator>
      <pubDate>Tue, 13 May 2025 12:29:10 +0000</pubDate>
      <link>https://dev.to/geromegrignon/angular-beyond-the-fog-1-3oli</link>
      <guid>https://dev.to/geromegrignon/angular-beyond-the-fog-1-3oli</guid>
      <description>&lt;p&gt;Since I created &lt;a href="https://www.angular.courses/angie" rel="noopener noreferrer"&gt;Angular Docs AI&lt;/a&gt; assistant (powered by Kapa.ia), some questions have been left unanswered due to some level of uncertainty as the AI model faced some limitations.&lt;/p&gt;

&lt;p&gt;Discover some of these questions and how a personal understanding of Angular, its ecosystem, and the community helps to provide better answers!&lt;/p&gt;

&lt;h3&gt;
  
  
  What's new in Angular 20?
&lt;/h3&gt;

&lt;p&gt;As the Angular 20 release is currently planned for &lt;a href="https://angular.dev/reference/releases#release-schedule" rel="noopener noreferrer"&gt;26th May week&lt;/a&gt;, the current official documentation is still based on Angular 19. Community content is also quite limited, and a decent amount of it is AI-generated content, providing unverified information.&lt;/p&gt;

&lt;p&gt;I'm currently working on providing you with the most up-to-date information about Angular 20, with a dedicated dashboard: feature list, commit history, Can I Use focus, tutorials, and quizzes.&lt;/p&gt;

&lt;p&gt;&lt;a href="/images/beyond-the-fog/1/version-insights.png" class="article-body-image-wrapper"&gt;&lt;img src="/images/beyond-the-fog/1/version-insights.png" alt="Version Insights"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It'll be publicly shared for the Angular 20 release date and will work at creating such content for all future Angular releases, even before they are released!&lt;/p&gt;

&lt;h3&gt;
  
  
  Why have I heard that constructor based DI may be replaced by the inject function?
&lt;/h3&gt;

&lt;p&gt;The 2 solutions are currently supported, and there is no plan to replace one with the other.&lt;/p&gt;

&lt;p&gt;However, part of the upcoming new official style guide is to recommend using the inject function, as explained in the &lt;a href="https://github.com/angular/angular/discussions/59522#discussioncomment-12781971" rel="noopener noreferrer"&gt;style guide RFC summary&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  I need a button component using TailwindCSS v4. Use the purple color
&lt;/h3&gt;

&lt;p&gt;Tailwind is a great project, but unrelated to Angular. You can find the Tailwind color palettes &lt;a href="https://tailwindcss.com/docs/colors" rel="noopener noreferrer"&gt;here&lt;/a&gt;. You'll have to choose the right value based on your design system and dark/light themes: there is no default 'purple color'.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it best to use React or Angular?
&lt;/h3&gt;

&lt;p&gt;Quoting &lt;a href="https://www.linkedin.com/in/marktechson/" rel="noopener noreferrer"&gt;Mark Thompson&lt;/a&gt;, Angular Devrel: build great web apps!&lt;br&gt;
And today, you can choose what you like best to create great web apps.&lt;/p&gt;

&lt;p&gt;Besides different mindsets, the main difference is that Angular is a full framework with its own official ecosystem, while React is a library. That's not about battling who is a framework or not, but by being a library, React enabled a lot of different ecosystems to grow around it, being both a blessing and a curse, leading to innovation but inconsistency between projects.&lt;/p&gt;

&lt;p&gt;My only true recommendation would be to prefer React for mobile or SSR applications, as community projects (&lt;a href="https://expo.dev/" rel="noopener noreferrer"&gt;Expo&lt;/a&gt; for mobile and &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; for SSR) are more mature and easier to set up.&lt;/p&gt;
&lt;h3&gt;
  
  
  Should I use ngModel or reactive form controls?
&lt;/h3&gt;

&lt;p&gt;There are just different mindsets: you won't be limited by choosing one over the other.&lt;br&gt;
The community was used to advocate for reactive forms, but lately, more people have been sharing the benefits of using template-driven forms with ngModel. A few years ago, Ward Bell gave a great talk about it: &lt;a href="https://www.youtube.com/watch?v=L7rGogdfe2Q" rel="noopener noreferrer"&gt;Prefer Template-Driven Forms &lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The Angular team started making some experiments with the Signals API to provide a &lt;strong&gt;Signals Forms&lt;/strong&gt; API, but it has not been unveiled yet. It's not part of the upcoming v20.0.0 release. However, we could dream of an experimental version released in some minor v20 version.&lt;/p&gt;

&lt;p&gt;I'd encourage you to choose an API and to stick with it for your project's consistency.&lt;/p&gt;
&lt;h3&gt;
  
  
  How to use effect without a constructor?
&lt;/h3&gt;

&lt;p&gt;You can create a local variable in your class Component to reference an effect, rather than using it in a constructor:&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;MyComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;myEffect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;effect&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="s2"&gt;effect&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;I'd still recommend using the constructor as a way to organize the code and make it more readable.&lt;br&gt;
As you won't need this reference, it avoids creating some conflicts at naming local variables.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to learn NgRx?
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://www.angular.courses/angie/" rel="noopener noreferrer"&gt;Angular Docs AI assistant&lt;/a&gt; is not shaped to answer about community projects: like any other project, the best solution to get started is to explore the official &lt;a href="https://ngrx.io/docs" rel="noopener noreferrer"&gt;library documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Mind NgRx now offers three approaches to manage your app state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@ngrx/store&lt;/code&gt; : the original project, inspired by Redux&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@ngrx/component-store&lt;/code&gt; : to manage component state&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@ngrx/signals&lt;/code&gt; : a new API to manage state with signals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Discover the original post on Angular courses: &lt;a href="https://www.angular.courses/beyond-the-fog/2025-05-13" rel="noopener noreferrer"&gt;https://www.angular.courses/beyond-the-fog/2025-05-13&lt;/a&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Introducing Router outlet Input in Angular 19</title>
      <dc:creator>Gérôme Grignon</dc:creator>
      <pubDate>Sat, 31 Aug 2024 07:19:56 +0000</pubDate>
      <link>https://dev.to/geromegrignon/introducing-router-outlet-input-in-angular-19-2opa</link>
      <guid>https://dev.to/geromegrignon/introducing-router-outlet-input-in-angular-19-2opa</guid>
      <description>&lt;p&gt;In modern frontend frameworks it's common to nest components and share data between them.&lt;br&gt;
Angular provides &lt;code&gt;input&lt;/code&gt; binding to pass data from parent to child components. However it only works when the child selector is directly used in the parent template.&lt;/p&gt;

&lt;p&gt;Angular 19 introduced, Router outlet Input, a feature available from version &lt;a href="https://www.angular.courses/tracker/19.0.0-next.0" rel="noopener noreferrer"&gt;19.0.0-next.0&lt;/a&gt; to pass data directly to components loaded via the router outlet.&lt;br&gt;
With this feature, you can now directly pass data to a routed component, eliminating the need for intermediary services in many cases.&lt;/p&gt;
&lt;h3&gt;
  
  
  How to use Router outlet Input
&lt;/h3&gt;

&lt;p&gt;Using Router outlet Input is straightforward. Similar to how you pass data to a direct child component using &lt;code&gt;input&lt;/code&gt;, you can now pass data to the  directive using the new routerOutletData input. Here’s how it works:&lt;/p&gt;

&lt;p&gt;Suppose you have an articles signal in your parent component. You can pass it to the router outlet 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;router-outlet&lt;/span&gt; &lt;span class="na"&gt;[routerOutletData]=&lt;/span&gt;&lt;span class="s"&gt;"articles()"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the routed component's side, you can access this data using the ROUTER_OUTLET_DATA injection token:&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;ROUTER_OUTLET_DATA&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="s2"&gt;@angular/router&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="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;RoutedComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;data&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;ROUTER_OUTLET_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;The injected data is provided as a &lt;code&gt;Signal&amp;lt;unknown&amp;gt;&lt;/code&gt; type. Since Angular doesn’t infer the type, you might want to cast it to the expected type for a better development experience:&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;ROUTER_OUTLET_DATA&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="s2"&gt;@angular/router&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="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;RoutedComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;data&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;ROUTER_OUTLET_DATA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Signal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Article&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  When to use it
&lt;/h3&gt;

&lt;p&gt;Router outlet Input is particularly useful when you need to share data between a parent component and multiple child components routed via the router outlet. Instead of recalculating the same data in each child component, you can compute it once in the parent component and pass it down. This approach not only reduces redundancy but also keeps your child components focused on their specific logic.&lt;/p&gt;

&lt;p&gt;For example, imagine you have a list of articles and a sidebar with filters. You can compute the filtered articles in the parent component and pass this data to the router outlet. Each child component can then use this precomputed data in various ways, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Displaying a filtered list of articles&lt;/li&gt;
&lt;li&gt;Showing a tag cloud based on the filtered articles&lt;/li&gt;
&lt;li&gt;Generating a chart of articles categorized by tags&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By updating the filters in the parent component, the new data is reactively updated to the active routed child component. This ensures that your child components always have the latest data without additional computations or service calls.&lt;/p&gt;

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

&lt;p&gt;The introduction of Router Outlet Data Input in Angular 19 is a powerful addition that simplifies data sharing between routed components. By leveraging this feature, you can write more efficient, maintainable, and cleaner Angular applications.&lt;/p&gt;

&lt;p&gt;Ready to explore this new feature? Check out the official &lt;a href="https://github.com/angular/angular/releases/tag/19.0.0-next.0" rel="noopener noreferrer"&gt;release notes&lt;/a&gt; for more details and start simplifying your Angular apps today!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>typescript</category>
      <category>javascript</category>
      <category>angular</category>
    </item>
    <item>
      <title>Common Pitfalls in TypeScript with HTTP Calls</title>
      <dc:creator>Gérôme Grignon</dc:creator>
      <pubDate>Fri, 26 Jul 2024 11:44:44 +0000</pubDate>
      <link>https://dev.to/geromegrignon/common-pitfalls-in-typescript-with-http-calls-ehk</link>
      <guid>https://dev.to/geromegrignon/common-pitfalls-in-typescript-with-http-calls-ehk</guid>
      <description>&lt;p&gt;When working with TypeScript to make HTTP calls, it's easy to fall into certain traps that can lead to unexpected behavior and bugs. In this blog post, we'll discuss three common errors and how to avoid them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Type Mismatch in HTTP Response
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Issue:&lt;/strong&gt; Typing your HTTP call response in TypeScript doesn't guarantee that the actual response body will match your type.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Explanation:&lt;/strong&gt; TypeScript's type system only works at compile-time. It helps you catch type errors while writing code, but it doesn't enforce type correctness at runtime. This means that even if you define a type for your HTTP response, there's no guarantee that the server will return data in the expected format.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;TypeScript is only about the code you write!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;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;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.example.com/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;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 the above example, if the server returns a response where the &lt;code&gt;firstName&lt;/code&gt; field is missing or using a different format like &lt;code&gt;first_name&lt;/code&gt;, TypeScript won't warn you. The mismatch will only become apparent at runtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Use runtime validation to ensure the response matches the expected type. Libraries like &lt;code&gt;valibot&lt;/code&gt; or &lt;code&gt;zod&lt;/code&gt; can help with this.&lt;br&gt;
Rather than creating an interface, use a type inferred from a schema and parse the response data with the schema.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example with &lt;code&gt;zod&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&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="s2"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;UserSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;userSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;UserSchema&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.example.com/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;UserSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&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="c1"&gt;// Runtime validation&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Nested Data in Response
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Issue:&lt;/strong&gt; Some APIs nest the actual data in a response wrapper, often under a &lt;code&gt;data&lt;/code&gt; property. If you don't check the response structure, you might end up with incorrect data handling.&lt;br&gt;
It often happen with paginated data where the top level object contains metadata like &lt;code&gt;total&lt;/code&gt;, &lt;code&gt;page&lt;/code&gt;, &lt;code&gt;perPage&lt;/code&gt;, etc.&lt;/p&gt;

&lt;p&gt;Besides the requirement for pagination, you might struggle due to your company's API design guidelines. Some people use &lt;code&gt;data&lt;/code&gt; while others use &lt;code&gt;result&lt;/code&gt; or &lt;code&gt;response&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"alice@mail.com"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"page"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the server response is expected to have a &lt;code&gt;data&lt;/code&gt; property that contains the actual user object. If the structure changes, you won't be notified by TypeScript.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Always check and validate the response structure. Once again using a library like &lt;code&gt;zod&lt;/code&gt; can help with this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&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="s2"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;UserSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ResponseSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;UserSchema&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.example.com/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;parsedData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ResponseSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;parsedData&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;h2&gt;
  
  
  Class Instances from HTTP Responses
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Issue:&lt;/strong&gt; Using a class to type your HTTP response doesn't automatically create instances of that class. The response data will just be plain objects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;public&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="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;firstName&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="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;lastName&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="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;getFullName&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="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &amp;lt;&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;lastName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.example.com/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;User&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 the example above, using &lt;code&gt;getFullName&lt;/code&gt; on the returned &lt;code&gt;User&lt;/code&gt; object will result in a runtime error because &lt;code&gt;data&lt;/code&gt; is not an instance of the &lt;code&gt;User&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Manually instantiate the class with the response data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;public&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="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;firstName&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="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;lastName&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="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;getFullName&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="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &amp;lt;&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;lastName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.example.com/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;User&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;id&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;name&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;email&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 &lt;code&gt;User&lt;/code&gt; instance will have the &lt;code&gt;getDisplayName&lt;/code&gt; method available.&lt;/p&gt;

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

&lt;p&gt;TypeScript is a powerful tool for catching type errors at compile-time, but it doesn't enforce type safety at runtime. When dealing with HTTP responses, always validate the response structure and instantiate classes manually to avoid common pitfalls. Using libraries like &lt;code&gt;zod&lt;/code&gt; for runtime validation can greatly enhance the reliability of your TypeScript applications.&lt;/p&gt;

</description>
      <category>typescript</category>
    </item>
    <item>
      <title>Announcing RealWorld Angular</title>
      <dc:creator>Gérôme Grignon</dc:creator>
      <pubDate>Mon, 22 Jul 2024 11:28:44 +0000</pubDate>
      <link>https://dev.to/geromegrignon/announcing-realworld-angular-11l9</link>
      <guid>https://dev.to/geromegrignon/announcing-realworld-angular-11l9</guid>
      <description>&lt;p&gt;I'm pretty excited to announce this new open-source project!&lt;/p&gt;

&lt;p&gt;I've been maintaining RealWorld project for 3 years and decided to create a spin-off to focus on Angular.&lt;/p&gt;

&lt;h2&gt;
  
  
  Once upon a time... RealWorld
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/gothinkster/realworld" rel="noopener noreferrer"&gt;RealWorld&lt;/a&gt; is an open-source project created by &lt;a href="https://x.com/iamalbertpai" rel="noopener noreferrer"&gt;Albert Pai&lt;/a&gt; and &lt;a href="https://x.com/ericsimons40" rel="noopener noreferrer"&gt;Eric Simons&lt;/a&gt; back in 2016.&lt;br&gt;
You can find their announcement &lt;a href="https://medium.com/@ericsimons/introducing-realworld-6016654d36b5" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In a nutshell, it aims to provide examples apps built with different frameworks but still adhering to the same API spec.&lt;br&gt;
If you know &lt;a href="https://todomvc.com/" rel="noopener noreferrer"&gt;TodoMVC&lt;/a&gt;, RealWorld is quite about the same philosophy but with more complete examples.&lt;/p&gt;

&lt;p&gt;At the time of writing, RealWorld is close to 80k GitHub Stars and includes 204 example apps listed &lt;a href="https://codebase.show/projects/realworld" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is RealWorld Angular compared to RealWorld
&lt;/h2&gt;

&lt;p&gt;This new project is a spin-off focusing on Angular.&lt;br&gt;
As RealWorld is about comparing frameworks with a limited list of example apps for each of them (for maintainability reasons), this new project aims to do quite about the same but by showcasing Angular libraries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why a spin-off
&lt;/h3&gt;

&lt;p&gt;As an Angular Discord server moderator, questions about recommended example apps are quite common.&lt;br&gt;
But in most situations, examples are limited StackBlitz projects or small demos by Angular library maintainers.&lt;/p&gt;

&lt;p&gt;The goal is both to provide a more &lt;strong&gt;Real world&lt;/strong&gt; situation to showcase technical aspects or library integration in an Angular project and to build a great playground with example apps you can test, customize, or update.&lt;/p&gt;

&lt;h3&gt;
  
  
  Angular app template
&lt;/h3&gt;

&lt;p&gt;RealWorld Angular will provide a demo application and API spec.&lt;br&gt;
But it'll be a new more complete application to open opportunities to enhance it with examples apps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what about adding i18n?&lt;/li&gt;
&lt;li&gt;what about using a state management library?&lt;/li&gt;
&lt;li&gt;what about choosing template-driven forms over Reactive Forms?&lt;/li&gt;
&lt;li&gt;what about using SSR over CSR?&lt;/li&gt;
&lt;li&gt;and so on...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unlike RealWorld requiring most example apps to start from scratch with a new framework, a GitHub starter template will be provided to focus on details.&lt;/p&gt;

&lt;p&gt;RealWorld provided a blogging platform as a demo. RealWorld Angular will provide an event platform demo, with way more situations to showcase Angular features and modern best practices used in web applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where is the template?
&lt;/h3&gt;

&lt;p&gt;This project is built in public and this template is still WIP: the first current step is the creation of the API, built with Nitro.&lt;/p&gt;

&lt;h3&gt;
  
  
  Community
&lt;/h3&gt;

&lt;p&gt;By focusing on a framework and providing an app template, it'll be easier with this project to engage with the community and create open-source contribution opportunities.&lt;br&gt;
Based on the template, Examples apps will be created on-demand in the &lt;a href="https://github.com/realworld-angular" rel="noopener noreferrer"&gt;GitHub organization&lt;/a&gt;, providing Maintainer role to anyone willing to help by contributing to a new example app: Trust by design!&lt;/p&gt;

&lt;p&gt;Discover a more complete introduction to RealWorld Angular on the &lt;a href="https://github.com/realworld-angular" rel="noopener noreferrer"&gt;GitHub organization&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>opensource</category>
      <category>community</category>
    </item>
    <item>
      <title>Display Angular @empty block at the right time!</title>
      <dc:creator>Gérôme Grignon</dc:creator>
      <pubDate>Fri, 08 Mar 2024 12:31:51 +0000</pubDate>
      <link>https://dev.to/geromegrignon/display-angular-empty-block-at-the-right-time-4g4m</link>
      <guid>https://dev.to/geromegrignon/display-angular-empty-block-at-the-right-time-4g4m</guid>
      <description>&lt;p&gt;The new Control Flow syntax for Angular has been introduced with Angular 17 release, as a &lt;a href="https://angular.dev/guide/templates/control-flow" rel="noopener noreferrer"&gt;developer preview&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It replaces &lt;code&gt;*ngIf&lt;/code&gt;, &lt;code&gt;*ngFor&lt;/code&gt;, and &lt;code&gt;*ngSwitch&lt;/code&gt; with a new syntax that is more expressive and easier to read.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@for(article of articles$ | async; track article.id) {
  &lt;span class="nt"&gt;&amp;lt;article&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;{{ article.title }}&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;{{ article.content }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;The Angular team added a new feature to enhance the &lt;strong&gt;&lt;a class="mentioned-user" href="https://dev.to/for"&gt;@for&lt;/a&gt;&lt;/strong&gt; block: the &lt;strong&gt;&lt;a class="mentioned-user" href="https://dev.to/empty"&gt;@empty&lt;/a&gt;&lt;/strong&gt; block.&lt;/p&gt;

&lt;p&gt;The content of this block will be displayed if the list is empty or undefined.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@for(article of articles$ | async; track article.id) {
  &lt;span class="nt"&gt;&amp;lt;article&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;{{ article.title }}&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;{{ article.content }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
} @empty {
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;No articles found&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;blockquote&gt;
&lt;p&gt;A common misconception is to think that the &lt;a class="mentioned-user" href="https://dev.to/empty"&gt;@empty&lt;/a&gt; block is evaluated only once the data is retrieved from an asynchronous call.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In most usecases, you iterate over a list of items retrieved from an API.&lt;br&gt;&lt;br&gt;
As the API call is asynchronous, the list is undefined/empty at the component initialization.&lt;/p&gt;

&lt;p&gt;From our point of view:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The list is evaluated as empty&lt;/li&gt;
&lt;li&gt;The data is retrieved from the API&lt;/li&gt;
&lt;li&gt;The list is evaluated once again and display the empty block if the list is empty&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;From an end user point of view:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The empty block is displayed at the beginning&lt;/li&gt;
&lt;li&gt;The view is updated with the list of items once the data is retrieved if a non-empty list is returned&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It means we are displaying the empty block at the &lt;strong&gt;wrong time!&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
For a second (or more depending on your API response time), the user sees the empty block before the list is displayed.&lt;br&gt;
They might think that the list is really empty, while it's not.&lt;/p&gt;
&lt;h2&gt;
  
  
  The solution
&lt;/h2&gt;

&lt;p&gt;You need to wrap your existing logic to deal with the loading state of the list.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@if(articles$ | async as articles) {
    @for(article of articles; track article.id) {
    &lt;span class="nt"&gt;&amp;lt;article&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;{{ article.title }}&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;{{ article.content }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
    } @empty {
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;No articles found&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    }   
} @else {
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Loading...&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There, instead of displaying the &lt;strong&gt;&lt;a class="mentioned-user" href="https://dev.to/empty"&gt;@empty&lt;/a&gt;&lt;/strong&gt; block while you are waiting for the list to be retrieved, you display a loading message.&lt;br&gt;
The &lt;strong&gt;&lt;a class="mentioned-user" href="https://dev.to/empty"&gt;@empty&lt;/a&gt;&lt;/strong&gt; block will be evaluated once the list is retrieved and displayed at the right time.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Add a favicon to your Storybook application</title>
      <dc:creator>Gérôme Grignon</dc:creator>
      <pubDate>Tue, 21 Feb 2023 10:47:22 +0000</pubDate>
      <link>https://dev.to/geromegrignon/add-a-favicon-to-your-storybook-application-57k2</link>
      <guid>https://dev.to/geromegrignon/add-a-favicon-to-your-storybook-application-57k2</guid>
      <description>&lt;p&gt;Favicons, short for "favorite icons," are small images or icons that appear in the browser tab, bookmarks, and other areas of the browser UI. Adding a favicon to your Storybook application can help to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Improve branding and visual appeal: A well-designed favicon can help to reinforce your brand and make your Storybook application more visually appealing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enhance user experience: Favicons can make it easier for users to find and recognize your Storybook application among multiple tabs or bookmarks, enhancing the user experience.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Build trust and credibility: A favicon can also help build trust and credibility with your users by demonstrating attention to detail and a commitment to providing a professional and high-quality application.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Adding a favicon is a small but significant step that can help improve the user experience and make your Storybook application more memorable and distinctive.&lt;/p&gt;

&lt;p&gt;So why not take this small but impactful step today and give your Storybook application that extra touch of visual appeal and professionalism?&lt;/p&gt;




&lt;p&gt;To add a favicon for Storybook, follow these steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Create or obtain&lt;/strong&gt; a favicon file matching the &lt;a href="https://en.wikipedia.org/wiki/Favicon#Image_file_format_support" rel="noopener noreferrer"&gt;supported formats&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Place&lt;/strong&gt; the favicon file in the public folder of your Storybook project as defined in your &lt;a href="https://storybook.js.org/docs/react/configure/images-and-assets#serving-static-files-via-storybook-configuration" rel="noopener noreferrer"&gt;configuration&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Open&lt;/strong&gt; the &lt;em&gt;.storybook/manager-head.html&lt;/em&gt; file in your project, or create it if it doesn't exist.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add&lt;/strong&gt; the following code to the file to reference the favicon file:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&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;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"shortcut icon"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/favicon.ico"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Save&lt;/strong&gt; the file and restart your Storybook server. Your favicon should now be visible in the browser tab for your Storybook application.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Warning&lt;/strong&gt;: The default Storybook favicon overrides the new one with Chrome by running the application locally.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>productivity</category>
      <category>debugging</category>
      <category>career</category>
    </item>
    <item>
      <title>Which projects will you contribute to this year?</title>
      <dc:creator>Gérôme Grignon</dc:creator>
      <pubDate>Mon, 02 Jan 2023 08:12:09 +0000</pubDate>
      <link>https://dev.to/geromegrignon/which-projects-will-you-contribute-to-this-year-4p2e</link>
      <guid>https://dev.to/geromegrignon/which-projects-will-you-contribute-to-this-year-4p2e</guid>
      <description>&lt;p&gt;Let's jump into this new year by contributing to new open-source projects!&lt;br&gt;
What's your pick?&lt;/p&gt;

</description>
      <category>softwaredevelopment</category>
      <category>python</category>
      <category>solidprinciples</category>
      <category>learning</category>
    </item>
    <item>
      <title>You shouldn't use EventEmitters in Angular services</title>
      <dc:creator>Gérôme Grignon</dc:creator>
      <pubDate>Thu, 06 Jan 2022 09:37:06 +0000</pubDate>
      <link>https://dev.to/geromegrignon/you-shouldnt-use-eventemitters-in-angular-services-8fa</link>
      <guid>https://dev.to/geromegrignon/you-shouldnt-use-eventemitters-in-angular-services-8fa</guid>
      <description>&lt;p&gt;&lt;em&gt;Cover photo by &lt;a href="https://unsplash.com/@hudsoncrafted?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Debby Hudson&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/build?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;By reviewing Angular code daily through mentoring or community support, I happen to find &lt;strong&gt;EventEmitters&lt;/strong&gt; being used in Angular services.&lt;/p&gt;

&lt;p&gt;Here is an example with a service dedicated to broadcasting some data to other parts of the application :&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;DataService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&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;Data&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;Data&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;updateData&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;Data&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="k"&gt;this&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="nf"&gt;emit&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By using dependency injection, a component can subscribe to the EventEmitter to receive the emitted values :&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;MyComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;dataService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DataService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="nf"&gt;ngOnInit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataService&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="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// do whatever you want&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;It works: if another part of the application uses &lt;code&gt;updateData&lt;/code&gt; to emit a value, the component will receive it.&lt;/p&gt;

&lt;p&gt;So why &lt;strong&gt;shouldn't&lt;/strong&gt; you use it?&lt;/p&gt;




&lt;p&gt;Let's take a look at EventEmitter API.&lt;br&gt;
Here is a simplified version of the original EventEmitter codebase based on its usage in the previous code samples :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EventEmitter&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Subject&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;emit&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="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;observerOrNext&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Subscription&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;observerOrNext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;sink&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;From creating your &lt;em&gt;EventEmitter&lt;/em&gt; to the subscription, you use nothing more than the extended class : a &lt;em&gt;Subject&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The first motivation not to use an &lt;em&gt;EventEmitter&lt;/em&gt; over a &lt;em&gt;Subject&lt;/em&gt; is to &lt;em&gt;keep it simple stupid&lt;/em&gt; (KISS), as a Subject already provides you all you need.&lt;/p&gt;

&lt;p&gt;The second reason lies in the original purpose of an &lt;em&gt;EventEmitter&lt;/em&gt; as explained in the &lt;a href="https://angular.io/api/core/EventEmitter" rel="noopener noreferrer"&gt;reference API&lt;/a&gt; :&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use in components with the @Output directive to emit custom events synchronously or asynchronously, and register handlers for those events by subscribing to an instance.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By using it for another purpose &lt;strong&gt;might lead to bugs&lt;/strong&gt; if changes occur on this API for the benefit of its original purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to refactor your codebase
&lt;/h2&gt;

&lt;p&gt;A reminder of the previous usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DataService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&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;Data&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;Data&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;updateData&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;Data&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="k"&gt;this&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="nf"&gt;emit&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The required changes are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the type from EventEmitter to Subject&lt;/li&gt;
&lt;li&gt;the exposure of the Subject as a private resource for the service to avoid external emissions&lt;/li&gt;
&lt;li&gt;the creation of a public Observable version of the Subject you can subscribe to&lt;/li&gt;
&lt;li&gt;an update to match the API to emit a new value
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DataService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// change the type and the visibility&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;dataSubject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Subject&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Data&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Subject&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Data&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// create a public observable out of the subject for external usage&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;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Data&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataSubject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asObservable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nf"&gt;updateData&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;Data&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="c1"&gt;// update the API&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;dataSubject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Such a move is also a great opportunity to explore &lt;a href="https://rxjs.dev/guide/subject" rel="noopener noreferrer"&gt;variant Subjects&lt;/a&gt;: BehaviorSubject, ReplaySubject, and AsynSubject.&lt;/p&gt;

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

</description>
      <category>angular</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
    <item>
      <title>[GitHub Actions] Complete CI-CD Javascript Workflow</title>
      <dc:creator>Gérôme Grignon</dc:creator>
      <pubDate>Thu, 09 Dec 2021 00:59:51 +0000</pubDate>
      <link>https://dev.to/geromegrignon/github-actions-full-ci-cd-javascript-workflow-39om</link>
      <guid>https://dev.to/geromegrignon/github-actions-full-ci-cd-javascript-workflow-39om</guid>
      <description>&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;p&gt;This workflow includes common &lt;strong&gt;continuous integration/deployment&lt;/strong&gt; tasks you can easily reuse for any web javascript project.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;collaboration comments&lt;/li&gt;
&lt;li&gt;quality tests&lt;/li&gt;
&lt;li&gt;deployment on &lt;strong&gt;Netlify&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;audit with &lt;strong&gt;Lighthouse&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It works on push and pull request situations.&lt;/p&gt;




&lt;p&gt;To showcase this workflow, i chose the &lt;a href="https://github.com/gothinkster/dojo-realworld-example-app" rel="noopener noreferrer"&gt;Dojo RealWorld&lt;/a&gt; implementation.&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%2Fey0bk932mqxvjz9s4kl3.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%2Fey0bk932mqxvjz9s4kl3.png" alt="RealWorld Dojo banner" width="800" height="124"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  My Workflow
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/gothinkster/dojo-realworld-example-app/tree/main/.github/workflows" rel="noopener noreferrer"&gt;Repository workflows&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%2F5qsh60x2ahchzi8de1b2.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%2F5qsh60x2ahchzi8de1b2.png" alt="Workflow" width="800" height="295"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h4&gt;
  
  
  Collaboration first!
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;Alone we can do so little; together we can do so much.&lt;br&gt;
&lt;em&gt;Helen Keller&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&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%2Fci4fj4rb2n3cbp7eefph.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%2Fci4fj4rb2n3cbp7eefph.png" alt="Communication workflow" width="282" height="144"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open source contributions are not just about code.&lt;br&gt;
&lt;strong&gt;That's all about people&lt;/strong&gt; collaborating to move a project forward.&lt;/p&gt;

&lt;p&gt;If the contributor is making their first pull request to the project, &lt;strong&gt;welcome them&lt;/strong&gt; accordingly. First open source contributions can be overwhelming as there so many considerations: code of conduct, license, guidelines...&lt;/p&gt;

&lt;p&gt;Even if GitHub makes it easy by onboarding new contributors when they land on a project, don't hesitate to provide additional context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;first_interaction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.event_name == 'pull_request'&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;first&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;interaction'&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/first-interaction@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;repo-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;pr-message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;Thanks for your first pull request on this project!&lt;/span&gt;
            &lt;span class="s"&gt;This is a kindly reminder to read the following resources:&lt;/span&gt;
            &lt;span class="s"&gt;- [code of conduct]()&lt;/span&gt;
            &lt;span class="s"&gt;- [contribution guidelines]()&lt;/span&gt;
            &lt;span class="s"&gt;It'll help us to review your contribution and to ensure it's aligned with our standards.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;blockquote&gt;
&lt;p&gt;I'm not a new contributor! Who cares?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not being a new contributor doesn't mean you should be ignored. As a review can be delayed, provide an instant comment to welcome new contributions. Even an automated one shows &lt;strong&gt;how much you care&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;greetings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.event_name == 'pull_request'&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kerhub/saved-replies@v1.0.0&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;secrets.GITHUB_TOKEN&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
          &lt;span class="na"&gt;reply&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;Hi @${{ github.event.pull_request.user.login }}, thanks for being part of the community :heart:&lt;/span&gt;
            &lt;span class="s"&gt;We'll review your contribution as soon as possible!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h4&gt;
  
  
  Reusable workflows
&lt;/h4&gt;

&lt;p&gt;When i started this workflow, i used &lt;code&gt;actions/cache&lt;/code&gt; to cache dependencies and speed up the workflows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Cache node modules&lt;/span&gt;
        &lt;span class="s"&gt;uses&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/cache@v2&lt;/span&gt;
        &lt;span class="s"&gt;env&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;cache-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cache-node-modules&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;~/.npm&lt;/span&gt;
          &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}&lt;/span&gt;
          &lt;span class="na"&gt;restore-keys&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;${{ runner.os }}-build-${{ env.cache-name }}-&lt;/span&gt;
            &lt;span class="s"&gt;${{ runner.os }}-build-&lt;/span&gt;
            &lt;span class="s"&gt;${{ runner.os }}-&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Meanwhile i discovered some changes happened to &lt;code&gt;actions/setup-node&lt;/code&gt; in July, removing the need of the previous boilerplate&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%2Fjw6z5495upo5lcduzaqz.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%2Fjw6z5495upo5lcduzaqz.png" alt="GitHub Actions: Setup-node now supports dependency caching" width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;Time to refactor? Not so much!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Such change didn't affect my workflow as such implementation detail was already hidden in a dedicated and reusable job by using the GitHub new feature: &lt;a href="https://docs.github.com/en/actions/learn-github-actions/reusing-workflows" rel="noopener noreferrer"&gt;Reusable Workflows&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This reusable workflow is isolated in a &lt;a href="https://github.com/kerhub/reusable-workflows" rel="noopener noreferrer"&gt;dedicated repository&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_call&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;node_job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;node&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;job'&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v2.4.1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;14'&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;npm'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{inputs.command}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h4&gt;
  
  
  Automate quality checks
&lt;/h4&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%2F1rbzu5nr6o55mubu59x5.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%2F1rbzu5nr6o55mubu59x5.png" alt="Quality checks" width="284" height="170"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The quality checks use the previous reusable workflow&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;Make your code &lt;strong&gt;Prettier&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://prettier.io/" rel="noopener noreferrer"&gt;Prettier&lt;/a&gt; is a famous code formatter.&lt;br&gt;
It removes all original styling* and ensures that all outputted code conforms to a consistent style.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;prettier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kerhub/reusable-workflows/.github/workflows/node-job.yml@main&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run prettier --check \"**\"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;blockquote&gt;
&lt;p&gt;Ensure maintainability with a &lt;strong&gt;linter&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://eslint.org/" rel="noopener noreferrer"&gt;ESLint&lt;/a&gt; is a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code, with the goal of making code more consistent and avoiding bugs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;linter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kerhub/reusable-workflows/.github/workflows/node-job.yml@main&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx eslint --fix src/**/*.ts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;blockquote&gt;
&lt;p&gt;Quality means doing it right even when no one is looking.&lt;br&gt;
&lt;em&gt;Henry Ford&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The future yourself will thank you for being able to push code with confidence thanks to tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;unit_tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;unit&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tests'&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kerhub/reusable-workflows/.github/workflows/node-job.yml@main&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h4&gt;
  
  
  Deployment
&lt;/h4&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%2Fdqezqatpwtlwzb1o3seq.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%2Fdqezqatpwtlwzb1o3seq.png" alt="Deployment workflow" width="266" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You don't want to manually deploy anymore.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;Review changes before they go live!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You want to preview changes due to a pull request.&lt;br&gt;
Netlify provides a &lt;strong&gt;preview feature&lt;/strong&gt; for such a need!&lt;br&gt;
By running this job on a pull request, a preview url will be created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;deploy_preview&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;deploy&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;preview'&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.event_name == 'pull_request'&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;prettier&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;linter&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;unit_tests&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kerhub/workflows/.github/workflows/netlify-preview-deploy.yml@main&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;build_directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;./output/dist'&lt;/span&gt;
    &lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;netlifyAuthToken&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;secrets.NETLIFY_AUTH_TOKEN&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
      &lt;span class="na"&gt;netlifySiteId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;secrets.NETLIFY_SITE_ID&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
      &lt;span class="na"&gt;repoToken&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;secrets.GITHUB_TOKEN&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It uses a &lt;a href="https://github.com/kerhub/reusable-workflows/blob/main/.github/workflows/netlify-preview-deploy.yml" rel="noopener noreferrer"&gt;reusable workflow&lt;/a&gt; once again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_call&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;build_directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
      &lt;span class="na"&gt;build_command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;npm&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;build'&lt;/span&gt;
    &lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;repoToken&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;netlifyAuthToken&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;netlifySiteId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;netlify&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v2.4.1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;14'&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;npm'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{inputs.build_command}}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Netlify&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nwtgck/actions-netlify@v1.2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;publish-dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;./output/dist'&lt;/span&gt;
          &lt;span class="na"&gt;github-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.repoToken }}&lt;/span&gt;
          &lt;span class="na"&gt;deploy-message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Deploy&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;GitHub&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Actions"&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;NETLIFY_AUTH_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.netlifyAuthToken }}&lt;/span&gt;
          &lt;span class="na"&gt;NETLIFY_SITE_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.netlifySiteId }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;blockquote&gt;
&lt;p&gt;Push to production!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By pushing code directly or by merging a pull request, this job will deploy a &lt;strong&gt;new version of your web app&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;deploy_live&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;deploy&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;live'&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.event_name == 'push'&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;prettier&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;linter&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;unit_tests&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kerhub/workflows/.github/workflows/netlify-live-deploy.yml@main&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;build_directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;./output/dist'&lt;/span&gt;
    &lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;netlifyAuthToken&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;secrets.NETLIFY_AUTH_TOKEN&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
      &lt;span class="na"&gt;netlifySiteId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;secrets.NETLIFY_SITE_ID&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It uses a &lt;a href="https://github.com/kerhub/reusable-workflows/blob/main/.github/workflows/netlify-live-deploy.yml" rel="noopener noreferrer"&gt;reusable workflow&lt;/a&gt; once again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_call&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;build_directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
      &lt;span class="na"&gt;build_command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;npm&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;build'&lt;/span&gt;
    &lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;netlifyAuthToken&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;netlifySiteId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;netlify&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v2.4.1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;14'&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;npm'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{inputs.build_command}}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Netlify&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nwtgck/actions-netlify@v1.2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;publish-dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;./output/dist'&lt;/span&gt;
          &lt;span class="na"&gt;production-deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;NETLIFY_AUTH_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.netlifyAuthToken }}&lt;/span&gt;
          &lt;span class="na"&gt;NETLIFY_SITE_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.netlifySiteId }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h4&gt;
  
  
  Audit
&lt;/h4&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%2Fqpmn5rxhxs120a0gblji.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%2Fqpmn5rxhxs120a0gblji.png" alt="Audit workflow" width="259" height="155"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/GoogleChrome/lighthouse" rel="noopener noreferrer"&gt;Lighthouse&lt;/a&gt; analyzes web apps and web pages, collecting modern performance metrics and insights on developer best practices.&lt;/p&gt;

&lt;p&gt;By pushing changes to your repository, it shouldn't affect performance and common best practices.&lt;/p&gt;

&lt;p&gt;The workflow includes 2 jobs for such a need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a preview one for the custom preview url (&lt;a href="https://github.com/kerhub/reusable-workflows/blob/main/.github/workflows/lighthouse-preview.yml" rel="noopener noreferrer"&gt;related reusable workflow&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;a live one using the production url (&lt;a href="https://github.com/kerhub/reusable-workflows/blob/main/.github/workflows/lighthouse-live.yml" rel="noopener noreferrer"&gt;related reusable workflow&lt;/a&gt;)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;lighthouse_preview&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;lighthouse&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;preview'&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy_preview&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kerhub/reusable-workflows/.github/workflows/lighthouse-preview.yml@main&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;siteName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dojo-realworld'&lt;/span&gt;
    &lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;netlifyAuthToken&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;secrets.NETLIFY_AUTH_TOKEN&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="na"&gt;lighthouse_live&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;lighthouse&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;live'&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy_live&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kerhub/reusable-workflows/.github/workflows/lighthouse-live.yml@main&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;siteUrl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://dojo-realworld.netlify.app/'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;blockquote&gt;
&lt;p&gt;Are we really done yet?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Open source contribution requires to spend significant time on it as you need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;understand its goal to ensure your contribution will match&lt;/li&gt;
&lt;li&gt;to read all guidelines&lt;/li&gt;
&lt;li&gt;to wait for a review before your contribution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Such dedication on a project worths to greet the contributor, not to just merge their work.&lt;/p&gt;

&lt;p&gt;But...there is no pull_request merged event.&lt;br&gt;
To identify a merged content, you need &lt;strong&gt;2 informations&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the event (push)&lt;/li&gt;
&lt;li&gt;the merged status of the pull request&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the solution i used in a dedicated workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;closed&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contribution-greetings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.event.pull_request.merged&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;greet the contributor&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kerhub/saved-replies@v1.0.0&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;reply&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;Thanks @${{ github.event.pull_request.user.login }}!&lt;/span&gt;
            &lt;span class="s"&gt;Your contribution is now fully part of this project :rocket:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Submission Category:
&lt;/h3&gt;

&lt;p&gt;Maintainer Must-Haves&lt;/p&gt;

&lt;h3&gt;
  
  
  Yaml File or Link to Code
&lt;/h3&gt;

&lt;p&gt;Workflow YAML Files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/gothinkster/dojo-realworld-example-app/blob/main/.github/workflows/ci-cd.yml" rel="noopener noreferrer"&gt;CI - CD&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gothinkster/dojo-realworld-example-app/blob/main/.github/workflows/merge-greetings.yml" rel="noopener noreferrer"&gt;Merge Greetings&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Additional Resources / Info
&lt;/h3&gt;

&lt;p&gt;GitHub Actions used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/actions/checkout" rel="noopener noreferrer"&gt;actions/checkout&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/actions/setup-node" rel="noopener noreferrer"&gt;actions/setup-node&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/actions/first-interaction" rel="noopener noreferrer"&gt;actions/first-interaction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kerhub/saved-replies" rel="noopener noreferrer"&gt;kerhub/saved-replies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/treosh/lighthouse-ci-action" rel="noopener noreferrer"&gt;treosh/lighthouse-ci-action&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kamranayub/wait-for-netlify-action" rel="noopener noreferrer"&gt;kamranayub/wait-for-netlify-action&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nwtgck/actions-netlify" rel="noopener noreferrer"&gt;nwtgck/actions-netlify&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GitHub Reusable Workflows created:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/kerhub/reusable-workflows/blob/main/.github/workflows/node-job.yml" rel="noopener noreferrer"&gt;node-job&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kerhub/reusable-workflows/blob/main/.github/workflows/netlify-preview-deploy.yml" rel="noopener noreferrer"&gt;netlify-preview-deploy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kerhub/reusable-workflows/blob/main/.github/workflows/netlify-live-deploy.yml" rel="noopener noreferrer"&gt;netlify-live-deploy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kerhub/reusable-workflows/blob/main/.github/workflows/lighthouse-preview.yml" rel="noopener noreferrer"&gt;lighthouse-preview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kerhub/reusable-workflows/blob/main/.github/workflows/lighthouse-live.yml" rel="noopener noreferrer"&gt;lighthouse-live&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>actionshackathon21</category>
      <category>netlify</category>
      <category>github</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
