<?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: Yasin Demir</title>
    <description>The latest articles on DEV Community by Yasin Demir (@ysndmr).</description>
    <link>https://dev.to/ysndmr</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%2F4019087%2F87d9f628-a325-4ea7-afaa-8b5e61a40555.jpg</url>
      <title>DEV Community: Yasin Demir</title>
      <link>https://dev.to/ysndmr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ysndmr"/>
    <language>en</language>
    <item>
      <title>Notes I Kept While Moving a Project to Angular 21</title>
      <dc:creator>Yasin Demir</dc:creator>
      <pubDate>Wed, 12 Aug 2026 21:27:36 +0000</pubDate>
      <link>https://dev.to/ysndmr/notes-i-kept-while-moving-a-project-to-angular-21-f9p</link>
      <guid>https://dev.to/ysndmr/notes-i-kept-while-moving-a-project-to-angular-21-f9p</guid>
      <description>&lt;h1&gt;
  
  
  Notes I Kept While Moving a Project to Angular 21
&lt;/h1&gt;

&lt;p&gt;Last month I decided to move a side project to Angular 21. My intention was mundane — bump the version, fix whatever broke, move on. Two weeks later I was working in a framework that shared a name with the one I knew but felt like a different tool entirely. This isn't a changelog. It's the collection of "wait, that's different" moments I wrote down as I hit them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I noticed the day I ripped out Zone.js
&lt;/h2&gt;

&lt;p&gt;When I first shipped &lt;code&gt;provideZonelessChangeDetection()&lt;/code&gt; to production, I expected a small bump on a performance graph. What I got was something else: the end of a question I used to ask constantly while debugging — "why did this component just re-render?" With Zone.js in the picture, that question could turn into a small investigation. A &lt;code&gt;setTimeout&lt;/code&gt; somewhere, a third-party library's event listener somewhere else, all of it triggering change detection, and you can't tell what triggered what just by staring at a template.&lt;/p&gt;

&lt;p&gt;Without zones, a change is no longer a mystery event — it's traceable. A signal changed, everything depending on it updated, that's the whole story. At first this clarity felt almost uncomfortable, because it comes with a responsibility I wasn't used to: your state actually has to live in a signal. You can't assign to a component property and hope change detection sorts it out eventually. But that responsibility turned out to be a gift — the code now explains itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;resource()&lt;/code&gt; didn't just replace RxJS, it killed a whole category of state bugs
&lt;/h2&gt;

&lt;p&gt;On a project detail page I was fetching data based on a route parameter. The old way looked like: a subject, a &lt;code&gt;switchMap&lt;/code&gt;, &lt;code&gt;takeUntilDestroyed&lt;/code&gt;, a separate &lt;code&gt;loading&lt;/code&gt; signal, a separate &lt;code&gt;error&lt;/code&gt; signal, and the constant chore of keeping them all in sync by hand. It always felt like hand-rolling a tiny state machine.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;resource()&lt;/code&gt;, the same job became:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;project&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;params&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="na"&gt;slug&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;slug&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentful&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getProjectBySlug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;project.value()&lt;/code&gt;, &lt;code&gt;project.isLoading()&lt;/code&gt;, &lt;code&gt;project.error()&lt;/code&gt; — all three are just there, synchronous, always consistent with each other. When the route changes, &lt;code&gt;params&lt;/code&gt; reruns, a new fetch fires, and the previous in-flight request gets discarded. Every time I hand-wrote this before, I'd leave a bug somewhere — usually a race condition where a stale response arrived late and clobbered newer state. &lt;code&gt;resource()&lt;/code&gt; gets this right by default, and I've simply stopped hunting for that bug.&lt;/p&gt;

&lt;p&gt;This isn't RxJS being thrown out — it's still on the table anywhere real composition is needed. But for "just wire an HTTP call to component state," I don't reach for it anymore, and that difference mattered more than I expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Changing the control flow syntax wasn't cosmetic — the template started behaving like TypeScript
&lt;/h2&gt;

&lt;p&gt;When I switched to &lt;code&gt;@if&lt;/code&gt; / &lt;code&gt;@for&lt;/code&gt;, I expected a cosmetic win — a shorter way to write what &lt;code&gt;*ngIf&lt;/code&gt; used to do. The real difference showed up in the editor. Write &lt;code&gt;@if (user(); as u)&lt;/code&gt; and the IDE actually knows the type of &lt;code&gt;u&lt;/code&gt; — none of the occasional ambiguity that &lt;code&gt;*ngIf&lt;/code&gt; + &lt;code&gt;as&lt;/code&gt; could produce. The mandatory &lt;code&gt;track&lt;/code&gt; in &lt;code&gt;@for&lt;/code&gt; felt like busywork at first, until I remembered how many times in this codebase I'd tracked by index and then watched the DOM misbehave the moment a list got reordered. The compiler refusing to let me forget &lt;code&gt;track&lt;/code&gt; quietly closed a category of bug I'd shipped more than once.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the build tooling change actually felt like: the wait disappearing
&lt;/h2&gt;

&lt;p&gt;After moving to the Vite/esbuild-based dev server, the thing I noticed most wasn't really a "feature" — it was the silence after hitting save. There used to be a small pause there, one I'd gotten so used to I stopped noticing it. Now that gap is gone, and once you consider that saving a file happens dozens of times a day, what disappeared wasn't just accumulated seconds — it was accumulated &lt;em&gt;attention loss&lt;/em&gt;. The flow doesn't break.&lt;/p&gt;

