<?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: SAVITA WADJE</title>
    <description>The latest articles on DEV Community by SAVITA WADJE (@savitawadje).</description>
    <link>https://dev.to/savitawadje</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4086885%2F29c3732d-7dfc-4297-932e-3ca520588b2a.jpg</url>
      <title>DEV Community: SAVITA WADJE</title>
      <link>https://dev.to/savitawadje</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/savitawadje"/>
    <language>en</language>
    <item>
      <title>Micro Frontends in Angular: A Simple Setup You Can Actually Follow</title>
      <dc:creator>SAVITA WADJE</dc:creator>
      <pubDate>Fri, 04 Sep 2026 12:08:59 +0000</pubDate>
      <link>https://dev.to/savitawadje/micro-frontends-in-angular-a-simple-setup-you-can-actually-follow-4f48</link>
      <guid>https://dev.to/savitawadje/micro-frontends-in-angular-a-simple-setup-you-can-actually-follow-4f48</guid>
      <description>&lt;p&gt;Micro frontends sound complicated the first time you read about them, separate teams, separate deployments, separate frameworks even. But the core idea is simpler than it sounds, and getting a basic setup running in Angular does not take as much effort as most articles make it seem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What a micro frontend actually is&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of building one large Angular app that owns every feature, you split the app into smaller, independently built and deployed pieces. Each piece can be developed, tested, and released by a different team, on its own timeline, without waiting on everyone else.&lt;/p&gt;

&lt;p&gt;A shell app (sometimes called the host) loads these smaller apps (called remotes) at runtime and stitches them together into one experience for the user.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why bother with this&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For a small app, honestly, you probably should not. Micro frontends add real complexity, and for a single team working on one codebase, a normal monolith Angular app is usually the better call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it starts to make sense:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Multiple teams working on the same product, each owning a different section&lt;br&gt;
Different parts of the app needing to release on different schedules&lt;br&gt;
Large legacy apps where a full rewrite is not realistic, but individual sections can be modernized one at a time&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tool that makes this practical: Module Federation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Module Federation, originally a Webpack feature, lets one application load code from another application at runtime, without bundling it together at build time. Angular supports this through the &lt;code&gt;@angular-architects/module-federation&lt;/code&gt; package, which handles most of the configuration for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setting up a basic example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's build the simplest possible version: a shell app that loads one remote app containing a single feature.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Create both apps&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ng new shell-app &lt;span class="nt"&gt;--routing&lt;/span&gt; &lt;span class="nt"&gt;--style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;css
ng new remote-app &lt;span class="nt"&gt;--routing&lt;/span&gt; &lt;span class="nt"&gt;--style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;css
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Add Module Federation to both&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;shell-app
ng add @angular-architects/module-federation &lt;span class="nt"&gt;--project&lt;/span&gt; shell-app &lt;span class="nt"&gt;--port&lt;/span&gt; 4200 &lt;span class="nt"&gt;--type&lt;/span&gt; host

&lt;span class="nb"&gt;cd&lt;/span&gt; ../remote-app
ng add @angular-architects/module-federation &lt;span class="nt"&gt;--project&lt;/span&gt; remote-app &lt;span class="nt"&gt;--port&lt;/span&gt; 4201 &lt;span class="nt"&gt;--type&lt;/span&gt; remote
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generates a webpack.config.js in each project and wires up the basic federation setup automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Configure the remote app&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;remote-app/webpack.config.js&lt;/code&gt;, expose the module you want the shell to be able to load:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;withModuleFederationPlugin&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;remoteApp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;exposes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./Module&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/app/remote/remote.module.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;shared&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;shareAll&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;singleton&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;strictVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;requiredVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auto&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;Create a simple feature module in the remote app to actually expose:&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;// remote/remote.module.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NgModule&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CommonModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@angular/common&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RouterModule&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RemoteComponent&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;./remote.component&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;NgModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;declarations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;RemoteComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;CommonModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;RouterModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forChild&lt;/span&gt;&lt;span class="p"&gt;([{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RemoteComponent&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;RemoteModule&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// remote/remote.component.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Component&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="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-remote&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;h2&amp;gt;Hello from the Remote App&amp;lt;/h2&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RemoteComponent&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;Step 4: Configure the shell app&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;shell-app/webpack.config.js&lt;/code&gt;, tell the shell where to find the remote:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;withModuleFederationPlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;remotes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;remoteApp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;remoteApp@http://localhost:4201/remoteEntry.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;shared&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;shareAll&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;singleton&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;strictVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;requiredVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auto&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;Then add a lazy-loaded route in the shell that points to the remote module:&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;// shell-app routing&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Routes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;remote-feature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;loadChildren&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nf"&gt;loadRemoteModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;remoteEntry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:4201/remoteEntry.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;remoteName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;remoteApp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;exposedModule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./Module&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RemoteModule&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;Step 5: Run both apps&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# terminal 1&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;remote-app &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ng serve

