<?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: Damian Sire (Dev)</title>
    <description>The latest articles on DEV Community by Damian Sire (Dev) (@damiansire).</description>
    <link>https://dev.to/damiansire</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%2F1596605%2Fad6dcbc2-fc46-4102-a686-d89a4abfbc21.png</url>
      <title>DEV Community: Damian Sire (Dev)</title>
      <link>https://dev.to/damiansire</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/damiansire"/>
    <language>en</language>
    <item>
      <title>Do you still need SCSS? What Next.js and Angular 22.2 actually do with native CSS nesting</title>
      <dc:creator>Damian Sire (Dev)</dc:creator>
      <pubDate>Fri, 09 Oct 2026 07:45:07 +0000</pubDate>
      <link>https://dev.to/angular/do-you-still-need-scss-what-nextjs-and-angular-222-actually-do-with-native-css-nesting-1l96</link>
      <guid>https://dev.to/angular/do-you-still-need-scss-what-nextjs-and-angular-222-actually-do-with-native-css-nesting-1l96</guid>
      <description>&lt;p&gt;&lt;em&gt;Another comparison between Angular 22.2 and React/Next.js. The previous one covered &lt;code&gt;@boundary&lt;/code&gt;, the new block for template errors: &lt;a href="https://dev.to/angular/template-errors-react-and-nextjs-vs-angular-222s-boundary-4big"&gt;Template errors: React and Next.js vs Angular 22.2's @boundary&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Browsers now support nested CSS without a preprocessor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.card&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="err"&gt;.title&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600&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;amp;&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F478vlosuq0flx3pg1i8y.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%2F478vlosuq0flx3pg1i8y.png" alt="Timeline: Firefox 117 in August 2023, Chrome 120 and Safari 17.2 in December 2023" width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cross-browser support for the relaxed nesting syntax arrived in Firefox 117, Chrome 120 and Safari 17.2, according to &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/Nesting_selector" rel="noopener noreferrer"&gt;MDN's compatibility data&lt;/a&gt;. Chrome 112 and Safari 16.5 already handled nested class selectors like &lt;code&gt;.title&lt;/code&gt;; the later versions added nested rules that start with a tag, like &lt;code&gt;h2 {}&lt;/code&gt;. Since June 2026, nesting is &lt;a href="https://webstatus.dev/features/nesting" rel="noopener noreferrer"&gt;Baseline widely available&lt;/a&gt;. (Baseline "newly available" means it works in the latest Chrome, Edge, Firefox and Safari; "widely available" comes 30 months later.)&lt;/p&gt;

&lt;p&gt;Nesting was probably one of the most common reasons to use Sass (the language behind your &lt;code&gt;.scss&lt;/code&gt; files). Now it's native. The question is what happens when you combine it with each framework's style isolation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js:&lt;/strong&gt; CSS Modules rename every local class, nested ones included, so nesting just works; the build also flattens it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Angular:&lt;/strong&gt; the CLI flattens nesting before encapsulating, so it works in component styles. Avoid nesting under &lt;code&gt;::ng-deep&lt;/code&gt;, and know the 22.2 &lt;code&gt;:host&lt;/code&gt; trap if raw nesting reaches the compiler.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sass:&lt;/strong&gt; still worth it for mixins, loops, BEM names with &lt;code&gt;&amp;amp;__&lt;/code&gt;, breakpoint variables and custom Angular Material themes. For the rest, native CSS is enough.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  React and Next.js: you pick the isolation
&lt;/h2&gt;

&lt;p&gt;React doesn't scope styles. &lt;code&gt;className&lt;/code&gt; works like the HTML &lt;code&gt;class&lt;/code&gt; attribute (&lt;a href="https://react.dev/reference/react-dom/components/common#applying-css-styles" rel="noopener noreferrer"&gt;react.dev&lt;/a&gt;), so a CSS class applies to the whole page. The &lt;a href="https://nextjs.org/docs/app/getting-started/css" rel="noopener noreferrer"&gt;Next.js styling docs&lt;/a&gt; list several options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tailwind CSS:&lt;/strong&gt; recommended "for most styling needs".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CSS Modules:&lt;/strong&gt; a &lt;code&gt;.module.css&lt;/code&gt; file generates unique class names, so you can use &lt;code&gt;.card&lt;/code&gt; in different files without collisions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Global CSS:&lt;/strong&gt; for what is truly global.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;External stylesheets, Sass and CSS-in-JS:&lt;/strong&gt; the other options.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;styles&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;./card.module.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;…&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fekcot5r2do5aj1v1a4bg.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%2Fekcot5r2do5aj1v1a4bg.png" alt="CSS Modules: two files with .card become x7f2aW_card and k93qdP_card after the build" width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Isolation comes from &lt;strong&gt;renaming the classes&lt;/strong&gt;, and that covers nesting too: the build renames every local class it finds (anything not wrapped in &lt;code&gt;:global()&lt;/code&gt;), nested or not. We put the &lt;code&gt;.card&lt;/code&gt; snippet from the top in a &lt;code&gt;card.module.css&lt;/code&gt; and ran &lt;code&gt;next build&lt;/code&gt; (Next.js 16.4 with default settings; Turbopack, the default bundler, compiles CSS with &lt;a href="https://lightningcss.dev/" rel="noopener noreferrer"&gt;Lightning CSS&lt;/a&gt;). This is the output, reformatted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.mXu7RW_card&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.mXu7RW_card&lt;/span&gt; &lt;span class="nc"&gt;.mXu7RW_title&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.mXu7RW_card&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&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;Both classes got the file's prefix, and the build also flattened the nesting. It flattened it even with a browserslist of modern browsers only: Turbopack lowers nesting regardless of your targets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Angular: emulated encapsulation by default
&lt;/h2&gt;