&lt;h2&gt;
  
  
  SSR/hydration: no longer "hopefully it holds," now it just holds
&lt;/h2&gt;

&lt;p&gt;This project already used server-side rendering, and hydration mismatches used to feel like a game of chance — a silent warning in the console, sometimes there, sometimes not, sometimes only in production. With Angular 21's hydration improvements, that uncertainty shrank considerably. The most concrete win: the code that skips server-rendering my WebGL background (the &lt;code&gt;gl-bg&lt;/code&gt; component) and hands it off to the client needs far fewer special-case checks now, because the framework understands "this can't hydrate, skip it" better than my own workarounds did.&lt;/p&gt;

&lt;h2&gt;
  
  
  The overall feeling: the framework stands behind you now, not in front of you
&lt;/h2&gt;

&lt;p&gt;Working with older Angular, I sometimes felt like I was writing code &lt;em&gt;against&lt;/em&gt; the framework's assumptions — outsmarting the change detection cycle, preventing unnecessary re-renders, adding defensive code to make sure an RxJS subscription wasn't leaking. In Angular 21, most of that defensive layer is gone. Signals, zoneless, and &lt;code&gt;resource()&lt;/code&gt; together don't just mean "less code" — they mean "less code to worry about." That sounds like a small difference until you've maintained a codebase for months and realized the real source of fatigue was never line count. It was that background worry.&lt;/p&gt;

&lt;p&gt;If there are three things I'd genuinely tell you to try: use &lt;code&gt;resource()&lt;/code&gt; in a real route-level data-fetching scenario (a toy todo list won't show you the difference), turn on zoneless in an existing project and leave one &lt;code&gt;@HostListener&lt;/code&gt;-driven piece of state as a plain property instead of a signal, then watch what breaks (an instructive kind of pain), and misuse &lt;code&gt;@for&lt;/code&gt; + &lt;code&gt;track&lt;/code&gt; on purpose once, just to see firsthand when the DOM "remembers" and when it forgets. All three read as unremarkable in the docs. They don't read that way once you've actually done them.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://ysndmr.com/writings/angular-21-notes-i-kept?utm_source=devto&amp;amp;utm_medium=cross-post&amp;amp;utm_campaign=writings" rel="noopener noreferrer"&gt;ysndmr.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>angular21</category>
      <category>signals</category>
      <category>zoneless</category>
    </item>
    <item>
      <title>Angular 21'e Taşınırken Defterime Düştüğüm Notlar</title>
      <dc:creator>Yasin Demir</dc:creator>
      <pubDate>Wed, 12 Aug 2026 21:26:23 +0000</pubDate>
      <link>https://dev.to/ysndmr/angular-21e-tasinirken-defterime-dustugum-notlar-48o9</link>
      <guid>https://dev.to/ysndmr/angular-21e-tasinirken-defterime-dustugum-notlar-48o9</guid>
      <description>&lt;h1&gt;
  
  
  Angular 21'e taşınırken defterime düştüğüm notlar
&lt;/h1&gt;

&lt;p&gt;Geçen ay bir yan projeyi Angular 21'e taşımaya karar verdiğimde niyetim sadece "sürüm numarasını güncelleyeyim" gibi bir şeydi. İki hafta sonra elimde farklı bir framework vardı — aynı isim, çok farklı bir his. Bu yazı bir changelog değil. Kod yazarken tuttuğum, "vay be bu değişti" dediğim anların derlemesi.&lt;/p&gt;

&lt;h2&gt;
  
  
  Zone.js'i kaldırdığım gün fark ettiğim şey
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;provideZonelessChangeDetection()&lt;/code&gt;'ı ilk kez production'a aldığımda beklediğim şey performans grafiğinde küçük bir iyileşmeydi. Olan şey bambaşkaydı: hata ayıklarken artık "neden bu component tekrar render oldu" sorusuna cevap veremediğim anlar bitti. Zone.js varken bu soru bazen bir dedektiflik işine dönüşürdü — bir yerde bir &lt;code&gt;setTimeout&lt;/code&gt;, bir yerde bir üçüncü parti kütüphanenin event listener'ı, hepsi CD tetikliyor ve sen neyin neyi tetiklediğini template'e bakarak asla anlayamıyorsun.&lt;/p&gt;

&lt;p&gt;Zoneless'ta değişiklik artık bir mistik olay değil, bir iz sürülebilir olay. Bir &lt;code&gt;signal&lt;/code&gt; değişti, bağımlı olan her yer güncellendi, hepsi bu. İlk başta bu netlik biraz ürkütücü geldi çünkü alışkın olmadığım bir sorumluluk getiriyor: state'ini gerçekten signal'da tutman lazım, "component property'sine atayıp CD'nin bir şekilde hallet"meyi bekleyemezsin. Ama bu sorumluluk aslında bir hediye — kodun artık kendini anlatıyor.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;resource()&lt;/code&gt; beni RxJS'ten değil, "ne zaman loading true olsun" sorusundan kurtardı
&lt;/h2&gt;