&lt;span class="c"&gt;# terminal 2&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;shell-app &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ng serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visit the shell app and navigate to /remote-feature. Angular loads the remote app's code at runtime and renders it right inside the shell, even though it is a completely separate build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The part that actually trips people up&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Shared dependencies. Both apps need to agree on shared library versions, especially Angular itself. If the shell and remote load different versions of Angular, you end up with duplicated frameworks running side by side, which is slow and buggy. The &lt;code&gt;shared&lt;/code&gt;config with &lt;code&gt;singleton: true&lt;/code&gt; handles this, but mismatched versions between the two apps are the most common source of runtime errors when people first try this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Routing across apps.&lt;/strong&gt; The shell owns the top level routes. Remote apps expose modules, they do not control the browser URL directly. Keeping this boundary clear early on avoids a lot of confusion later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Independent deployment.&lt;/strong&gt; In a real setup, the remote app gets built and deployed separately, and its &lt;code&gt;remoteEntry.js&lt;/code&gt; URL points to wherever it actually lives in production, not localhost. That URL becomes the contract between the shell and the remote.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When this is worth the complexity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you are working solo or on a small team, stick with a normal Angular app, monoliths are simpler and Module Federation adds real overhead for no benefit at that scale. It starts paying off once you have multiple teams, independent release cycles, or a large legacy app you want to modernize piece by piece instead of all at once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wrapping up&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Micro frontends are not magic, they are really just "load this other app's code at runtime instead of bundling it together." Once that clicks, the actual Angular setup is mostly configuration, not something conceptually hard.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>architecture</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why FAIR Data Validators Don't Agree With Each Other</title>
      <dc:creator>SAVITA WADJE</dc:creator>
      <pubDate>Fri, 04 Sep 2026 11:56:23 +0000</pubDate>
      <link>https://dev.to/savitawadje/why-fair-data-validators-dont-agree-with-each-other-50e8</link>
      <guid>https://dev.to/savitawadje/why-fair-data-validators-dont-agree-with-each-other-50e8</guid>
      <description>&lt;p&gt;This one is from my masters coursework, a research internship I did on something most developers have probably never heard of: FAIR data validators. Stick with me, the actual finding is more interesting than the name suggests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem I started with&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Research data is supposed to follow something called the FAIR principles. Findable, Accessible, Interoperable, Reusable. The idea is simple: if a dataset follows these principles properly, other researchers can actually find it, use it, and trust it.&lt;/p&gt;

&lt;p&gt;There are tools that automatically check how well a dataset follows FAIR, F-UJI, FAIR Evaluator, and FAIR Checker are the three big ones. You give them a dataset, they give you a compliance score.&lt;/p&gt;

&lt;p&gt;Here is the question nobody had really answered before: if you run the same dataset through all three tools, do they agree?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spoiler, they do not&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I took datasets from Zenodo and DataCite and ran them through all three validators. Same dataset, same moment, three different tools claiming to measure the same thing.&lt;/p&gt;