&lt;p&gt;Angular scopes each component's styles with no setup. &lt;a href="https://angular.dev/guide/components/styling" rel="noopener noreferrer"&gt;Emulated encapsulation&lt;/a&gt; generates a unique HTML attribute per component (&lt;code&gt;_ngcontent-X&lt;/code&gt;), adds it to the elements in its template, and inserts it into every selector in its styles. It adds a second one, &lt;code&gt;_nghost-X&lt;/code&gt;, to the component's own host element (like &lt;code&gt;&amp;lt;app-card&amp;gt;&lt;/code&gt;), and that's what &lt;code&gt;:host&lt;/code&gt; selects.&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%2F7z2rouhhx4hj6bdjk7tx.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%2F7z2rouhhx4hj6bdjk7tx.png" alt="Angular emulated encapsulation: the template and the selectors get the same _ngcontent attribute" width="800" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Up to Angular 22.1, that rewrite &lt;strong&gt;didn't process style rules nested inside other style rules&lt;/strong&gt; (rules inside at-rules such as &lt;code&gt;@media&lt;/code&gt; were fine), as the description of &lt;a href="https://github.com/angular/angular/pull/50693" rel="noopener noreferrer"&gt;PR #50693&lt;/a&gt; says. With the &lt;code&gt;.card&lt;/code&gt; snippet from the top, &lt;code&gt;.title&lt;/code&gt; got no attribute, so it also styled the &lt;code&gt;.title&lt;/code&gt; of any child component inside the card.&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%2Fb69hpxiwc3p1syv6clax.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%2Fb69hpxiwc3p1syv6clax.png" alt="Before the fix: the nested .title got no attribute and also styled the .title of a child component inside the card" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So the CLI took a shortcut: since &lt;code&gt;@angular/build&lt;/code&gt; 19.2.8, released in April 2025, the default esbuild-based builders &lt;strong&gt;always flatten nesting&lt;/strong&gt; in component styles before they reach the compiler (&lt;a href="https://github.com/angular/angular-cli/commit/3d997feb68" rel="noopener noreferrer"&gt;commit&lt;/a&gt;). &lt;code&gt;.card { .title {} }&lt;/code&gt; reaches it as &lt;code&gt;.card .title {}&lt;/code&gt;. It doesn't depend on which browsers you support: we tested a 22.2.1 project with a &lt;code&gt;.browserslistrc&lt;/code&gt; of modern Chrome only, and it still flattened.&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%2F7dpcliikhdzhz9ttr2bh.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%2F7dpcliikhdzhz9ttr2bh.png" alt="Default CLI build, AOT or JIT: @angular/build flattens nesting before the compiler adds attributes. Styles that skip it (non-literal JIT styles, webpack dev builds, some libraries) reach the compiler raw" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Angular 22.2: the fix, more than three years later
&lt;/h2&gt;