&lt;p&gt;Bir proje detay sayfasında route parametresine göre veri çekiyordum. Eskiden bu iş şuna benzerdi: bir subject, bir &lt;code&gt;switchMap&lt;/code&gt;, &lt;code&gt;takeUntilDestroyed&lt;/code&gt;, ayrı bir &lt;code&gt;loading&lt;/code&gt; signal'ı, ayrı bir &lt;code&gt;error&lt;/code&gt; signal'ı, hepsini elle senkron tutma derdi. Küçük bir state machine'i elle yazmış gibi hissederdim.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;resource()&lt;/code&gt; ile aynı işi şöyle kurdum:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;project&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;params&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="na"&gt;slug&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;slug&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentful&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getProjectBySlug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;project.value()&lt;/code&gt;, &lt;code&gt;project.isLoading()&lt;/code&gt;, &lt;code&gt;project.error()&lt;/code&gt; — üçü de zaten orada, senkron, birbirleriyle tutarlı. Route değişince &lt;code&gt;params&lt;/code&gt; fonksiyonu yeniden çalışıyor, yeni fetch tetikleniyor, eski isteğin sonucu iptal ediliyor. Bunu daha önce elle yazdığım her seferinde bir yerinde bug bırakırdım — genelde eski isteğin geç dönüp yeni state'in üstüne yazması şeklinde bir race condition. &lt;code&gt;resource()&lt;/code&gt; bunu default olarak doğru yapıyor ve ben artık bu bug'ı aramıyorum.&lt;/p&gt;

&lt;p&gt;RxJS'i attığım için değil, bu — RxJS hâlâ gerçek composition gerektiren yerlerde masada. Ama "sadece bir HTTP isteğini component state'ine bağlamak" için artık ona ihtiyacım yok, ve bu fark hissettiğimden daha büyük çıktı.&lt;/p&gt;

&lt;h2&gt;
  
  
  Control flow'u değiştirmek "sözdizimi güzelleşti" değil, "template artık TypeScript gibi davranıyor" demek
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@if&lt;/code&gt; / &lt;code&gt;@for&lt;/code&gt;'a geçtiğimde beklentim kozmetikti — &lt;code&gt;*ngIf&lt;/code&gt; yerine daha kısa bir şey yazacaktım. Asıl fark editördeydi: &lt;code&gt;@if (user(); as u)&lt;/code&gt; yazdığımda IDE artık &lt;code&gt;u&lt;/code&gt;'nun tipini gerçekten biliyor, &lt;code&gt;*ngIf&lt;/code&gt; + &lt;code&gt;as&lt;/code&gt; kombinasyonunun bazen kafa karıştırdığı belirsizlikler yok. &lt;code&gt;@for&lt;/code&gt; içindeki &lt;code&gt;track&lt;/code&gt; zorunluluğu ilk başta bir angarya gibi geldi, sonra fark ettim ki bu proje boyunca yanlışlıkla index'e track ettiğim, sonra liste yeniden sıralanınca DOM'un tuhaf davrandığı kaç yer olduğunu unutmuşum. Derleyicinin "track'i unutma" demesi aslında geçmişte kendime defalarca açtığım bir bug kategorisini kapatmış.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build tarafında hissedilen şey: bekleme süresinin kaybolması
&lt;/h2&gt;

&lt;p&gt;Vite/esbuild tabanlı dev server'a geçtikten sonra en çok fark ettiğim şey aslında bir "özellik" bile değildi — save tuşuna bastıktan sonraki sessizlikti. Eskiden orada küçük bir bekleme vardı, o kadar alışmıştım ki fark etmiyordum bile. Şimdi o boşluk yok, ve bunun günde onlarca kez tekrarlanan bir eylem olduğunu düşününce, biriken zamandan çok, biriken &lt;em&gt;dikkat kaybının&lt;/em&gt; önlendiğini fark ettim. Akış kırılmıyor.&lt;/p&gt;

&lt;h2&gt;
  
  
  SSR/hydration: artık "umarım tutar" değil "tutuyor"
&lt;/h2&gt;