&lt;p&gt;On one climate science dataset, the overall scores came out like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;F-UJI: 65%&lt;/li&gt;
&lt;li&gt;FAIR Checker: 51%&lt;/li&gt;
&lt;li&gt;FAIR Evaluator: 57%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A 14 point gap on the exact same dataset. That is not a rounding error, that is three tools genuinely disagreeing about whether the same data is FAIR.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why? It comes down to vague words&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This was the part that actually surprised me. I expected the disagreement to be random, like the tools just had bugs or different implementations. It was not random at all.&lt;/p&gt;

&lt;p&gt;The FAIR specification uses words like formal, clear, and accessible, but never defines them precisely. So every tool that implements a validator has to make its own engineering decision about what those words mean. All of them are reasonable. All of them are different.&lt;/p&gt;

&lt;p&gt;Look at how differently the tools interpreted a rule that just says a dataset needs a formal knowledge language:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;F-UJI checks for JSON-LD or Schema.org in the metadata&lt;/li&gt;
&lt;li&gt;FAIR Checker requires a registered OWL or RDF ontology URI&lt;/li&gt;
&lt;li&gt;FAIR Evaluator just checks if any structured vocabulary is referenced at all&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three completely different technical checks, all built from the same vague sentence.&lt;/p&gt;

&lt;p&gt;Compare that to a rule that is actually specific, like requiring a unique identifier. A DOI either resolves or it does not. All three tools agreed perfectly there, zero gap.&lt;/p&gt;

&lt;p&gt;So the pattern became clear pretty fast: vague rules produce big disagreements, specific rules produce none.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finding exactly where the gaps live&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once I saw the pattern, I wanted to pin down precisely which sub-principles were causing the disagreement, not just say "some things are inconsistent."&lt;/p&gt;

&lt;p&gt;Three sub-principles kept showing up with the highest gaps, every single time:&lt;/p&gt;

&lt;p&gt;I1, whether the dataset uses a formal knowledge language, gap of 0.3&lt;br&gt;
R1.1, whether the dataset has a clear usage license, gap of 0.4&lt;br&gt;
A2, whether metadata persists and stays accessible, gap of 0.3&lt;/p&gt;

&lt;p&gt;Meanwhile things like unique identifiers and basic HTTP accessibility stayed at zero gap across all three tools, every time. The disagreement was not scattered randomly across all 15 sub-principles, it was concentrated in exactly the ones with the vaguest wording.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can you actually fix this?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This was the part I found most satisfying. If the disagreement comes from vague metadata, then maybe better metadata closes the gap, regardless of which tool is checking it.&lt;/p&gt;

&lt;p&gt;So I built what I called a Minimum Metadata Profile, a small, specific set of fields designed to satisfy the strictest interpretation of each rule:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A DOI that actually resolves over HTTP&lt;/li&gt;
&lt;li&gt;Title and description in plain text&lt;/li&gt;
&lt;li&gt;A machine readable license URI, not just a license name&lt;/li&gt;
&lt;li&gt;An OWL or RDF ontology URI, using something standard like Dublin Core&lt;/li&gt;
&lt;li&gt;An ORCID for the author, plus a related work DOI&lt;/li&gt;
&lt;li&gt;Proper DataCite metadata registration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then I ran the experiment. Took a dataset with no profile applied, ran all three validators, recorded the scores. Applied the profile, uploaded it to Zenodo again, ran all three validators on the improved version.&lt;/p&gt;

&lt;p&gt;Every score went up. And more importantly, the gap between the tools shrank significantly. Writing metadata well enough to satisfy the strictest tool ended up satisfying the more lenient ones automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this actually means, practically&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you are a researcher or data manager wondering which FAIR score to trust, the honest answer is that the score itself is less important than understanding why it differs. The disagreement is not a bug in any one tool, it is a structural problem with how the FAIR principles are written.&lt;/p&gt;

