<?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>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>
    <item>
      <title>What ref() and reactive() Are Actually For</title>
      <dc:creator>Yasin Demir</dc:creator>
      <pubDate>Tue, 07 Jul 2026 09:39:53 +0000</pubDate>
      <link>https://dev.to/ysndmr/what-ref-and-reactive-are-actually-for-c52</link>
      <guid>https://dev.to/ysndmr/what-ref-and-reactive-are-actually-for-c52</guid>
      <description>&lt;p&gt;Vue 3 gives you two ways to make something reactive: &lt;code&gt;ref()&lt;/code&gt; and &lt;code&gt;reactive()&lt;/code&gt;. The docs explain the difference clearly enough. What they don't tell you is which one to reach for by default.&lt;/p&gt;

&lt;p&gt;The short answer: &lt;strong&gt;&lt;code&gt;ref()&lt;/code&gt;, almost always.&lt;/strong&gt; &lt;code&gt;reactive()&lt;/code&gt; for specific situations where you know what you're trading away.&lt;/p&gt;

&lt;p&gt;Here's the problem with &lt;code&gt;reactive()&lt;/code&gt;. It loses reactivity when you destructure it or reassign the root object, which catches people off guard more than it should.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;reactive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;count&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       &lt;span class="c1"&gt;// works, Vue tracks this&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;count&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// doesn't work, Vue has no idea&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ref()&lt;/code&gt; sidesteps this because the reactive pointer lives on the &lt;code&gt;.value&lt;/code&gt; property. You can pass a ref anywhere, destructure the container, and the ref itself stays tracked.&lt;/p&gt;

&lt;p&gt;The case where &lt;code&gt;reactive()&lt;/code&gt; actually earns its place: a form object where all the values are always read and written together, passed as-is to a composable, and you're never going to replace the whole reference. Some people find dropping the &lt;code&gt;.value&lt;/code&gt; noise worth that constraint. I get it. But in composables especially, &lt;code&gt;ref()&lt;/code&gt; is much easier to work with because you can return individual refs that stay reactive when the caller destructures them.&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useCounter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;increment&lt;/span&gt; &lt;span class="o"&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;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;increment&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;increment&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCounter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// both still reactive&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can't do that cleanly with &lt;code&gt;reactive()&lt;/code&gt;: destructuring breaks the connection.&lt;/p&gt;

&lt;p&gt;For most of the time I was figuring out the ref/reactive split, I was making it more complicated than it needed to be. Default to &lt;code&gt;ref()&lt;/code&gt;. Reach for &lt;code&gt;reactive()&lt;/code&gt; when you have a specific reason. That's the whole decision.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://ysndmr.com/writings/what-ref-and-reactive-are-actually-for?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>vue</category>
      <category>javascript</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Watchers Are Usually the Wrong Answer</title>
      <dc:creator>Yasin Demir</dc:creator>
      <pubDate>Tue, 07 Jul 2026 09:39:46 +0000</pubDate>
      <link>https://dev.to/ysndmr/watchers-are-usually-the-wrong-answer-15an</link>
      <guid>https://dev.to/ysndmr/watchers-are-usually-the-wrong-answer-15an</guid>
      <description>&lt;p&gt;When I started with Vue's Composition API I used &lt;code&gt;watch()&lt;/code&gt; constantly. State changes, something needs to happen. That's what watchers are for. Right?&lt;/p&gt;

&lt;p&gt;Most of the time, no.&lt;/p&gt;

&lt;p&gt;Watchers are for side effects: persisting to &lt;code&gt;localStorage&lt;/code&gt;, calling an API when something changes, syncing with a library that Vue doesn't control. They're not for computing derived values, and they're not for keeping reactive state in sync with other reactive state.&lt;/p&gt;

&lt;p&gt;The most common misuse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firstName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fullName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;last&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;fullName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&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;first&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;last&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a computed property written the hard way. It runs asynchronously by default, it's harder to read, and it can produce frames where &lt;code&gt;fullName&lt;/code&gt; is stale before the watcher fires.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firstName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fullName&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&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;lastName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Always in sync. No timing edge cases. One line.&lt;/p&gt;

&lt;p&gt;The other pattern that causes problems: chaining watchers. Watch A to update B, watch B to update C. I've done this. Every time I've eventually produced a cycle or a timing issue that was annoying to debug. If values derive from each other, &lt;code&gt;computed&lt;/code&gt; handles it.&lt;/p&gt;

&lt;p&gt;Where &lt;code&gt;watch()&lt;/code&gt; actually belongs: real side effects. &lt;code&gt;userId&lt;/code&gt; changes and you need to fetch new data, that's a watcher. The route changes and you need to update a third-party map instance, watcher. You need to debounce an API call on search input, watcher with a cleanup function.&lt;/p&gt;

&lt;p&gt;The question I ask before adding a &lt;code&gt;watch&lt;/code&gt;: is this a side effect, or is this a value that depends on other values? If it's the second thing, it's a &lt;code&gt;computed&lt;/code&gt;. I've removed a lot of watchers by asking that.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://ysndmr.com/writings/watchers-are-usually-the-wrong-answer?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>vue</category>
      <category>javascript</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Why I Built ngx-local-vault: Signals, Encryption, and TTL for Angular Storage</title>
      <dc:creator>Yasin Demir</dc:creator>
      <pubDate>Tue, 07 Jul 2026 08:42:14 +0000</pubDate>
      <link>https://dev.to/ysndmr/why-i-built-ngx-local-vault-bringing-signals-encryption-and-ttl-to-angular-storage-3jd6</link>
      <guid>https://dev.to/ysndmr/why-i-built-ngx-local-vault-bringing-signals-encryption-and-ttl-to-angular-storage-3jd6</guid>
      <description>&lt;p&gt;Every app needs to persist something in the browser: user preferences, a sidebar toggle, a short-lived session token. &lt;code&gt;localStorage&lt;/code&gt; and &lt;code&gt;sessionStorage&lt;/code&gt; are the default tools for that.&lt;/p&gt;