&lt;p&gt;Angular 22.2 &lt;a href="https://github.com/angular/angular/commit/d0d7f57e0810a24ba16dbb1f2ab9f079a096fa3d" rel="noopener noreferrer"&gt;scopes nested rules&lt;/a&gt; when they reach the compiler. If the parent selector uses &lt;code&gt;::ng-deep&lt;/code&gt; (Angular's deprecated escape hatch that turns encapsulation off for the rest of the selector), nested rules aren't encapsulated: &lt;code&gt;:host ::ng-deep { .foo {} }&lt;/code&gt; behaves like &lt;code&gt;:host ::ng-deep .foo {}&lt;/code&gt; (&lt;a href="https://github.com/angular/angular/commit/a4f1a949486d20ce2bf9add427eea6629228b28b" rel="noopener noreferrer"&gt;commit&lt;/a&gt;). In 22.2.0 and 22.2.1, a nested rule under &lt;code&gt;::ng-deep&lt;/code&gt; could still get the attribute when an earlier rule also used &lt;code&gt;::ng-deep&lt;/code&gt;; 22.2.2 fixed it (&lt;a href="https://github.com/angular/angular/commit/fa63bfad14dfb94d2985d7e486c8b0b98c231e52" rel="noopener noreferrer"&gt;commit&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That exception only helps when raw nesting reaches the compiler. In a default CLI project, don't nest under &lt;code&gt;::ng-deep&lt;/code&gt; at all: the CLI's flattening turns &lt;code&gt;:host ::ng-deep { .foo {} }&lt;/code&gt; into &lt;code&gt;:is() .foo {}&lt;/code&gt;, which matches nothing (&lt;a href="https://github.com/angular/angular/issues/58996" rel="noopener noreferrer"&gt;issue #58996&lt;/a&gt;, open since November 2024).&lt;/p&gt;

&lt;p&gt;The first attempt dates from June 2023. A month later, Google's internal tests found a failure in a file with base64 images; the PR was marked as blocked in 2024 and closed in 2025. The second attempt was merged in August 2026 and shipped with Angular 22.2 in September: &lt;strong&gt;more than three years&lt;/strong&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq9fg8dahe94vzair1vc6.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%2Fq9fg8dahe94vzair1vc6.png" alt="Timeline from PR #50693 in June 2023 to Angular 22.2.0 in September 2026 and 22.2.2 in October 2026" width="800" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In a default CLI project you'll barely notice the fix, because your component styles already arrived flattened. It matters when raw nested CSS reaches the compiler: JIT components whose &lt;code&gt;styles&lt;/code&gt; isn't a string literal (the CLI can't see those at build time), dev builds on the webpack builders (&lt;code&gt;@angular-devkit/build-angular&lt;/code&gt;, which don't force flattening; production builds may still flatten it when minifying for your browser targets), or libraries built with ng-packagr that target only modern browsers, which ship nesting as is.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;:host&lt;/code&gt; and &lt;code&gt;&amp;amp;&lt;/code&gt; trap
&lt;/h3&gt;

&lt;p&gt;In those cases, 22.2 brings a new side effect. We ran the same CSS through the compiler from 22.1.8 and from 22.2.0:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* what you write */&lt;/span&gt;
&lt;span class="nd"&gt;:host&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="err"&gt;&amp;amp;.active&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="err"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* 22.1.8: works, the browser resolves [_nghost-X].active */&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;_nghost-X&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="err"&gt;&amp;amp;.active&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="err"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* 22.2.0: no match, the host doesn't have _ngcontent-X */&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;_nghost-X&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="err"&gt;&amp;amp;.active[_ngcontent-X]&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj1cngtpy3osh5dmx2kp6.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%2Fj1cngtpy3osh5dmx2kp6.png" alt="The host element has _nghost-X but not _ngcontent-X, so 22.2's rule stops matching; :host(.active) works" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We confirmed it in Chromium with a JIT build of 22.2.1 whose &lt;code&gt;styles&lt;/code&gt; was a constant, so the CLI couldn't flatten it: the rule stops applying. The same happens with &lt;code&gt;:host { &amp;amp;:hover {} }&lt;/code&gt;. It's already reported as &lt;a href="https://github.com/angular/angular/issues/71050" rel="noopener noreferrer"&gt;issue #71050&lt;/a&gt;, still unfixed in 22.2.2 (a fix, &lt;a href="https://github.com/angular/angular/pull/71152" rel="noopener noreferrer"&gt;PR #71152&lt;/a&gt;, is open). Use the function form of &lt;code&gt;:host&lt;/code&gt; instead: &lt;code&gt;:host(.active)&lt;/code&gt; and &lt;code&gt;:host(:hover)&lt;/code&gt;, which is also the form that works with Shadow DOM. Your own component styles in a default CLI build (AOT or JIT with literal styles) aren't affected, because the CLI flattens them first.&lt;/p&gt;

&lt;p&gt;This story shows how fragile it is to emulate encapsulation by rewriting selectors: every new CSS syntax has to be taught to the compiler, and every fix can break another combination.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do you still need SCSS?
&lt;/h2&gt;

&lt;p&gt;Sass is the most common preprocessor, so it's the one we compare. &lt;a href="https://lesscss.org/features/#mixins-feature-mixins-parametric-feature" rel="noopener noreferrer"&gt;Less&lt;/a&gt; offers the same features discussed below (mixins with parameters, and loops through &lt;a href="https://lesscss.org/features/#mixins-feature-loops-feature" rel="noopener noreferrer"&gt;recursive mixins&lt;/a&gt; or &lt;a href="https://lesscss.org/functions/#list-functions-each" rel="noopener noreferrer"&gt;&lt;code&gt;each()&lt;/code&gt;&lt;/a&gt;), so the same reasoning applies. One practical difference: the Angular CLI compiles both (Less after you install the &lt;code&gt;less&lt;/code&gt; package), while Next.js doesn't support Less out of the box (for Turbopack, its default bundler, it's &lt;a href="https://nextjs.org/docs/app/api-reference/turbopack" rel="noopener noreferrer"&gt;"planned via plugins"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvuxywkwhc1m8stmfyjz6.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%2Fvuxywkwhc1m8stmfyjz6.png" alt="Native now: nesting, custom properties, math and color functions. Still a preprocessor: mixins, loops, BEM with &amp;amp;__, variables in media queries" width="800" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What Sass used to give you that is now native:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nesting:&lt;/strong&gt; native, with two differences. &lt;code&gt;&amp;amp;&lt;/code&gt; doesn't concatenate, so there's no BEM-style &lt;code&gt;&amp;amp;__title&lt;/code&gt;, and its specificity is computed like &lt;code&gt;:is()&lt;/code&gt; (&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Nesting/Using" rel="noopener noreferrer"&gt;MDN&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Variables:&lt;/strong&gt; custom properties (&lt;code&gt;--brand-color&lt;/code&gt;), which also change at runtime. But you can't use them in a media query condition: &lt;code&gt;@media (min-width: var(--bp))&lt;/code&gt; doesn't work, while a Sass variable does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Math:&lt;/strong&gt; &lt;code&gt;calc()&lt;/code&gt;, &lt;code&gt;min()&lt;/code&gt;, &lt;code&gt;max()&lt;/code&gt;, &lt;code&gt;clamp()&lt;/code&gt; and trigonometric functions like &lt;code&gt;sin()&lt;/code&gt; are Baseline widely available. &lt;code&gt;round()&lt;/code&gt;, &lt;code&gt;mod()&lt;/code&gt; and &lt;code&gt;rem()&lt;/code&gt; are newer: Baseline newly available since May 2024, widely available from November 2026.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Color functions:&lt;/strong&gt; &lt;code&gt;color-mix()&lt;/code&gt; (Baseline widely available) can lighten or darken by mixing with white or black. For exact adjustments like Sass's &lt;code&gt;color.adjust()&lt;/code&gt; (the old, now deprecated &lt;code&gt;lighten()&lt;/code&gt; and &lt;code&gt;darken()&lt;/code&gt;), there's relative color syntax, which works in all current major browsers (Baseline since September 2024) but won't be widely available until March 2027.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What is still Sass:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mixins:&lt;/strong&gt; reusable blocks of styles with parameters.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="k"&gt;@mixin&lt;/span&gt; &lt;span class="nf"&gt;truncate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;webkit-box&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="na"&gt;-webkit-line-clamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$lines&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="na"&gt;-webkit-box-orient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;vertical&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.description&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;@include&lt;/span&gt; &lt;span class="nd"&gt;truncate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&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;Loops:&lt;/strong&gt; generating many rules from a list.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="k"&gt;@each&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$color&lt;/span&gt; &lt;span class="nf"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;green&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;red&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;warning&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;orange&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.badge-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$color&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;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%2Fzjrsbgelvo3gfsooaeyj.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%2Fzjrsbgelvo3gfsooaeyj.png" alt="A mixin clamps text to two lines; an @each loop generates three badge classes" width="800" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Custom properties cover part of what a mixin did (a reusable value you can change per component), but CSS has no way to include a parameterized block inside another rule; the closest is a utility class that reads custom properties, which you have to add in the markup. There's also no way to generate rules in a loop. Native mixins (&lt;code&gt;@mixin&lt;/code&gt;) aren't enabled by default in any browser (Chrome has them only as a &lt;a href="https://groups.google.com/a/chromium.org/g/blink-dev/c/g0dSC7CvdlA/m/Wz30GgIZAwAJ" rel="noopener noreferrer"&gt;prototype behind a flag&lt;/a&gt;), and &lt;code&gt;@function&lt;/code&gt;, which returns a value rather than a block of declarations, ships only in &lt;a href="https://developer.chrome.com/blog/new-in-chrome-139" rel="noopener noreferrer"&gt;Chrome&lt;/a&gt; and Edge 139+. And if you want a custom Angular Material theme through its supported theming API, you still need Sass (a file with the &lt;code&gt;mat.theme&lt;/code&gt; mixin); without it, the supported alternative is one of the prebuilt CSS themes (&lt;a href="https://github.com/angular/components/blob/main/guides/theming.md" rel="noopener noreferrer"&gt;guide&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond emulated encapsulation
&lt;/h2&gt;

&lt;p&gt;The platform has two native ways to keep styles in their place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shadow DOM, available today.&lt;/strong&gt; Angular offers &lt;code&gt;ViewEncapsulation.ShadowDom&lt;/code&gt;, which affects event propagation and content projection. Angular also registers each regular shadow root with its shared style host, so Angular-managed component styles are copied into it. &lt;code&gt;ExperimentalIsolatedShadowDom&lt;/code&gt; skips that shared-style injection, although normal CSS inheritance, including custom properties, still crosses the shadow boundary (&lt;a href="https://angular.dev/guide/components/styling" rel="noopener noreferrer"&gt;guide&lt;/a&gt;, &lt;a href="https://github.com/angular/angular/blob/main/packages/platform-browser/src/dom/dom_renderer.ts" rel="noopener noreferrer"&gt;implementation&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;@scope&lt;/code&gt;, looking ahead.&lt;/strong&gt; &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@scope" rel="noopener noreferrer"&gt;&lt;code&gt;@scope&lt;/code&gt;&lt;/a&gt; limits styles to a DOM subtree. It works since Chrome 118, Firefox 146 and Safari 17.4 (Safari 26.0 to 26.3 didn't apply it to &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt;; &lt;a href="https://webkit.org/blog/17862/webkit-features-for-safari-26-4/" rel="noopener noreferrer"&gt;26.4 fixed it&lt;/a&gt;), and it's Baseline newly available since March 2026. There's an &lt;a href="https://github.com/angular/angular/issues/64851" rel="noopener noreferrer"&gt;open request&lt;/a&gt; to add an encapsulation mode based on it, and a team member replied that it's something they've been thinking about. It isn't on the roadmap, and some developers already switch Angular's encapsulation off and scope components with &lt;code&gt;@scope&lt;/code&gt; by hand (&lt;a href="https://www.oidaisdes.org/blog/css-scope-angular-best-practices/" rel="noopener noreferrer"&gt;example&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7g3xu5ky2egu3lpqkhcg.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%2F7g3xu5ky2egu3lpqkhcg.png" alt="Four ways to keep styles in their place: CSS Modules and emulated encapsulation rewrite code; Shadow DOM and @scope are native" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Our take
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;New project, React or Angular:&lt;/strong&gt; native CSS is enough for most cases. Add a preprocessor if you need mixins, loops, BEM names built with &lt;code&gt;&amp;amp;__&lt;/code&gt;, breakpoints as variables in media queries, or a custom Angular Material theme.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You write nesting, the browser gets flat CSS.&lt;/strong&gt; With default settings, both Next.js and the Angular CLI (in component styles) flatten nesting at build time, and both keep the styles isolated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In Angular, you can already use native nesting in component styles,&lt;/strong&gt; because the default CLI builder flattens it before encapsulating. The exception is nesting under &lt;code&gt;::ng-deep&lt;/code&gt; (#58996). If raw nesting reaches your compiler (non-literal JIT styles, webpack dev builds, some libraries), move to 22.2.2 or later and write host states with &lt;code&gt;:host(...)&lt;/code&gt;. On the webpack builders, moving to the esbuild-based &lt;code&gt;@angular/build:application&lt;/code&gt; builder also avoids the problem, because it flattens first.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The underlying difference remains. React lets you choose the isolation mechanism (and choosing well is on you). Angular solves it for you (and adapting it to new CSS is on the framework, sometimes years late).&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%2Fwzpd76lfhrjeyxx5rhas.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%2Fwzpd76lfhrjeyxx5rhas.png" alt="React: you choose between Tailwind, CSS Modules, CSS-in-JS or global CSS. Angular: the framework provides emulated encapsulation" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>react</category>
      <category>css</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Template errors: React and Next.js vs Angular 22.2's @boundary</title>
      <dc:creator>Damian Sire (Dev)</dc:creator>
      <pubDate>Fri, 02 Oct 2026 07:25:51 +0000</pubDate>
      <link>https://dev.to/angular/template-errors-react-and-nextjs-vs-angular-222s-boundary-4big</link>
      <guid>https://dev.to/angular/template-errors-react-and-nextjs-vs-angular-222s-boundary-4big</guid>
      <description>&lt;p&gt;A dashboard with three widgets. The sales widget reads &lt;code&gt;data.points.length&lt;/code&gt;, and one day the backend returns &lt;code&gt;{ points: null }&lt;/code&gt;. On render, &lt;code&gt;null.length&lt;/code&gt; throws a &lt;code&gt;TypeError&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The question is what the user sees: the whole screen broken, or just that widget with an error message. Each framework answers differently, and the answer says a lot about how each one is designed.&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%2Fvl94uysre87ytjbppasr.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%2Fvl94uysre87ytjbppasr.png" alt="Same bug, two outcomes: without a boundary, React removes everything rendered by that root and only a TypeError is left in the console; with a boundary around the sales widget, only that widget shows its fallback while users and alerts keep working." width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  React: a class component
&lt;/h2&gt;

&lt;p&gt;If a component throws while rendering and nobody catches it, React &lt;a href="https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary" rel="noopener noreferrer"&gt;removes the UI of that root from the screen&lt;/a&gt;, which in a typical app means all of it. To prevent that, there's the &lt;strong&gt;Error Boundary&lt;/strong&gt;: a component that implements &lt;code&gt;getDerivedStateFromError&lt;/code&gt; to show a fallback and &lt;code&gt;componentDidCatch&lt;/code&gt; to log the error.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&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;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="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ErrorBoundary&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;hasError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;getDerivedStateFromError&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;hasError&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="nf"&gt;componentDidCatch&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="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;logError&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="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentStack&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasError&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;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fallback&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;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ErrorBoundary&lt;/span&gt; &lt;span class="na"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;We couldn't load sales.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Sales&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;sales&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ErrorBoundary&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details that surprise people coming from hooks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It has to be a class.&lt;/strong&gt; The React docs say it plainly: there's currently no way to write an Error Boundary as a function component. The usual way out is the &lt;code&gt;react-error-boundary&lt;/code&gt; library.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It doesn't catch everything.&lt;/strong&gt; Event handlers, async code (&lt;code&gt;setTimeout&lt;/code&gt;), server-side rendering and errors thrown by the boundary itself are left out. The exception is the &lt;code&gt;startTransition&lt;/code&gt; returned by &lt;code&gt;useTransition&lt;/code&gt;: errors thrown there do reach the boundary (the standalone &lt;code&gt;startTransition&lt;/code&gt; imported from &lt;code&gt;react&lt;/code&gt; doesn't: it goes to &lt;code&gt;reportError&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why it works this way:&lt;/strong&gt; in React, error handling lives in the component lifecycle. Hooks have no equivalent of &lt;code&gt;getDerivedStateFromError&lt;/code&gt;, so the only piece that can catch its children's errors is a class component.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next.js: one file per route segment
&lt;/h2&gt;

&lt;p&gt;Next.js builds its boundaries on top of folder-based routing. An &lt;code&gt;error.tsx&lt;/code&gt; inside a segment wraps that segment's &lt;code&gt;page&lt;/code&gt; and its children in a boundary; it doesn't cover the &lt;code&gt;layout.tsx&lt;/code&gt; of the same segment, which is handled by the parent's &lt;code&gt;error.tsx&lt;/code&gt; (&lt;a href="https://nextjs.org/docs/app/getting-started/error-handling" rel="noopener noreferrer"&gt;Next.js docs&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// error boundaries must be Client Components&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ErrorPage&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="nx"&gt;retry&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="nl"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;digest&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;retry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Something went wrong&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&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;retry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Try again&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&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;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%2Ftzvsm4e26z0fkp6wzszh.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%2Ftzvsm4e26z0fkp6wzszh.png" alt="Next.js App Router nesting: errors in app/dashboard/page.tsx are caught by app/dashboard/error.tsx, errors in app/dashboard/layout.tsx by the parent app/error.tsx, and errors in the root app/layout.tsx by global-error.tsx." width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Errors bubble up to the nearest &lt;code&gt;error.tsx&lt;/code&gt;, and &lt;code&gt;global-error.tsx&lt;/code&gt; covers the root layout. To contain errors at the component level, not just the route level, Next.js has &lt;a href="https://nextjs.org/docs/app/api-reference/functions/catchError" rel="noopener noreferrer"&gt;&lt;code&gt;catchError&lt;/code&gt;&lt;/a&gt; (stable since 16.3), which turns a function into a boundary without writing the class. Its fallback receives &lt;code&gt;retry()&lt;/code&gt;, which re-fetches and re-renders the children, so it also recovers from Server Component errors, and &lt;code&gt;reset()&lt;/code&gt;, which only clears the error state (&lt;a href="https://nextjs.org/docs/app/api-reference/functions/catchError" rel="noopener noreferrer"&gt;docs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it works this way:&lt;/strong&gt; in Next.js, the structure of the app is its folders. It makes sense for the route segment to be the natural unit of error.&lt;/p&gt;

&lt;h2&gt;
  
  
  Angular before 22.2: only a global handler
&lt;/h2&gt;

&lt;p&gt;Angular only had &lt;code&gt;ErrorHandler.handleError()&lt;/code&gt;: a central place to find out about an error and report it, but no built-in way to replace the broken part with a local fallback. As &lt;a href="https://blog.ninja-squad.com/2026/09/23/what-is-new-angular-22.2" rel="noopener noreferrer"&gt;Ninja Squad&lt;/a&gt; describes it, if a component threw while rendering, the rest of its subtree wasn't rendered and the page ended up partially blank.&lt;/p&gt;

&lt;h2&gt;
  
  
  Angular 22.2: &lt;code&gt;@boundary&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Angular 22.2, released on September 23, 2026, adds &lt;code&gt;@boundary&lt;/code&gt; in developer preview (&lt;a href="https://angular.dev/guide/templates/error-boundaries" rel="noopener noreferrer"&gt;official guide&lt;/a&gt;):&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;h1&amp;gt;&lt;/span&gt;Dashboard&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

@boundary {
  &lt;span class="nt"&gt;&amp;lt;app-sales&lt;/span&gt; &lt;span class="na"&gt;[data]=&lt;/span&gt;&lt;span class="s"&gt;"sales()"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
} @error (let err; when isNetworkError(err)) {
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Network problem.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"$reset()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Retry&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
} @error {
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;We couldn't load sales: {{ $error.message }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
}

&lt;span class="nt"&gt;&amp;lt;app-users&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;app-alerts&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;Only the sales box shows the fallback; users and alerts render normally. The &lt;code&gt;@error&lt;/code&gt; blocks are evaluated in order, and if none matches and there's no &lt;code&gt;@error&lt;/code&gt; without &lt;code&gt;when&lt;/code&gt; at the end, the error keeps going, wrapped in a &lt;code&gt;BoundaryError&lt;/code&gt; with the original in &lt;code&gt;.cause&lt;/code&gt; (&lt;a href="https://github.com/angular/angular/blob/v22.2.2/packages/core/src/render3/instructions/boundary.ts#L200" rel="noopener noreferrer"&gt;source&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg6l2dgrayp6ox9f7omj6.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%2Fg6l2dgrayp6ox9f7omj6.png" alt="Angular 22.2 @boundary flow: an error inside the block (constructor, lifecycle hook, template binding or effect) first notifies the global ErrorHandler, through onViewError or else handleError; then the @error blocks are checked in order. The first match renders its fallback; if none matches, the error keeps going wrapped in a BoundaryError with the original in .cause." width="800" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before the fallback, the error reaches your &lt;code&gt;ErrorHandler&lt;/code&gt;: &lt;code&gt;onViewError()&lt;/code&gt; if you implement it, &lt;code&gt;handleError()&lt;/code&gt; otherwise (&lt;a href="https://github.com/angular/angular/blob/v22.2.2/packages/core/src/render3/instructions/boundary.ts#L142-L146" rel="noopener noreferrer"&gt;source&lt;/a&gt;). According to the &lt;a href="https://github.com/angular/angular/blob/v22.2.2/packages/core/test/render3/error_boundary_spec.ts" rel="noopener noreferrer"&gt;framework's tests&lt;/a&gt;, the block catches errors thrown by the constructors of the components inside it, their lifecycle hooks, template bindings and their &lt;code&gt;effect()&lt;/code&gt;s.&lt;/p&gt;

&lt;p&gt;It's not just a template block: three pieces ship together.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Piece&lt;/th&gt;
&lt;th&gt;What it's for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;@boundary&lt;/code&gt; / &lt;code&gt;@error&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The declarative version, in the template&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;onError&lt;/code&gt; in &lt;code&gt;createComponent&lt;/code&gt; and &lt;code&gt;createEmbeddedView&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The equivalent for views created in code: you get the error in a callback and build the fallback yourself. It doesn't cover the creation phase (the constructor, or creating the view in &lt;code&gt;createEmbeddedView&lt;/code&gt;): that throws synchronously from the same call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ErrorHandler.onViewError()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Optional hook that receives every error a boundary intercepts, so you can send it to monitoring. If you implement it, those errors no longer go through &lt;code&gt;handleError()&lt;/code&gt;; if you don't, they go there&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;viewContainerRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Widget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;:&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="nx"&gt;details&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;report&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it works this way:&lt;/strong&gt; in Angular, the template compiler already owns control flow (&lt;code&gt;@if&lt;/code&gt;, &lt;code&gt;@for&lt;/code&gt;, &lt;code&gt;@defer&lt;/code&gt;), and a boundary is one more block, not a component. The programmatic API completes the picture: a component you insert into a &lt;code&gt;ViewContainerRef&lt;/code&gt; located inside a &lt;code&gt;@boundary&lt;/code&gt; is already covered (a framework test checks exactly that), but not every dynamic view has a block around it, and that's what &lt;code&gt;onError&lt;/code&gt; is for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where to put them:&lt;/strong&gt; Angular's guide doesn't say, but &lt;a href="https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary" rel="noopener noreferrer"&gt;react.dev's&lt;/a&gt; criterion applies just as well. You don't need to wrap every component: a boundary goes where it makes sense to show an error message, like a widget or a third-party component. And errors you can anticipate, like a failing fetch, Angular &lt;a href="https://angular.dev/best-practices/error-handling" rel="noopener noreferrer"&gt;recommends&lt;/a&gt; handling where they happen, with &lt;code&gt;try/catch&lt;/code&gt;. The &lt;code&gt;ErrorHandler&lt;/code&gt; is for reporting the unexpected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we prefer Angular's approach
&lt;/h2&gt;

&lt;p&gt;This section is opinion:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;You don't need a class or a library.&lt;/strong&gt; It's template syntax, just like &lt;code&gt;@if&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You discriminate by error type in the template.&lt;/strong&gt; Several &lt;code&gt;@error (when ...)&lt;/code&gt; blocks with a catch-all at the end. In React, that logic goes inside the fallback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Declarative, programmatic and global are part of the same feature.&lt;/strong&gt; No need to assemble the puzzle from loose pieces.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To be fair:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@boundary&lt;/code&gt; is in &lt;strong&gt;developer preview&lt;/strong&gt;: per the &lt;a href="https://angular.dev/reference/releases#developer-preview" rel="noopener noreferrer"&gt;versioning policy&lt;/a&gt;, it can change even in a patch release. React's boundaries have years in production.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use 22.2.2 or later.&lt;/strong&gt; In 22.2.0 and 22.2.1, writing to a signal while a boundary handled an error threw &lt;code&gt;NG0600&lt;/code&gt; (&lt;a href="https://github.com/angular/angular/issues/71199" rel="noopener noreferrer"&gt;#71199&lt;/a&gt;); 22.2.2 fixed it (&lt;a href="https://github.com/angular/angular/pull/71204" rel="noopener noreferrer"&gt;PR #71204&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Next.js's &lt;code&gt;catchError&lt;/code&gt; already solves the "I don't want to write a class" problem. And React also has a global callback: &lt;a href="https://react.dev/reference/react-dom/client/createRoot" rel="noopener noreferrer"&gt;&lt;code&gt;createRoot&lt;/code&gt;&lt;/a&gt; accepts &lt;code&gt;onCaughtError&lt;/code&gt;, which receives the errors a boundary catches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It doesn't catch event handlers either.&lt;/strong&gt; In Angular 22.2's code, an error inside a listener goes straight to the global &lt;code&gt;ErrorHandler&lt;/code&gt;, without going through any boundary (&lt;a href="https://github.com/angular/angular/blob/v22.2.0/packages/core/src/render3/view/listeners.ts" rel="noopener noreferrer"&gt;&lt;code&gt;listeners.ts&lt;/code&gt;&lt;/a&gt;). Same limit as React.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch out for content projection.&lt;/strong&gt; Wrapping &lt;code&gt;&amp;lt;ng-content&amp;gt;&lt;/code&gt; in a &lt;code&gt;@boundary&lt;/code&gt; doesn't catch errors from projected content: that content belongs to the view that declares it. You have to wrap the container component from the parent template (&lt;a href="https://angular.dev/guide/templates/error-boundaries#content-projection" rel="noopener noreferrer"&gt;guide&lt;/a&gt;). In React, a boundary that wraps &lt;code&gt;children&lt;/code&gt; does catch their errors.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;React:&lt;/strong&gt; a class component, or &lt;code&gt;react-error-boundary&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next.js:&lt;/strong&gt; &lt;code&gt;error.tsx&lt;/code&gt; per route and &lt;code&gt;catchError&lt;/code&gt; per component.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Angular 22.2:&lt;/strong&gt; a template block, with a programmatic equivalent and a global hook.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three different designs for the same problem, each one stemming from how its framework is built.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>react</category>
      <category>nextjs</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Evolution in Form Validators: Goodbye customError, Hello Plain Objects</title>
      <dc:creator>Damian Sire (Dev)</dc:creator>
      <pubDate>Thu, 30 Oct 2025 21:42:24 +0000</pubDate>
      <link>https://dev.to/damiansire/evolution-in-form-validators-goodbye-customerror-hello-plain-objects-4c35</link>
      <guid>https://dev.to/damiansire/evolution-in-form-validators-goodbye-customerror-hello-plain-objects-4c35</guid>
      <description>&lt;p&gt;Form management in Angular, especially with the arrival of &lt;strong&gt;Signal-based forms&lt;/strong&gt;, is constantly evolving to improve ergonomics and the overall &lt;strong&gt;developer experience (DX)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A subtle but incredibly welcome change is the simplification in how we define &lt;strong&gt;custom validation errors&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
What previously required a utility function like &lt;code&gt;customError&lt;/code&gt; to wrap our error object, now allows us to return a &lt;strong&gt;plain JavaScript object (POJO)&lt;/strong&gt; directly.&lt;/p&gt;

&lt;p&gt;This change reduces boilerplate, simplifies the API, and makes creating validators much more intuitive.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Note:&lt;/strong&gt; The &lt;code&gt;form()&lt;/code&gt; and &lt;code&gt;validate()&lt;/code&gt; APIs discussed here are part of the new Signal-based forms system.&lt;br&gt;&lt;br&gt;
You can explore the implementation details in the Angular PR:&lt;br&gt;&lt;br&gt;
👉 &lt;a href="https://github.com/angular/angular/pull/64339/files" rel="noopener noreferrer"&gt;PR #64339 — Simplify Custom Errors&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  🔑 The Key Change: Before and After
&lt;/h2&gt;

&lt;p&gt;Let's look at a direct comparison — the core of this improvement.&lt;/p&gt;
&lt;h3&gt;
  
  
  Before 👎
&lt;/h3&gt;

&lt;p&gt;Previously, to indicate a custom validation error, we often needed to import and use a utility function to &lt;em&gt;wrap&lt;/em&gt; our error and ensure it was correctly typed and recognized by the forms system.&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;customError&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;some-forms-library&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;cat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;meow&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;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;form&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// We needed the customError wrapper&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;p&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;meow&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;return&lt;/span&gt; &lt;span class="nf"&gt;customError&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;i am a custom error&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;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  🧩 Simplified Validator API in Angular Signal Forms
&lt;/h1&gt;

&lt;p&gt;With the new simplified API, the validation engine is smart enough to understand a simple object as an error response.&lt;br&gt;&lt;br&gt;
If the validator function returns an object, it's an error.&lt;br&gt;&lt;br&gt;
If it returns &lt;code&gt;null&lt;/code&gt; or &lt;code&gt;undefined&lt;/code&gt;, it's valid.&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;cat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;meow&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;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;form&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// We simply return the error object&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;p&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;meow&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;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;iAmACustomError&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="c1"&gt;// Much cleaner!&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Valid&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;
  
  
  💡 Why Does This Change Matter?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🚫 Less Boilerplate
&lt;/h3&gt;

&lt;p&gt;You no longer need to import &lt;code&gt;customError&lt;/code&gt; in every validator file.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧠 More Intuitive API
&lt;/h3&gt;

&lt;p&gt;The flow feels natural — &lt;em&gt;"Is there an error? If so, return the error object."&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🧪 Easier Testing
&lt;/h3&gt;

&lt;p&gt;Mocking or stubbing a validator is trivial when it just returns an object.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚙️ Consistency
&lt;/h3&gt;

&lt;p&gt;It aligns with how synchronous validators work in traditional reactive forms (&lt;code&gt;Validators.required&lt;/code&gt; returns &lt;code&gt;{ required: true }&lt;/code&gt;).&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;While this might seem like a small change, it’s a perfect example of Angular’s ongoing effort to make APIs simpler, more consistent, and more developer-friendly.&lt;/p&gt;

&lt;p&gt;Cleaner validators mean fewer imports, less friction, and a smoother DX overall — especially as Signal-based forms continue to mature.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>webdev</category>
      <category>signals</category>
      <category>forms</category>
    </item>
    <item>
      <title>File Naming Conventions: Keep Your Project Clean and Readable ✨</title>
      <dc:creator>Damian Sire (Dev)</dc:creator>
      <pubDate>Sat, 27 Sep 2025 19:17:16 +0000</pubDate>
      <link>https://dev.to/damiansire/file-naming-conventions-keep-your-project-clean-and-readable-1plk</link>
      <guid>https://dev.to/damiansire/file-naming-conventions-keep-your-project-clean-and-readable-1plk</guid>
      <description>&lt;p&gt;Ever jumped into an Angular project and felt lost in a sea of confusing file names? 🌊 Consistent naming isn't just about being tidy; it's a cornerstone of professional, maintainable, and team-friendly code. When your file structure is predictable, you spend less time searching and more time coding.&lt;/p&gt;

&lt;p&gt;Let's break down some simple but powerful file naming conventions from the official Angular style guide that will make your life (and your teammates' lives) much easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate Words with Hyphens (Kebab-Case)
&lt;/h2&gt;

&lt;p&gt;This is the foundational rule. Always separate words in your file names with hyphens. This convention, often called &lt;strong&gt;kebab-case&lt;/strong&gt;, is a standard in web development and makes file names easy to read.&lt;/p&gt;

&lt;p&gt;For example, if you have a component class named &lt;code&gt;UserProfileComponent&lt;/code&gt;, its file name should be:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;user-profile.component.ts&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is much clearer and more conventional than &lt;code&gt;userprofile.ts&lt;/code&gt; or &lt;code&gt;user_profile.ts&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Name Test Files with &lt;code&gt;.spec&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Your unit tests are first-class citizens in your project and should be easy to find. The standard is to use the exact same name as the file you are testing, but with a &lt;code&gt;.spec.ts&lt;/code&gt; suffix.&lt;/p&gt;

&lt;p&gt;This pattern is instantly recognizable and is the default configuration for testing frameworks like Karma and Jest in the Angular ecosystem.&lt;/p&gt;

&lt;p&gt;For our &lt;code&gt;user-profile.component.ts&lt;/code&gt; file, the corresponding test file would be:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;user-profile.component.spec.ts&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Match the File Name to Its Contents
&lt;/h2&gt;

&lt;p&gt;A file's name should be a clear signpost to what's inside. When a file contains a primary identifier, like a TypeScript &lt;code&gt;class&lt;/code&gt;, the file name should be the kebab-case version of that identifier.&lt;/p&gt;

&lt;p&gt;For instance, a file containing the class &lt;code&gt;OrderHistoryService&lt;/code&gt; should be named:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;order-history.service.ts&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A quick tip:&lt;/strong&gt; Avoid overly generic file names like &lt;code&gt;helpers.ts&lt;/code&gt;, &lt;code&gt;utils.ts&lt;/code&gt;, or &lt;code&gt;common.ts&lt;/code&gt;. While tempting, these files often become a dumping ground for unrelated code, making them hard to maintain. If you have several unrelated helper functions, consider breaking them into separate, more specific files (e.g., &lt;code&gt;string-formatters.ts&lt;/code&gt;, &lt;code&gt;validation-rules.ts&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Group Component Files by Name
&lt;/h2&gt;

&lt;p&gt;A typical Angular component is made up of several files: the TypeScript logic, the HTML template, and the CSS/SCSS styles. To keep these related files neatly grouped and easy to locate, they should all share the same base name.&lt;/p&gt;

&lt;p&gt;Following our &lt;code&gt;UserProfileComponent&lt;/code&gt; example, its complete set of files would look like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;user-profile.component.ts&lt;/code&gt; (the component class)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;user-profile.component.html&lt;/code&gt; (the template)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;user-profile.component.scss&lt;/code&gt; (the styles)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes it incredibly simple to find all the pieces of a single component.&lt;/p&gt;

&lt;p&gt;What if a component has multiple style files? Just append a descriptive word. For example, you might have styles specific to different themes or sections:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;user-profile-light.scss&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;user-profile-dark.scss&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Does This Matter? 🤔
&lt;/h2&gt;

&lt;p&gt;Following these conventions brings huge benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Readability:&lt;/strong&gt; Your project becomes instantly easier to scan and understand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Findability:&lt;/strong&gt; You can find any file you need quickly, without guesswork.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistency:&lt;/strong&gt; It creates a professional and predictable structure, which is crucial when working on a team.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tooling:&lt;/strong&gt; Many CLI commands and IDE extensions rely on these conventions to generate, find, and link code correctly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By adopting these simple naming rules, you're not just organizing files—you're building a more professional, scalable, and enjoyable Angular application. &lt;/p&gt;

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

</description>
      <category>angular</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Is ViewChild and Lifecycle Hooks in Angular Really Code Smells? No.</title>
      <dc:creator>Damian Sire (Dev)</dc:creator>
      <pubDate>Fri, 18 Apr 2025 13:21:13 +0000</pubDate>
      <link>https://dev.to/damiansire/is-viewchild-and-lifecycle-hooks-in-angular-really-code-smells-no-29me</link>
      <guid>https://dev.to/damiansire/is-viewchild-and-lifecycle-hooks-in-angular-really-code-smells-no-29me</guid>
      <description>&lt;h2&gt;
  
  
  Demystifying &lt;code&gt;ViewChild&lt;/code&gt; in Angular: Useful Tool or Code Smell?
&lt;/h2&gt;

&lt;p&gt;In recent discussions within the Angular development community, the use of &lt;code&gt;ViewChild&lt;/code&gt; has been labeled by some as a "code smell." Supporters of this view argue it's often unnecessary, even comparing it to the redundancy of certain lifecycle hooks like &lt;code&gt;ngAfterViewInit&lt;/code&gt;, claiming that setter functions on the &lt;code&gt;@ViewChild&lt;/code&gt; property are enough.&lt;/p&gt;

&lt;p&gt;However, this perspective oversimplifies both the utility of &lt;code&gt;ViewChild&lt;/code&gt; and the core purpose of Angular lifecycle hooks. &lt;code&gt;ViewChild&lt;/code&gt; is a decorator provided by Angular to allow a parent component to access child component instances, directives, or DOM elements within its template. Like any powerful tool, &lt;strong&gt;misuse&lt;/strong&gt; can lead to design issues, and it's this misuse that might be a code smell—not the tool itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Can &lt;code&gt;ViewChild&lt;/code&gt; Be Problematic?
&lt;/h2&gt;

&lt;p&gt;Labeling &lt;code&gt;ViewChild&lt;/code&gt; as &lt;em&gt;always&lt;/em&gt; a code smell is an overgeneralization. However, some usage patterns should raise concerns:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Excessive Parent-to-Child Communication:&lt;/strong&gt; If you're constantly using &lt;code&gt;ViewChild&lt;/code&gt; to call methods or set properties on child components, you're likely creating tight coupling. Instead, consider using &lt;code&gt;@Input()&lt;/code&gt; to pass data and &lt;code&gt;@Output()&lt;/code&gt; with &lt;code&gt;EventEmitter&lt;/code&gt; to notify the parent. Shared services are another excellent alternative.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Direct DOM Manipulation:&lt;/strong&gt; Using &lt;code&gt;ViewChild&lt;/code&gt; to access an &lt;code&gt;ElementRef&lt;/code&gt; and manipulate the DOM goes against Angular's abstractions. While sometimes necessary (e.g., integrating third-party libraries), Angular often provides idiomatic alternatives like property binding, attribute binding, or custom directives.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In short, if you're using &lt;code&gt;ViewChild&lt;/code&gt; for fine-grained control or DOM manipulation without a solid reason, it's time to reconsider. Explore &lt;code&gt;@Input()&lt;/code&gt;, &lt;code&gt;@Output()&lt;/code&gt;, or services instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setter vs. &lt;code&gt;ngAfterViewInit&lt;/code&gt;: Not Interchangeable
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;ViewChild&lt;/code&gt; setter is &lt;strong&gt;not&lt;/strong&gt; a replacement for &lt;code&gt;ngAfterViewInit&lt;/code&gt;. Setters run when that specific reference becomes available. In contrast, &lt;code&gt;ngAfterViewInit&lt;/code&gt; runs &lt;strong&gt;after&lt;/strong&gt; the component's full view and all child views are initialized.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Matters:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multiple Dependencies:&lt;/strong&gt; If your logic depends on several &lt;code&gt;ViewChild&lt;/code&gt; or &lt;code&gt;ViewChildren&lt;/code&gt; references, &lt;code&gt;ngAfterViewInit&lt;/code&gt; ensures they’re all ready.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full View State Dependency:&lt;/strong&gt; Sometimes your code needs the final rendered view (e.g., dimensions or position). &lt;code&gt;ngAfterViewInit&lt;/code&gt; is the right hook for this.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Valid Use Cases for &lt;code&gt;ViewChild&lt;/code&gt; and &lt;code&gt;ngAfterViewInit&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Here are legitimate scenarios where &lt;code&gt;ViewChild&lt;/code&gt; and &lt;code&gt;ngAfterViewInit&lt;/code&gt; are appropriate:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Integrating External JavaScript Libraries
&lt;/h3&gt;

&lt;p&gt;Chart.js or Swiper might require a direct DOM reference. Use &lt;code&gt;ViewChild&lt;/code&gt; to grab it, and &lt;code&gt;ngAfterViewInit&lt;/code&gt; to safely initialize.&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;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;AfterViewInit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ViewChild&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ElementRef&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;import&lt;/span&gt; &lt;span class="nx"&gt;Chart&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;chart.js/auto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// example with Chart.js&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-chart&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;canvas #myCanvas&amp;gt;&amp;lt;/canvas&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChartComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;AfterViewInit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;ViewChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;myCanvas&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;myCanvas&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ElementRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLCanvasElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;chart&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="nf"&gt;ngAfterViewInit&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&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;myCanvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nativeElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Chart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bar&lt;/span&gt;&lt;span class="dl"&gt;'&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="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* data */&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* options */&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;else&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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Could not get the 2D context from the canvas.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the previous case, there is no doubt that it is a good path. Now come cases where consensus is not entirely clear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Possibles valid Use Case for ViewChild and ngAfterViewInit (Although frankly, I would use a directive)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Post-Render UI Logic (Also check example 5)
&lt;/h3&gt;

&lt;p&gt;Tooltip positioning, layout adjustments, and viewport visibility checks require post-render DOM access. &lt;code&gt;ngAfterViewInit&lt;/code&gt; ensures proper timing.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Directives Affecting Multiple Children
&lt;/h3&gt;

&lt;p&gt;For example, a directive that styles table rows via &lt;code&gt;@for&lt;/code&gt;. You need &lt;code&gt;ngAfterViewInit&lt;/code&gt; or &lt;code&gt;ngAfterContentInit&lt;/code&gt; and &lt;code&gt;@ViewChildren&lt;/code&gt; or &lt;code&gt;@ContentChildren&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Dynamic Dimension Calculations and Positioning
&lt;/h3&gt;

&lt;p&gt;You often encounter scenarios where you must perform tasks based on the actual size and position of elements only after the browser has rendered them. Common examples include calculating one element's height to adjust another's layout, checking if an element is currently visible within the viewport, dynamically positioning tooltips or popovers relative to their trigger elements, or implementing complex, responsive layouts such as a masonry grid. These operations depend critically on knowing the final computed dimensions and placement.&lt;/p&gt;

&lt;p&gt;The reason these calculations require the fully rendered view is fundamental to how web browsers work. The true dimensions (obtainable via properties like offsetWidth, offsetHeight, or methods like getBoundingClientRect()) and exact positions of elements are only finalized once the browser has processed the HTML structure, applied all relevant CSS styles, and completed the layout rendering pass. Attempting to measure elements before this stage often yields inaccurate or incomplete data.&lt;/p&gt;

&lt;p&gt;Within an Angular application, the ngAfterViewInit lifecycle hook is specifically designed for these initial post-render calculations, firing after the component's view and child views are fully initialized. If dimensions or positions need to be recalculated later due to dynamic changes (like data updates or window resizing), you might use ngAfterViewChecked (being careful about performance) or, preferably, leverage more modern and efficient browser APIs like ResizeObserver to react specifically to element size modifications.&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;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;AfterViewInit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ViewChild&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ElementRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Renderer2&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="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-tooltip-host&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;button #trigger (mouseenter)="showTooltip()" (mouseleave)="hideTooltip()"&amp;gt;Hover me&amp;lt;/button&amp;gt;
    &amp;lt;div #tooltip class="tooltip" [style.visibility]="visible ? 'visible' : 'hidden'"&amp;gt;
      Tooltip content!
    &amp;lt;/div&amp;gt;
  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`.tooltip { position: absolute; /* ... other styles ... */ }`&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;TooltipHostComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;AfterViewInit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;ViewChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;trigger&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;trigger&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ElementRef&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;ViewChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tooltip&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;tooltip&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ElementRef&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&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;renderer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Renderer2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="nf"&gt;ngAfterViewInit&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;// You could do an initial calculation here if necessary,&lt;/span&gt;
    &lt;span class="c1"&gt;// but the main logic is triggered by events.&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;


  &lt;span class="nf"&gt;showTooltip&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;visible&lt;/span&gt; &lt;span class="o"&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;// Short delay to ensure the tooltip is visible BEFORE measuring&lt;/span&gt;
    &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;positionTooltip&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Call renamed function&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;hideTooltip&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;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;positionTooltip&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Ensure it's visible to measure&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;triggerRect&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;trigger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nativeElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBoundingClientRect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// Needs to be rendered and visible!&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tooltipRect&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;tooltip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nativeElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBoundingClientRect&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;tooltipElement&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;tooltip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nativeElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Example: Position above the trigger&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;triggerRect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;tooltipRect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 5px of space&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;triggerRect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;triggerRect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tooltipRect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Simple logic to prevent it from going off the top (could be more complex)&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Position below if it doesn't fit above&lt;/span&gt;
        &lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;triggerRect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bottom&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Adjust for scroll&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;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tooltipElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;top&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Adjust for scroll&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;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tooltipElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollX&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px`&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;
  
  
  Final Conclusion
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ViewChild&lt;/code&gt; isn't inherently bad or a code smell. It’s a specific tool for specific jobs in Angular. Rejecting it entirely—or thinking a setter replaces it—ignores valid use cases and lifecycle guarantees.&lt;/p&gt;

&lt;p&gt;The key is understanding its purpose and recognizing signs of design issues, like tight coupling or unjustified DOM manipulation.&lt;/p&gt;

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