&lt;p&gt;The practical takeaway I landed on: do not just chase a score from one validator. Build metadata that satisfies the strictest possible interpretation of each rule, and the scores across every tool tend to converge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is next&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The bigger fix has to happen at the specification level, not the tool level. My recommendation, and something worth pushing for through groups like the Research Data Alliance, is a unified technical definition for the vague sub-principles, so validators stop having to guess what "formal" or "clear" actually means. Until that happens, every validator will keep making its own reasonable, and different, engineering decision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why I am sharing this here&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This was research work, but the core lesson felt very familiar to anything in software: specifications that use natural language instead of precise technical definitions will always get implemented inconsistently. I have seen smaller versions of this exact problem in API docs, in style guides, in "should be self explanatory" requirements at work. Vague words always eventually produce disagreement, whether it is three FAIR validators or three developers on the same team.&lt;/p&gt;

</description>
      <category>computerscience</category>
      <category>data</category>
      <category>science</category>
    </item>
    <item>
      <title>Simple Practices to Optimize Angular Applications</title>
      <dc:creator>SAVITA WADJE</dc:creator>
      <pubDate>Fri, 04 Sep 2026 11:44:01 +0000</pubDate>
      <link>https://dev.to/savitawadje/simple-practices-to-optimize-angular-applications-e6o</link>
      <guid>https://dev.to/savitawadje/simple-practices-to-optimize-angular-applications-e6o</guid>
      <description>&lt;p&gt;Angular apps can slow down quietly as they grow, more components, more data, more subscriptions nobody remembers to clean up. None of the fixes below are advanced tricks, just habits that make a real difference once you apply them consistently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Use OnPush change detection&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By default, Angular checks every component on every change detection cycle, even ones that have not actually changed. Switching to OnPush tells Angular to only re-check a component when its inputs change or an event fires inside it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app-user-card&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="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;./user-card.component.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserCardComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&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;This alone can meaningfully cut down unnecessary checks in larger apps, especially ones with deep component trees.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Unsubscribe from observables properly&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Forgetting to unsubscribe is one of the most common sources of memory leaks in Angular apps. Subscriptions that outlive their component keep doing work in the background long after the component is gone.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;destroy$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Subject&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="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;userService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUser&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;takeUntil&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;destroy$&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;user&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&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;span class="nf"&gt;ngOnDestroy&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;destroy$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;destroy$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The async pipe in templates is even better when possible, it subscribes and unsubscribes automatically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;*ngIf=&lt;/span&gt;&lt;span class="s"&gt;"user$ | async as user"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  {{ user.name }}
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Use trackBy with ngFor&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Without trackBy, Angular re-renders the entire list whenever the underlying array reference changes, even if most items stayed the same. trackBy tells Angular how to identify individual items, so it only updates what actually changed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;trackByUserId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&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="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&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;return&lt;/span&gt; &lt;span class="nx"&gt;user&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;*ngFor=&lt;/span&gt;&lt;span class="s"&gt;"let user of users; trackBy: trackByUserId"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  {{ user.name }}
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Small change, noticeable difference on any list that updates frequently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Lazy load feature modules&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Loading the entire app upfront means users wait for code they might not even use in that session. Lazy loading splits your app into chunks that load only when needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Routes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;loadChildren&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./admin/admin.module&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AdminModule&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 keeps your initial bundle smaller, which directly improves first load time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Avoid function calls in templates&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Calling a function directly in a template runs it on every single change detection cycle, not just when its inputs actually change.&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;!-- Runs on every change detection cycle --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;{{ calculateTotal() }}&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Calculated once, only recalculates when dependencies change --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;{{ total }}&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a value needs computing, calculate it once in the component class (or with a pure pipe) instead of calling a function straight from the template.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Use pure pipes for transformations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pipes marked as pure only re-run when their input reference changes, unlike template function calls, which run constantly.&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;Pipe&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;formatCurrency&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;pure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FormatCurrencyPipe&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;PipeTransform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;transform&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;number&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="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;{{ price | formatCurrency }}&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same result as a template function, far less repeated work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Virtual scroll for long lists&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Rendering hundreds or thousands of DOM elements at once, even off screen, is expensive. Angular CDK's virtual scroll only renders what is actually visible in the viewport.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;html
&lt;span class="nt"&gt;&amp;lt;cdk-virtual-scroll-viewport&lt;/span&gt; &lt;span class="na"&gt;itemSize=&lt;/span&gt;&lt;span class="s"&gt;"50"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;*cdkVirtualFor=&lt;/span&gt;&lt;span class="s"&gt;"let item of items"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ item.name }}&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/cdk-virtual-scroll-viewport&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Makes a real difference on any list that can realistically grow large, like search results or activity feeds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What actually matters most&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If I had to pick just two to start with: OnPush change detection and properly unsubscribing from observables. Those two alone catch a large chunk of the performance issues I have run into in real Angular apps, the rest are worth adding as the app grows.&lt;/p&gt;