&lt;p&gt;But using the raw Web Storage API in Angular still feels like 2015. You end up writing the same boilerplate every time.&lt;/p&gt;

&lt;p&gt;Most storage wrappers give you a getter and a setter and call it done. Reactivity, encryption, expiration: you're on your own for all three.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;ngx-local-vault&lt;/strong&gt;: persistence, encryption, and TTL, wired into a native Angular &lt;code&gt;WritableSignal&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Actually Wrong with Browser Storage
&lt;/h2&gt;

&lt;p&gt;If you've built anything non-trivial in Angular, at least one of these has bitten you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No reactivity.&lt;/strong&gt; &lt;code&gt;localStorage.setItem()&lt;/code&gt; doesn't trigger change detection. You end up writing your own events, or an RxJS Subject, just so a component notices the value changed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plain text.&lt;/strong&gt; JWTs and profile data sitting in &lt;code&gt;localStorage&lt;/code&gt; in plain text are one DevTools tab (or one XSS script) away from being read.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No expiry.&lt;/strong&gt; &lt;code&gt;localStorage&lt;/code&gt; just sits there forever. Want a token to die after 15 minutes? Store a timestamp yourself, check it on load, delete it yourself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSR headaches.&lt;/strong&gt; Touch &lt;code&gt;window.localStorage&lt;/code&gt; during server-side rendering and you get &lt;code&gt;ReferenceError: window is not defined&lt;/code&gt;. Wrapping every access in &lt;code&gt;isPlatformBrowser&lt;/code&gt; gets old fast.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ngx-local-vault
&lt;/h2&gt;

&lt;p&gt;It's a small library, under 2KB gzipped with no dependencies beyond Angular itself, that treats browser storage as something a component can just read reactively.&lt;/p&gt;

&lt;p&gt;Instead of separate read and write calls, there's a &lt;code&gt;watchSignal()&lt;/code&gt; method that hands you back a real Angular Signal. Read it, &lt;code&gt;.set()&lt;/code&gt; it, &lt;code&gt;.update()&lt;/code&gt; it: encryption and TTL happen underneath without you thinking about them.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Setup
&lt;/h3&gt;

&lt;p&gt;Add it to &lt;code&gt;app.config.ts&lt;/code&gt;. Works with Angular 17 through 20.&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;ApplicationConfig&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;provideVault&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-local-vault&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ApplicationConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;provideVault&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;prefix&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_&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;encryptionKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-secure-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;local&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Using it
&lt;/h3&gt;

&lt;p&gt;Inject &lt;code&gt;VaultService&lt;/code&gt; and watch a key:&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;inject&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;@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;VaultService&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-local-vault&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-root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app.component.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;standalone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;vault&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;VaultService&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;theme&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;vault&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;watchSignal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;session&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;vault&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;watchSignal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;session-token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;expiresIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;15m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;toggleTheme&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;theme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&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;token&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;h3&gt;
  
  
  What it does differently
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real signals.&lt;/strong&gt; &lt;code&gt;watchSignal()&lt;/code&gt; returns an actual &lt;code&gt;WritableSignal&amp;lt;T&amp;gt;&lt;/code&gt;, not some wrapper you have to learn.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encrypted at rest.&lt;/strong&gt; Open the Application tab in DevTools and you won't find raw JSON or a bare token sitting there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TTL that actually deletes itself.&lt;/strong&gt; Pass &lt;code&gt;expiresIn: '15m'&lt;/code&gt; and the entry disappears in-tab when the clock runs out. No reload needed. It takes plain strings: &lt;code&gt;500ms&lt;/code&gt;, &lt;code&gt;30s&lt;/code&gt;, &lt;code&gt;15m&lt;/code&gt;, &lt;code&gt;2h&lt;/code&gt;, &lt;code&gt;1d&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSR-safe.&lt;/strong&gt; Guarded by &lt;code&gt;PLATFORM_ID&lt;/code&gt;, so on the server it's just a no-op. No hydration mismatches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Actually zero dependencies.&lt;/strong&gt; Only &lt;code&gt;@angular/core&lt;/code&gt; and &lt;code&gt;@angular/common&lt;/code&gt; as peers. Even &lt;code&gt;tslib&lt;/code&gt; gets stripped at build time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Live demo: &lt;a href="https://ysndmr.github.io/ngx-local-vault" rel="noopener noreferrer"&gt;ysndmr.github.io/ngx-local-vault&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;NPM: &lt;a href="https://npmjs.com/package/ngx-local-vault" rel="noopener noreferrer"&gt;npmjs.com/package/ngx-local-vault&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/ysndmr/ngx-local-vault" rel="noopener noreferrer"&gt;github.com/ysndmr/ngx-local-vault&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;My site: &lt;a href="https://ysndmr.com" rel="noopener noreferrer"&gt;ysndmr.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Original post: &lt;a href="https://dev.to/ysndmr/why-i-built-ngx-local-vault-bringing-signals-encryption-and-ttl-to-angular-storage-3jd6"&gt;dev.to&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're not on Angular, there are React and Vue versions too. Links are in the GitHub README.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;Signals changed how Angular handles state. I think storage should have followed the same shift a while ago.&lt;/p&gt;

&lt;p&gt;How are you handling browser storage in your Angular apps right now? Open an issue or drop a star on GitHub if this is useful to you.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://ysndmr.com/writings/why-i-built-ngx-local-vault?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>frontend</category>
    </item>
  </channel>
</rss>