&lt;p&gt;Bu projede zaten server-side render kullanıyordum ve geçmişte hydration mismatch'leri benim için hep bir şans oyunuydu — console'da sessizce bir uyarı çıkar, bazen çıkmaz, production'da bazen görürsün bazen görmezsin. Angular 21'deki hydration iyileştirmeleriyle bu belirsizlik payı küçüldü. En somut fayda: WebGL arka planımı (&lt;code&gt;gl-bg&lt;/code&gt; component'i) server'da hiç render etmeden client'a bırakan kod artık daha az özel-durum kontrolüne ihtiyaç duyuyor çünkü framework "bu hydrate edilemez, atla" durumunu benden daha iyi anlıyor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Genel his: framework artık arkanda duruyor, önünde durmuyor
&lt;/h2&gt;

&lt;p&gt;Eski Angular'la çalışırken bazen hissettiğim şey şuydu: framework'ün varsayımlarına &lt;em&gt;karşı&lt;/em&gt; kod yazıyordum — CD döngüsünü kandırmak, gereksiz re-render'ı önlemek, RxJS subscription'ının sızmadığından emin olmak için ekstra defans yazmak. Angular 21'de bu defans katmanının çoğu kayboldu. Signal + zoneless + resource() üçlüsü bir araya geldiğinde ortaya çıkan şey "daha az kod" değil — "daha az kod &lt;em&gt;hakkında endişelenmek&lt;/em&gt;". Fark küçük gibi görünüyor ama bir codebase'i aylarca sürdürdüğünde asıl yorgunluğun kaynağının satır sayısı değil, bu arka plan endişesi olduğunu anlıyorsun.&lt;/p&gt;

&lt;p&gt;Denemenizi mutlaka önereceğim üç şey varsa: &lt;code&gt;resource()&lt;/code&gt;'ı gerçek bir route-level veri çekme senaryosunda kullanın (basit bir todo listesinde farkı göremezsiniz), zoneless'ı açık bir projede &lt;code&gt;@HostListener&lt;/code&gt; ile güncellenen bir state'i signal'a çevirmeden bırakıp neyin kırıldığını izleyin (öğretici bir acı), ve &lt;code&gt;@for&lt;/code&gt; + &lt;code&gt;track&lt;/code&gt;'i yanlış kullanıp DOM'un ne zaman "hatırladığını" ne zaman unuttuğunu bizzat görün. Üçü de dokümanda okuyunca sıradan görünüyor. Elde deneyince değil.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://ysndmr.com/writings/angular-21-defterime-dustugum-notlar?utm_source=devto&amp;amp;utm_medium=cross-post&amp;amp;utm_campaign=writings" rel="noopener noreferrer"&gt;ysndmr.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>angular21</category>
      <category>signals</category>
      <category>zoneless</category>
    </item>
    <item>
      <title>Signals Cleaned Up State in Angular. The DOM Was Still Stuck in 2018.</title>
      <dc:creator>Yasin Demir</dc:creator>
      <pubDate>Thu, 30 Jul 2026 22:55:47 +0000</pubDate>
      <link>https://dev.to/ysndmr/signals-cleaned-up-state-in-angular-the-dom-was-still-stuck-in-2018-2513</link>
      <guid>https://dev.to/ysndmr/signals-cleaned-up-state-in-angular-the-dom-was-still-stuck-in-2018-2513</guid>
      <description>&lt;p&gt;Angular Signals cleaned up state management. It didn't clean up the DOM.&lt;/p&gt;

&lt;p&gt;Every time I needed to know whether an element was on screen, I wrote the same directive again. Set up an &lt;code&gt;IntersectionObserver&lt;/code&gt; in &lt;code&gt;ngOnInit&lt;/code&gt;, disconnect it in &lt;code&gt;ngOnDestroy&lt;/code&gt;, call &lt;code&gt;markForCheck()&lt;/code&gt; so change detection actually notices, and wrap the whole thing in an &lt;code&gt;isPlatformBrowser&lt;/code&gt; check so the server doesn't blow up on a global that doesn't exist there.&lt;/p&gt;

&lt;p&gt;That's a lot of ceremony to answer "is this thing visible yet."&lt;/p&gt;

&lt;p&gt;The alternative was pulling in a third-party directive, which usually meant a dependency tree I didn't want and SSR behavior I had to verify myself.&lt;/p&gt;

&lt;p&gt;So I wrote &lt;strong&gt;ngx-viewport-signals&lt;/strong&gt;. Four primitives, no dependencies, and the observers clean themselves up.&lt;/p&gt;




&lt;h2&gt;
  
  
  The four primitives
&lt;/h2&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;ElementRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inject&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;inViewport&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;viewportRatio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;elementSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;scrollProgress&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;ngx-viewport-signals&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;HeroSection&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;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="nf"&gt;inViewport&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;el&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;once&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;ratio&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;viewportRatio&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;el&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;size&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;elementSize&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;el&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;progress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;scrollProgress&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;el&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;inViewport()&lt;/code&gt; is a boolean. Pass &lt;code&gt;once: true&lt;/code&gt; for entrance animations and it disconnects the observer after the first hit, instead of leaving a live one running for the rest of the page's life.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;viewportRatio()&lt;/code&gt; gives you the raw &lt;code&gt;0..1&lt;/code&gt; intersection ratio when a boolean isn't enough.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;elementSize()&lt;/code&gt; is a &lt;code&gt;{ width, height }&lt;/code&gt; signal backed by &lt;code&gt;ResizeObserver&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;scrollProgress()&lt;/code&gt; is &lt;code&gt;0..1&lt;/code&gt; as the element travels through the viewport. Useful for progress bars and parallax.&lt;/p&gt;

&lt;p&gt;All four accept an &lt;code&gt;ElementRef&lt;/code&gt;, a raw &lt;code&gt;Element&lt;/code&gt;, or an accessor function. That last one matters: it means a &lt;code&gt;viewChild()&lt;/code&gt; signal query drops straight in, no &lt;code&gt;AfterViewInit&lt;/code&gt; dance.&lt;/p&gt;




&lt;h2&gt;
  
  
  The part I actually care about: teardown
&lt;/h2&gt;

&lt;p&gt;Three of the four primitives share one skeleton, and the whole lifecycle story is a single &lt;code&gt;effect()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;onCleanup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;elementSignal&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="nx"&gt;el&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;observer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createObserver&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;onCleanup&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unobserve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disconnect&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;code&gt;onCleanup&lt;/code&gt; fires in both situations that matter: before the effect re-runs because the element changed, and when the injector is destroyed. One hook, both cases. No &lt;code&gt;DestroyRef&lt;/code&gt; wiring, no &lt;code&gt;ngOnDestroy&lt;/code&gt;, no leaked observer when a &lt;code&gt;viewChild()&lt;/code&gt; swaps out from under you.&lt;/p&gt;




&lt;h2&gt;
  
  
  SSR
&lt;/h2&gt;

&lt;p&gt;The guard sits at the top of the factory, not sprinkled through the code:&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;value&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="nx"&gt;initial&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="nf"&gt;isBrowserPlatform&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="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asReadonly&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;On the server you get a static signal holding the default and nothing else runs. &lt;code&gt;IntersectionObserver&lt;/code&gt; is never touched. Your components don't need platform checks of their own.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why scrollProgress is different
&lt;/h2&gt;

&lt;p&gt;The other three are one observer with a callback. &lt;code&gt;scrollProgress&lt;/code&gt; isn't — you need a value on every frame, and &lt;code&gt;IntersectionObserver&lt;/code&gt; only fires on threshold crossings.&lt;/p&gt;

&lt;p&gt;So it runs a &lt;code&gt;requestAnimationFrame&lt;/code&gt; loop, gated by an observer:&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;new&lt;/span&gt; &lt;span class="nc"&gt;IntersectionObserver&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isIntersecting&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;active&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;rafId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&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="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="nx"&gt;active&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;rafId&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;cancelAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rafId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;rafId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;threshold&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="na"&gt;rootMargin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0px&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rAF loop only spins while the element is actually intersecting. Scroll past it and the loop stops dead. Ten of these on a page cost you nothing when they're all off screen — which is most of the time.&lt;/p&gt;

&lt;p&gt;It didn't fit the shared skeleton, so I left it bespoke rather than bending the abstraction to cover it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Config is optional
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nf"&gt;provideViewportSignals&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;defaultRootMargin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0px&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;defaultThreshold&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set a baseline &lt;code&gt;rootMargin&lt;/code&gt; once if you want. Skip it entirely and everything still works — there's a default injection value, so nothing throws when the provider isn't there.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Demo:&lt;/strong&gt; &lt;a href="https://ysndmr.github.io/ngx-viewport-signals/" rel="noopener noreferrer"&gt;ysndmr.github.io/ngx-viewport-signals&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/ysndmr/ngx-viewport-signals" rel="noopener noreferrer"&gt;github.com/ysndmr/ngx-viewport-signals&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Install:&lt;/strong&gt; &lt;code&gt;npm i ngx-viewport-signals&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Angular 17 through 21, MIT, zero runtime dependencies.&lt;/p&gt;

&lt;p&gt;If you're doing scroll-driven animation, lazy-loading heavy components, or anything that reacts to layout, give it a shot. I'm curious how you're handling viewport observation right now — the hand-rolled directive is still the most common answer I get, and I'd like to know if that's changing.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://ysndmr.com/writings/ngx-viewport-signals-angular-viewport-primitives?utm_source=devto&amp;amp;utm_medium=cross-post&amp;amp;utm_campaign=writings" rel="noopener noreferrer"&gt;ysndmr.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>signals</category>
      <category>performance</category>
      <category>frontend</category>
    </item>
    <item>
      <title>The Two-Level Computed Graph: How I Stopped Toggle Events From Re-Running Business Logic</title>
      <dc:creator>Yasin Demir</dc:creator>
      <pubDate>Wed, 08 Jul 2026 23:14:18 +0000</pubDate>
      <link>https://dev.to/ysndmr/the-two-level-computed-graph-how-i-stopped-toggle-events-from-re-running-business-logic-3ke8</link>
      <guid>https://dev.to/ysndmr/the-two-level-computed-graph-how-i-stopped-toggle-events-from-re-running-business-logic-3ke8</guid>
      <description>&lt;p&gt;When you build a mapping interface in Angular Signals, the naive approach puts everything in one &lt;code&gt;computed&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;sections&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&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;draft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draftSignal&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;collapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collapsedSignal&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// 👈 mistake&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;section&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;section&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;collapsed&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks clean. One signal, one truth. But the dependency graph doesn't care about your intentions. Every time the user toggles a section open or closed, &lt;code&gt;collapsed&lt;/code&gt; changes, the computed re-runs, and &lt;code&gt;resolve()&lt;/code&gt;, which does badge counting, mapping resolution, and fallback calculation across every row in every section, runs again in full.&lt;/p&gt;

&lt;p&gt;On a feed with 50 columns and 200 values per column, that's a lot of work for a toggle.&lt;/p&gt;

&lt;p&gt;The fix is a two-level graph:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Level 1 — depends on data + draft only&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;resolvedSections&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&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;draft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draftSignal&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;section&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;section&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="c1"&gt;// isExpanded = false here, placeholder&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Level 2 — depends on Level 1 + collapse state only&lt;/span&gt;
&lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;sections&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;collapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collapsedSignal&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="nf"&gt;resolvedSections&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;section&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;section&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;isExpanded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;section&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;available&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;collapsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;section&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now a toggle only invalidates Level 2. Badge counts, mapping resolution, validation: none of that runs. &lt;code&gt;isValidToSave&lt;/code&gt; reads &lt;code&gt;resolvedSections()&lt;/code&gt; directly, so it's also unaffected by toggles.&lt;/p&gt;

&lt;p&gt;The broader principle: Angular's computed graph is only as good as your dependency boundaries. If a computed reads ten signals but only five of them are relevant to its output, the other five are noise, and that noise has a runtime cost. Designing the graph means deciding, explicitly, which signals belong together.&lt;/p&gt;

&lt;p&gt;Glitch-free reactivity isn't a property you get for free. It's something you architect.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://ysndmr.com/writings/two-level-computed-graph-toggle-events?utm_source=devto&amp;amp;utm_medium=cross-post&amp;amp;utm_campaign=writings" rel="noopener noreferrer"&gt;ysndmr.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>signals</category>
      <category>architecture</category>
      <category>frontend</category>
    </item>
    <item>
      <title>useEffect Is Not a Lifecycle Method</title>
      <dc:creator>Yasin Demir</dc:creator>
      <pubDate>Wed, 08 Jul 2026 15:12:18 +0000</pubDate>
      <link>https://dev.to/ysndmr/useeffect-is-not-a-lifecycle-method-326i</link>
      <guid>https://dev.to/ysndmr/useeffect-is-not-a-lifecycle-method-326i</guid>
      <description>&lt;p&gt;I spent longer than I should have mentally mapping &lt;code&gt;useEffect&lt;/code&gt; onto &lt;code&gt;componentDidMount&lt;/code&gt; and &lt;code&gt;componentDidUpdate&lt;/code&gt;. That model works just enough to feel right. Then it breaks in ways that are genuinely hard to debug.&lt;/p&gt;

&lt;p&gt;The actual model is simpler: &lt;strong&gt;useEffect synchronizes something outside React with your current state.&lt;/strong&gt; That's it. Not "run when the component mounts." Synchronize an external thing.&lt;/p&gt;

&lt;p&gt;The clearest version of this is a subscription. If your effect subscribes to something and your cleanup unsubscribes, React runs that cycle every time the dependency changes. Subscribe, unsubscribe, subscribe again. This feels wrong through the lifecycle lens. Through the synchronization lens it makes complete sense: whenever &lt;code&gt;userId&lt;/code&gt; changes, I need to be subscribed to the right user's data.&lt;/p&gt;

&lt;p&gt;Where this actually matters in practice: data fetching.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/user/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;setUser&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This has a race condition. Two fetches fire, the slower one resolves second, you display stale data. The lifecycle model doesn't explain why. The synchronization model does: you started a new sync cycle, you never cancelled the old one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;cancelled&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/user/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="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="nx"&gt;cancelled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;setUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cancelled&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="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cleanup doesn't mean "clean up when the component unmounts." It means "clean up before the next sync cycle starts."&lt;/p&gt;

&lt;p&gt;Most of the time when &lt;code&gt;useEffect&lt;/code&gt; feels weird or wrong, it's because you're thinking about &lt;em&gt;when&lt;/em&gt;, not &lt;em&gt;what&lt;/em&gt;. The question isn't "when does this run?" It's "what am I keeping in sync, and with what?"&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://ysndmr.com/writings/useeffect-is-not-a-lifecycle-method?utm_source=devto&amp;amp;utm_medium=cross-post&amp;amp;utm_campaign=writings" rel="noopener noreferrer"&gt;ysndmr.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>frontend</category>
    </item>
    <item>
      <title>rxResource Is Not a Command Bus</title>
      <dc:creator>Yasin Demir</dc:creator>
      <pubDate>Wed, 08 Jul 2026 15:12:17 +0000</pubDate>
      <link>https://dev.to/ysndmr/rxresource-is-not-a-command-bus-2jj7</link>
      <guid>https://dev.to/ysndmr/rxresource-is-not-a-command-bus-2jj7</guid>
      <description>&lt;p&gt;When Angular shipped &lt;code&gt;rxResource&lt;/code&gt;, a lot of developers, myself included, immediately asked: can I use this for mutations too?&lt;/p&gt;

&lt;p&gt;The answer is technically yes. Practically, no.&lt;/p&gt;

&lt;p&gt;Here's what it looks like when you try:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;saveTrigger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Payload&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;saveResource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rxResource&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;params&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;saveTrigger&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;save&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;saveTrigger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buildPayload&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;Three things go wrong immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First&lt;/strong&gt;, &lt;code&gt;params&lt;/code&gt; starts as &lt;code&gt;undefined&lt;/code&gt;. &lt;code&gt;rxResource&lt;/code&gt; still evaluates the stream, so on initial render, before the user has done anything, your save API gets called with &lt;code&gt;undefined&lt;/code&gt;. You can guard against this, but you're already fighting the primitive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second&lt;/strong&gt;, if the user saves twice with identical data, &lt;code&gt;saveTrigger.set()&lt;/code&gt; produces a new object reference each time, so the signal technically changes, and the save fires. But if anything in your pipeline normalises the payload or Angular's scheduler batches the sets, you might skip the second trigger silently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third&lt;/strong&gt;, error recovery is broken. After a failed save, &lt;code&gt;saveTrigger&lt;/code&gt; still holds the last payload. The next &lt;code&gt;save()&lt;/code&gt; call with the same data produces a new object reference, which re-triggers. That might be what you want. Or it might not. &lt;code&gt;rxResource&lt;/code&gt; wasn't designed to give you control over this.&lt;/p&gt;

&lt;p&gt;The correct tool for a one-shot mutation is a direct subscribe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;isSavingSignal&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="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;save&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;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buildPayload&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;isSavingSignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&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;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;takeUntilDestroyed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;destroyRef&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&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;handleSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&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;isSavingSignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="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;handleError&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;isSavingSignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&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;Explicit, predictable, recoverable. &lt;code&gt;isSavingSignal&lt;/code&gt; is a plain &lt;code&gt;WritableSignal&lt;/code&gt;: readable anywhere, testable in isolation.&lt;/p&gt;

&lt;p&gt;The rule I now apply: &lt;strong&gt;&lt;code&gt;rxResource&lt;/code&gt; is for idempotent reads&lt;/strong&gt;. If calling the operation twice with the same input would be a problem, it's a command, and commands don't belong in &lt;code&gt;rxResource&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;RxJS and Signals aren't competing. They're for different jobs. The skill is knowing which job you're solving before you pick the tool.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://ysndmr.com/writings/rxresource-is-not-a-command-bus?utm_source=devto&amp;amp;utm_medium=cross-post&amp;amp;utm_campaign=writings" rel="noopener noreferrer"&gt;ysndmr.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>rxjs</category>
      <category>signals</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Why I Stopped Writing tap() Inside rxResource Streams</title>
      <dc:creator>Yasin Demir</dc:creator>
      <pubDate>Wed, 08 Jul 2026 09:40:46 +0000</pubDate>
      <link>https://dev.to/ysndmr/why-i-stopped-writing-tap-inside-rxresource-streams-4db2</link>
      <guid>https://dev.to/ysndmr/why-i-stopped-writing-tap-inside-rxresource-streams-4db2</guid>
      <description>&lt;p&gt;There's a pattern I see a lot in Angular codebases that adopted Signals early: a developer discovers &lt;code&gt;rxResource&lt;/code&gt;, loves that it handles loading and error state automatically, and then immediately reaches for &lt;code&gt;tap()&lt;/code&gt; to write a signal inside the stream.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rxResource&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;params&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;paramsSignal&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sideSignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;// 💥&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks harmless. It runs in development without complaint in zone-based Angular. Then you enable zoneless, or Angular tightens its reactive graph enforcement, and you get &lt;code&gt;NG0600: Writing to signals is not allowed in a reactive context&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;rxResource&lt;/code&gt; stream runs inside Angular's reactive scheduler. Signal writes there aren't just discouraged. They're illegal by design. The scheduler assumes computed signals and reactive contexts are read-only during evaluation. A write mid-computation breaks the glitch-free guarantee Angular's signal graph is built on.&lt;/p&gt;

&lt;p&gt;The fix I landed on: make the stream return everything it needs to return, as a single typed value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ResourceValue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;sections&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Section&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rxResource&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ResourceValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Params&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="na"&gt;sections&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="na"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&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;No &lt;code&gt;tap&lt;/code&gt;. No side signal. Everything the rest of the store needs lives in &lt;code&gt;resource.value()&lt;/code&gt; and can be read via &lt;code&gt;computed&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The lesson isn't "don't use tap". The lesson is that &lt;code&gt;rxResource&lt;/code&gt; has a contract: it is a &lt;strong&gt;read primitive&lt;/strong&gt;. Its stream is for fetching and transforming. If you're writing signals inside it, you're treating it as a command bus, and that's a different tool.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://ysndmr.com/writings/why-i-stopped-writing-tap-inside-rxresource-streams?utm_source=devto&amp;amp;utm_medium=cross-post&amp;amp;utm_campaign=writings" rel="noopener noreferrer"&gt;ysndmr.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>rxjs</category>
      <category>signals</category>
      <category>frontend</category>
    </item>
    <item>
      <title>On building systems that survive team turnover</title>
      <dc:creator>Yasin Demir</dc:creator>
      <pubDate>Tue, 07 Jul 2026 12:14:39 +0000</pubDate>
      <link>https://dev.to/ysndmr/on-building-systems-that-survive-team-turnover-ebg</link>
      <guid>https://dev.to/ysndmr/on-building-systems-that-survive-team-turnover-ebg</guid>
      <description>&lt;p&gt;I have taken over codebases and had codebases taken from me. The experience in both directions has converged on one principle: a durable system communicates intent, not just implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What durable looks like
&lt;/h2&gt;

&lt;p&gt;Naming that explains purpose. Architecture that encodes constraints. Tests that document behavior, not implementation. The goal is a codebase where a newcomer can form a correct mental model in an afternoon.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hardest part
&lt;/h2&gt;

&lt;p&gt;Documentation rots. Comments lie. The only truthful documentation is the code itself. That's why naming and structure matter more than any README.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://ysndmr.com/writings/systems-that-survive-turnover?utm_source=devto&amp;amp;utm_medium=cross-post&amp;amp;utm_campaign=writings" rel="noopener noreferrer"&gt;ysndmr.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>engineering</category>
      <category>process</category>
    </item>
    <item>
      <title>Why I stopped using design tokens as implementation details</title>
      <dc:creator>Yasin Demir</dc:creator>
      <pubDate>Tue, 07 Jul 2026 12:14:38 +0000</pubDate>
      <link>https://dev.to/ysndmr/why-i-stopped-using-design-tokens-as-implementation-details-1kca</link>
      <guid>https://dev.to/ysndmr/why-i-stopped-using-design-tokens-as-implementation-details-1kca</guid>
      <description>&lt;p&gt;Design tokens are not CSS variables. This distinction sounds semantic, but getting it wrong produces systems that serve neither designers nor developers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mistake
&lt;/h2&gt;

&lt;p&gt;When teams generate tokens from Figma and map them 1:1 to CSS variables, they end up with &lt;code&gt;--color-blue-500&lt;/code&gt; everywhere. This is an implementation detail, not a token. A token is a decision: &lt;code&gt;--color-accent&lt;/code&gt;. The implementation of that decision can change. The token should not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The correct layer
&lt;/h2&gt;

&lt;p&gt;Tokens belong at the semantic layer. They answer the question "what is this for?" not "what is this value?". &lt;code&gt;--color-border-subtle&lt;/code&gt; is a token. &lt;code&gt;--gray-200&lt;/code&gt; is a primitive.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://ysndmr.com/writings/design-tokens-not-implementation?utm_source=devto&amp;amp;utm_medium=cross-post&amp;amp;utm_campaign=writings" rel="noopener noreferrer"&gt;ysndmr.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>css</category>
      <category>frontend</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Stop Putting Everything in useState</title>
      <dc:creator>Yasin Demir</dc:creator>
      <pubDate>Tue, 07 Jul 2026 11:00:29 +0000</pubDate>
      <link>https://dev.to/ysndmr/stop-putting-everything-in-usestate-4oeg</link>
      <guid>https://dev.to/ysndmr/stop-putting-everything-in-usestate-4oeg</guid>
      <description>&lt;p&gt;There's a pattern I see constantly: every piece of UI state gets its own &lt;code&gt;useState&lt;/code&gt;, the component re-renders on every interaction, someone adds &lt;code&gt;useMemo&lt;/code&gt; to fix the performance, and the whole thing gets progressively harder to follow.&lt;/p&gt;

&lt;p&gt;Most of that &lt;code&gt;useState&lt;/code&gt; wasn't necessary.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;useState&lt;/code&gt; is for values React needs to track in order to render correctly. Not every value in a component qualifies.&lt;/p&gt;

&lt;p&gt;Three things that routinely end up in state when they shouldn't:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Derived values.&lt;/strong&gt; If you have &lt;code&gt;items&lt;/code&gt; in state and you're also storing &lt;code&gt;filteredItems&lt;/code&gt; in state, you have two sources of truth that can diverge. &lt;code&gt;filteredItems&lt;/code&gt; isn't state, it's a computation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// what I see&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setItems&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;filteredItems&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setFilteredItems&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;

&lt;span class="c1"&gt;// what it should be&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setItems&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&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;filteredItems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&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="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;items&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;Values you only read in event handlers.&lt;/strong&gt; If a value never influences what gets rendered, it doesn't need to trigger a re-render. &lt;code&gt;useRef&lt;/code&gt; holds it without the overhead. A closure variable sometimes works too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every form field.&lt;/strong&gt; Controlled inputs are occasionally the right call. More often, you're adding a re-render on every keystroke for no benefit. Reading from &lt;code&gt;FormData&lt;/code&gt; on submit, or using an uncontrolled input with a ref, covers most cases without the noise.&lt;/p&gt;

&lt;p&gt;The check I use before reaching for &lt;code&gt;useState&lt;/code&gt;: if this value changes and nothing in the rendered output needs to change, it's probably not state. If it can be computed from something that is state, it definitely isn't.&lt;/p&gt;

&lt;p&gt;React re-renders fast, but not free. The easiest performance wins don't come from memoizing expensive renders. They come from not rendering when you don't need to.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://ysndmr.com/writings/stop-putting-everything-in-usestate?utm_source=devto&amp;amp;utm_medium=cross-post&amp;amp;utm_campaign=writings" rel="noopener noreferrer"&gt;ysndmr.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Angular Signals: six months of production observations</title>
      <dc:creator>Yasin Demir</dc:creator>
      <pubDate>Tue, 07 Jul 2026 10:37:34 +0000</pubDate>
      <link>https://dev.to/ysndmr/angular-signals-six-months-of-production-observations-3eoc</link>
      <guid>https://dev.to/ysndmr/angular-signals-six-months-of-production-observations-3eoc</guid>
      <description>&lt;p&gt;Six months ago I started migrating the MKX Capital frontend from a BehaviorSubject-heavy architecture to Angular Signals. Here is what I learned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Signals win
&lt;/h2&gt;

&lt;p&gt;Computed state is where Signals are genuinely superior to RxJS. With &lt;code&gt;computed()&lt;/code&gt;, derived state is lazy, memoized, and glitch-free. The mental model is simpler: a Signal is a value, not a stream.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where they fall short
&lt;/h2&gt;

&lt;p&gt;Time-based operations. Anything involving &lt;code&gt;debounceTime&lt;/code&gt;, &lt;code&gt;throttleTime&lt;/code&gt;, or complex multicasting still belongs in RxJS. The async pipe story for Signals is better with &lt;code&gt;toSignal()&lt;/code&gt;, but the interop layer adds cognitive overhead.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule I settled on
&lt;/h2&gt;

&lt;p&gt;Signals for synchronous reactive state. RxJS for async streams (HTTP, WebSockets, timers). The two cooperate well via &lt;code&gt;toSignal()&lt;/code&gt; and &lt;code&gt;toObservable()&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://ysndmr.com/writings/angular-signals-production?utm_source=devto&amp;amp;utm_medium=cross-post&amp;amp;utm_campaign=writings" rel="noopener noreferrer"&gt;ysndmr.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>signals</category>
      <category>rxjs</category>
      <category>frontend</category>
    </item>
    <item>
      <title>State colocation is not a preference, it is an architecture</title>
      <dc:creator>Yasin Demir</dc:creator>
      <pubDate>Tue, 07 Jul 2026 09:53:35 +0000</pubDate>
      <link>https://dev.to/ysndmr/state-colocation-is-not-a-preference-it-is-an-architecture-2p5k</link>
      <guid>https://dev.to/ysndmr/state-colocation-is-not-a-preference-it-is-an-architecture-2p5k</guid>
      <description>&lt;p&gt;The first question I ask when reviewing a frontend architecture is: where does the state live relative to where it is used?&lt;/p&gt;

&lt;p&gt;In most codebases I have reviewed, the answer is "in a global store, regardless of scope." This is the wrong default.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule
&lt;/h2&gt;

&lt;p&gt;State should live as close to its consumers as possible. If only one component needs it, it is component state. If a subtree needs it, it is a context or service scoped to that subtree. Global state is for truly global concerns: authentication, locale, theme.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://ysndmr.com/writings/state-colocation?utm_source=devto&amp;amp;utm_medium=cross-post&amp;amp;utm_campaign=writings" rel="noopener noreferrer"&gt;ysndmr.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>frontend</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