&lt;p&gt;Curious what other practices people rely on, especially anything specific to newer Angular features like signals, that changes some of this.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>frontend</category>
      <category>performance</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Bootstrap vs Tailwind CSS: How I Decide Which One to Use</title>
      <dc:creator>SAVITA WADJE</dc:creator>
      <pubDate>Fri, 04 Sep 2026 11:33:21 +0000</pubDate>
      <link>https://dev.to/savitawadje/bootstrap-vs-tailwind-css-how-i-decide-which-one-to-use-2pj9</link>
      <guid>https://dev.to/savitawadje/bootstrap-vs-tailwind-css-how-i-decide-which-one-to-use-2pj9</guid>
      <description>&lt;p&gt;"Bootstrap or Tailwind, which one is better?" is a question I used to have a strong opinion about. Now I think it depends entirely on the project. Here is how I actually decide.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The core difference&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Bootstrap gives you pre-built components: buttons, navbars, cards, modals, already styled and ready to drop in. Tailwind gives you utility classes: small, single-purpose classes you combine yourself to build your own design.&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;!-- Bootstrap: pre-styled component --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-primary"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Tailwind: build the style yourself --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Submit
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same button, completely different philosophy behind getting there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When I reach for Bootstrap&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Fast prototypes and internal tools. If I need something functional and reasonably clean quickly, not necessarily unique looking, Bootstrap gets me there fast. Admin panels, internal dashboards, quick MVPs.&lt;/p&gt;

&lt;p&gt;Teams without a dedicated designer. Bootstrap's components already look decent out of the box, which matters when there is no design system or designer to define one.&lt;/p&gt;

&lt;p&gt;Projects where consistency matters more than uniqueness. Bootstrap enforces a certain look and feel by default, which can be a feature, not a limitation, for internal tools where every screen should feel the same without much effort.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When I reach for Tailwind&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Custom designs that need to look distinct. If the project has real design requirements, a specific brand feel, custom spacing, unique components, Tailwind gets out of the way instead of fighting against Bootstrap's defaults.&lt;/p&gt;

&lt;p&gt;Projects that will grow and change a lot. Overriding Bootstrap's default styles gets messy over time, lots of !important, custom overrides stacked on top of the framework's own CSS. Tailwind avoids that problem because you are not overriding anything, you are just composing utilities.&lt;/p&gt;

&lt;p&gt;When bundle size and unused CSS matter. Tailwind purges unused styles in production, so your final CSS only includes what you actually used. Bootstrap ships more CSS by default, even for components you never touch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The trade-off nobody mentions enough&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Bootstrap is fast to start with and slower to customize. Tailwind is slower to start with (more decisions up front) and faster to customize later. Neither is free, the cost just shows up at a different point in the project.&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;!-- Overriding Bootstrap's default button padding --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-primary"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"padding: 10px 20px;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Submit
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Tailwind, same customization, no override needed --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"px-5 py-2.5 bg-blue-600 text-white rounded-md"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Submit
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Bootstrap, customization often means fighting the framework. With Tailwind, customization is just part of how you already write every class.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My rule of thumb&lt;/strong&gt;&lt;br&gt;
Internal tool, quick prototype, no designer involved → Bootstrap&lt;br&gt;
Custom product, brand-specific design, long-term project → Tailwind&lt;br&gt;
Team already knows one well → stick with it, familiarity usually beats theoretical advantages&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I actually use most these days&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I lean Tailwind for most client and product work now, mainly because customization pain shows up early with Bootstrap and I would rather deal with the utility class learning curve upfront than fight overrides six months in. But I still reach for Bootstrap without hesitation for quick internal tools where speed matters more than uniqueness.&lt;/p&gt;

&lt;p&gt;Curious where others land on this, especially if you have used both on projects of a similar size. Would love to hear a different take in the comments.&lt;/p&gt;

</description>
      <category>css</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building JobFit AI, an AI Resume Matcher with the MERN Stack</title>
      <dc:creator>SAVITA WADJE</dc:creator>
      <pubDate>Fri, 21 Aug 2026 15:19:39 +0000</pubDate>
      <link>https://dev.to/savitawadje/building-jobfit-ai-an-ai-resume-matcher-with-the-mern-stack-1d90</link>
      <guid>https://dev.to/savitawadje/building-jobfit-ai-an-ai-resume-matcher-with-the-mern-stack-1d90</guid>
      <description>&lt;p&gt;Job hunting means rewriting the same resume bullet points for every posting and guessing whether you're even a good match. So I built JobFit AI: paste your resume and a job description, and it scores the match, tells you what's missing, and rewrites bullet points to fit the role, powered by the Gemini API, with your history tracked over time.&lt;/p&gt;

&lt;p&gt;Here's how it's built, how it works, and how it might help you if you're job hunting too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem I was solving&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every job application starts the same way. Copy the resume, tweak a few lines, guess if the keywords match what the recruiter is looking for, submit, repeat. I was doing this manually dozens of times and had no real way to know if my resume actually matched a posting or just felt like it did.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwg0m34nc7z4kpha6bwid.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwg0m34nc7z4kpha6bwid.png" alt=" " width="800" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is basically the whole reason JobFit AI exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tech stack&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Frontend: React 19 + Vite, React Router, Tailwind CSS v4&lt;/li&gt;
&lt;li&gt;Backend: Node.js + Express, MongoDB with Mongoose&lt;/li&gt;
&lt;li&gt;Auth: JWT + bcrypt, email/password, own database&lt;/li&gt;
&lt;li&gt;AI: Google Gemini API (free tier)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Architecture&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Three layers, kept deliberately separate:&lt;br&gt;
&lt;strong&gt;1. Client (React)&lt;/strong&gt;&lt;br&gt;
Handles the resume/job description input, displays the match score and suggestions, and manages auth state. Kept as dumb as possible, all the real logic lives in the backend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. API layer (Express)&lt;/strong&gt;&lt;br&gt;
Handles auth, request validation, and talks to both MongoDB and the Gemini API. Each route is scoped tightly, one endpoint to submit a match request, one to fetch history, one to handle auth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Data layer (MongoDB + Mongoose)&lt;/strong&gt;&lt;br&gt;
Stores user accounts and a history of every resume/job match a user has run, so they can go back and compare results over time instead of losing them after one session.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// simplified match route&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;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/match&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authMiddleware&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;resumeText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;jobDescription&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&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;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resumeText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;jobDescription&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;aiResponse&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;geminiClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseMatchResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aiResponse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;MatchHistory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&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;resumeText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;jobDescription&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;suggestions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;suggestions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;res&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="nx"&gt;result&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;How the matching actually works&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The core of the app is a single prompt sent to Gemini that asks it to compare the resume against the job description and return three things: a match score, a list of missing skills or keywords, and rewritten bullet points that better align with the role.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resumeText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;jobDescription&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`
    Compare this resume against the job description below.
    Return a match score out of 100, a list of missing keywords,
    and 3 rewritten resume bullet points tailored to this job.

    Resume:
    &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;resumeText&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;

    Job Description:
    &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;jobDescription&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
  `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tricky part was not the prompt itself, it was getting Gemini to return something consistently structured that I could reliably parse into the UI, instead of a wall of freeform text.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0qr61lu0y8cvvwf8ckil.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0qr61lu0y8cvvwf8ckil.png" alt=" " width="800" height="544"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Challenges I ran into&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Structuring AI output reliably. Free-text responses looked fine but broke my UI whenever the format shifted slightly. I ended up being very explicit in the prompt about the exact structure I wanted back.&lt;/li&gt;
&lt;li&gt;Rate limits on the free tier. Had to add basic request throttling on my end so users could not spam the match endpoint and burn through the quota.&lt;/li&gt;
&lt;li&gt;Keeping history without bloating the database. Storing every resume and job description per request adds up fast, so I trimmed what gets stored long term versus what is just used for the immediate response.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What's next&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Better diffing between resume versions over time&lt;/li&gt;
&lt;li&gt;Exporting rewritten bullet points directly into a formatted resume&lt;/li&gt;
&lt;li&gt;Support for multiple resume profiles per user&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Try it / see the code&lt;br&gt;
&lt;a href="https://github.com/savitawadje/JobFit-AI" rel="noopener noreferrer"&gt;🔗 &lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Angular vs React: How I Decide Which to Use for a New Project</title>
      <dc:creator>SAVITA WADJE</dc:creator>
      <pubDate>Thu, 20 Aug 2026 15:15:22 +0000</pubDate>
      <link>https://dev.to/savitawadje/angular-vs-react-how-i-decide-which-to-use-for-a-new-project-309b</link>
      <guid>https://dev.to/savitawadje/angular-vs-react-how-i-decide-which-to-use-for-a-new-project-309b</guid>
      <description>&lt;p&gt;&lt;strong&gt;The wrong question&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"Angular vs React, which is better?" is the wrong question. Both are mature, well-supported, and can build the same things. The better question is: which one fits this specific project, team, and timeline? Here's how I actually think about it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When I reach for Angular&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Large, long-lived enterprise apps -Angular's opinionated structure (modules, services, DI) pays off when a team of 10+ devs needs consistency over years, not just weeks.&lt;/li&gt;
&lt;li&gt;Teams that want batteries included - routing, forms, HTTP client, and testing tools all come built-in. Less time spent choosing libraries, more time building.&lt;/li&gt;
&lt;li&gt;TypeScript-first teams - Angular is TypeScript by default, so if your team already thinks in strict typing, it feels native rather than bolted on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When I reach for React&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast-moving products, startups, MVPs  React's flexibility means you can ship quickly without committing to a rigid structure upfront.&lt;/li&gt;
&lt;li&gt;Teams that want to pick their own stack state management, routing, styling React lets you choose (Redux vs Zustand, React Router, Tailwind vs CSS-in-JS), which is powerful if your team has strong opinions.&lt;/li&gt;
&lt;li&gt;Hiring pool matters - React's ecosystem is larger, so if you're scaling a team fast, there's usually a deeper talent pool to pull from.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The trade-off nobody talks about enough&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Angular's structure feels restrictive early on but saves you from "a thousand ways to do the same thing" chaos later. React's flexibility feels great early on but can turn into inconsistency across a codebase if the team doesn't agree on conventions upfront.&lt;/p&gt;

&lt;p&gt;In other words: Angular front-loads discipline. React front-loads speed  and asks you to add discipline yourself, later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My rule of thumb&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enterprise app, long timeline, larger team → Angular&lt;/li&gt;
&lt;li&gt;MVP, startup, small team, need to move fast → React&lt;/li&gt;
&lt;li&gt;Not sure? Default to whichever your team already knows well — consistency beats "the better tool" almost every time.&lt;/li&gt;
&lt;li&gt;What's your experience?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'd love to hear how others make this call, especially if you've switched from one to the other mid-project. Drop your take in the comments.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>frontend</category>
      <category>react</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
