<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Developer Joon on Medium]]></title>
        <description><![CDATA[Stories by Developer Joon on Medium]]></description>
        <link>https://medium.com/@dev.master0917?source=rss-dad9984c4665------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*YcMG2_yrtl0qeqNa</url>
            <title>Stories by Developer Joon on Medium</title>
            <link>https://medium.com/@dev.master0917?source=rss-dad9984c4665------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Fri, 13 Nov 2026 22:16:50 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@dev.master0917/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[[TIL — Angular] I’ve Been Using My Team’s Validator Pattern for 3 Years.]]></title>
            <link>https://medium.com/@dev.master0917/til-angular-ive-been-using-my-team-s-validator-pattern-for-3-years-afc79394e646?source=rss-dad9984c4665------2</link>
            <guid isPermaLink="false">https://medium.com/p/afc79394e646</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[frontend-development]]></category>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[software-engineering]]></category>
            <dc:creator><![CDATA[Developer Joon]]></dc:creator>
            <pubDate>Wed, 29 Apr 2026 19:22:50 GMT</pubDate>
            <atom:updated>2026-04-29T19:22:50.738Z</atom:updated>
            <content:encoded><![CDATA[<p><strong><em>[TIL — Angular] I’ve Been Using My Team’s Validator Pattern for 3 Years. I Just Realized Why It’s Different.</em></strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Minu8B5KLxDFxG6yUtqpOg.png" /></figure><p>I’ve been writing Angular for 4 years. For the last 3 of them, I’ve been using my team’s custom validator pattern at work — copy-pasting it, extending it, debugging it. I never wrote one from scratch.</p><p>Last night, while studying for the Angular L3 certification, I finally did. And what came out of my fingers looked <strong>nothing</strong> like what we have at work.</p><p><em>Mine:</em></p><pre>title: new FormControl(&#39;&#39;, {<br>  validators: [noProfanityValidator([&#39;spam&#39;, &#39;advertisement&#39;])],<br>});<br><br>export function noProfanityValidator(forbidden: string[]): ValidatorFn {<br>  return (control) =&gt; {<br>    const value = control.value;<br>    if (!value) return null;<br>    const found = forbidden.find(w =&gt; value.toLowerCase().includes(w));<br>    return found ? { profanity: found } : null;<br>  };<br>}</pre><p><em>My team’s, simplified:</em></p><pre>this.formGroup = new FormGroup({<br>  title: new FormControl(&#39;&#39;),<br>  content: new FormControl(&#39;&#39;),<br>}, {<br>  validators: [<br>    CustomValidator({<br>      fieldNameTitle: &#39;title&#39;,<br>      errorName: { profanity: &#39;profanity&#39; },<br>    }),<br>  ],<br>});<br><br>export function CustomValidator(options: ValidatorOptions): ValidatorFn {<br>  return (group: AbstractControl) =&gt; {<br>    const titleField = group.get(options.fieldNameTitle);<br>    const title: string | undefined = titleField?.value;<br>    // ...<br>  };<br>}</pre><p>Two questions hit me:</p><ol><li><em>Why does ours pass the field name as a config option?</em></li><li><em>Why does ours register on the FormGroup, not the individual FormControl?</em></li></ol><p>For three years, I’d been using this pattern. I’d never asked.</p><h3><strong><em>The Answer Was Architectural, Not Stylistic</em></strong></h3><p>Here’s what clicked:</p><p><strong>FormControl-level validators</strong> (mine) only see one field. The control parameter <em>is</em> that field. control.value is the string directly.</p><p><strong>FormGroup-level validators</strong> (my team’s) see the entire group. control is the FormGroup. To access a field, you call control.get(&#39;title&#39;).</p><p>This isn’t a style preference. It’s a structural decision.</p><p>If you only need to validate <strong>one field in isolation</strong> — “title can’t contain ‘spam’” — FormControl-level is simpler. Less indirection. Errors attach directly to the field, easier to display in the template.</p><p>If you need to validate <strong>across fields</strong> — “password must equal passwordConfirm” — FormControl-level is impossible. You need access to both fields, which only exists at the group level.</p><p>My team chose FormGroup-level + options for <strong>a third reason</strong> I’d missed: they wanted one reusable validator factory across multiple forms. The fieldNameTitle: &#39;title&#39; option lets the same validator work whether the field is called title, subject, or headline.</p><p>That’s not “complicated for the sake of it.” That’s a deliberate choice for a large codebase where consistency across teams matters more than terseness in a single form.</p><h3>The Uncomfortable Realization</h3><p>I’d been <strong>using</strong> my team’s pattern for 3 years without asking why it looked different from what a tutorial would teach.</p><p>If you’d asked me a week ago “what’s the difference between registering a validator on a FormControl vs a FormGroup?” — I would have said “uh, scope, I guess?” and moved on.</p><p>The exam preparation forced me to write one from scratch. And once I had something to compare against, the difference at work suddenly had a <em>shape</em>. It stopped being “the way we do it here” and became “a deliberate choice for these specific reasons.”</p><h3>What I’m Trying On</h3><p>I think this is a kind of blindness that gets common as you stay longer in a codebase. You stop seeing patterns as choices. They become “the way it is.”</p><p>Studying for an exam is one way to break that — not because the exam content matters per se, but because it forces you to <strong>build from zero</strong>, which forces a comparison.</p><p>Side projects work too. So does pair programming with someone from another company. So does mentoring a junior who asks “why?” about something you’ve never questioned.</p><p>Four years of using a pattern doesn’t mean understanding it. Sometimes it takes a side project, an exam, and a comparison to see what was always there.</p><p>What’s the last “obvious” thing in your codebase that you don’t actually know the <em>why</em> of?</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=afc79394e646" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[[Angular] Building a Simple Dialog? 3 Key Component Design Principles I Learned from a Challenge]]></title>
            <link>https://medium.com/@dev.master0917/angular-building-a-simple-dialog-3-key-component-design-principles-i-learned-from-a-challenge-878c7957b999?source=rss-dad9984c4665------2</link>
            <guid isPermaLink="false">https://medium.com/p/878c7957b999</guid>
            <category><![CDATA[component-design]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[frontend-development]]></category>
            <category><![CDATA[web-standards]]></category>
            <dc:creator><![CDATA[Developer Joon]]></dc:creator>
            <pubDate>Wed, 25 Mar 2026 20:33:55 GMT</pubDate>
            <atom:updated>2026-03-25T20:33:55.731Z</atom:updated>
            <content:encoded><![CDATA[<p><em>[Angular] Building a Simple Dialog? 3 Key Component Design Principles I Learned from a Challenge</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*79BE_lrSyl6JZKDXxwzqVw.png" /></figure><p>Recently, while diving deep into TypeScript Utility Types — exploring the internal implementations of conditional and mapped types — I was feeling quite confident in my frontend development skills.</p><p>Then, I faced a UI component design challenge. The requirements seemed straightforward at first glance:</p><figure><img alt="Challenge Description." src="https://cdn-images-1.medium.com/max/709/1*GZRTRgLOaPfpHBeq3h_azw.png" /></figure><p>The initial dialog.component.html :</p><pre>&lt;div class=&quot;card&quot;&gt;<br>  &lt;h3 class=&quot;section&quot;&gt;Modal&lt;/h3&gt;<br>  &lt;div class=&quot;section double-padded&quot;&gt;This is a modal dialog!&lt;/div&gt;<br>  &lt;div class=&quot;section&quot;&gt;<br>    &lt;button&gt;Close&lt;/button&gt;<br>  &lt;/div&gt;<br>&lt;/div&gt;</pre><p>Initially, I thought, “I’ll just accept title text as an input, create a modal window on the screen, and make it disappear when the close button is clicked.” I approached it lightly, much like how I used to consume UI libraries like MatDialog in my daily work.</p><p>However, when I tried to implement it in a blank editor using only pure Angular and Web Standards, I realized that this challenge was not merely asking for a “floating modal window.”</p><p>It was actually a robust architectural test designed to evaluate three core competencies simultaneously:</p><ol><li><strong>View Reusability</strong> using ng-content.</li><li><strong>Component Communication Control</strong> using @ViewChild and public methods.</li><li><strong>The Ability to Utilize Pure Web Standard APIs</strong> (specifically, the native &lt;dialog&gt; tag) without relying on external libraries.</li></ol><p>This challenge was fundamentally testing a frontend developer’s ability to design common components based on these <strong>three key architectural principles</strong>. Here is a deep dive into what I learned through this process.</p><p><strong><em>View Reusability with </em></strong><strong><em>ng-content</em></strong></p><p>At first, I planned to receive all necessary texts (title, body content, button names, etc.) from the parent component via @Input(). My initial thought process looked something like this:</p><p><strong>❌ Bad: Hardcoded UI with excessive Inputs</strong></p><pre>@Component({<br>  selector: &#39;app-dialog&#39;,<br>  template: `<br>    &lt;div class=&quot;modal&quot;&gt;<br>      &lt;h2&gt;{{ title }}&lt;/h2&gt;<br>      &lt;p&gt;{{ message }}&lt;/p&gt;<br>      &lt;button (click)=&quot;submit()&quot;&gt;{{ submitText }}&lt;/button&gt;<br>      &lt;button (click)=&quot;cancel()&quot; *ngIf=&quot;showCancel&quot;&gt;{{ cancelText }}&lt;/button&gt;<br>    &lt;/div&gt;<br>  `<br>})<br>export class DialogComponent {<br>  @Input() title = &#39;&#39;;<br>  @Input() message = &#39;&#39;;<br>  @Input() submitText = &#39;OK&#39;;<br>  @Input() cancelText = &#39;Cancel&#39;;<br>  @Input() showCancel = true;<br>}</pre><p>However, this design has a critical flaw. What if the requirements change, and you need to put a complex login form instead of simple text in the body, or add a warning icon next to the confirmation button? Implementing this via @Input() would lead to endless if/else logic inside the component and a bloated number of input properties, eventually making the component unmaintainable.</p><p><strong>💡 What I Learned:</strong></p><p>Common components should only provide the “shell” (borders, dimming effects, etc.), while the “content” should be provided freely by the parent component utilizing it. This is achieved through Content Projection, or &lt;ng-content&gt;.</p><p><strong>✅ Good: Flexible shell using Content Projection</strong></p><pre>&lt;div class=&quot;dialog-shell&quot;&gt;<br>  &lt;header class=&quot;dialog-header&quot;&gt;<br>    &lt;ng-content select=&quot;[dialog-title]&quot;&gt;&lt;/ng-content&gt;<br>  &lt;/header&gt;<br>  <br>  &lt;section class=&quot;dialog-body&quot;&gt;<br>    &lt;ng-content select=&quot;[dialog-body]&quot;&gt;&lt;/ng-content&gt;<br>  &lt;/section&gt;<br>  <br>  &lt;footer class=&quot;dialog-footer&quot;&gt;<br>    &lt;ng-content select=&quot;[dialog-footer]&quot;&gt;&lt;/ng-content&gt;<br>  &lt;/footer&gt;<br>&lt;/div&gt;</pre><p>By creating slots within the dialog for the body and footer areas, I learned that I can flexibly accommodate any form of UI, whether it’s text or a complex form.</p><p><strong><em>Controlling Component Communication with </em></strong><strong><em>@ViewChild and Public Methods</em></strong></p><p>Controlling when to open and close the dialog was also a dilemma. The easiest way is to create a boolean state variable in the parent.</p><p><strong>❌ Bad: Parent tightly coupled with a boolean state</strong></p><pre>&lt;app-dialog [isOpen]=&quot;isDialogOpen&quot;&gt;&lt;/app-dialog&gt;<br>&lt;button (click)=&quot;isDialogOpen = true&quot;&gt;Open&lt;/button&gt;</pre><p>On a screen with multiple dialogs, this leads to an excessive number of boolean variables piling up in the parent component, creating tight coupling.</p><p><strong>💡 What I Learned:</strong></p><p>I realized that utilizing Inversion of Control (IoC) is a better design pattern. You declare public methods (open(), close()) inside the child component, and let the parent trigger them. Furthermore, the dialog must remain a “Dumb Component” that only emits events, rather than handling API calls itself.</p><p><strong>✅ Good: Inversion of Control &amp; Dumb Component</strong></p><pre>// --- dialog.component.ts (Child) ---<br>@Component({ selector: &#39;app-dialog&#39;, ... })<br>export class DialogComponent {<br>  @Output() confirmed = new EventEmitter&lt;void&gt;();<br>  <br>  // Public remote controls<br>  open() { /* Logic to open */ }<br>  close() { /* Logic to close */ }<br>  <br>  onConfirmClick() {<br>    // Just shout! Do not inject HttpClient or Services here.<br>    this.confirmed.emit(); <br>  }<br>}<br><br>// --- parent.component.ts (Parent) ---<br>@Component({<br>  template: `<br>    &lt;app-dialog #myDialog (confirmed)=&quot;submitQuiz()&quot;&gt;&lt;/app-dialog&gt;<br>    &lt;button (click)=&quot;myDialog.open()&quot;&gt;Open Dialog&lt;/button&gt;<br>  `<br>})<br>export class ParentComponent {<br>  // The parent handles the actual business logic<br>  submitQuiz() {<br>    // API Call logic...<br>  }<br>}</pre><p><strong><em>Utilizing Pure Web Standard APIs (</em></strong><strong><em>&lt;dialog&gt;) Without External Libraries</em></strong></p><p>To float the modal window and dim the background, my instinct was to reinvent the wheel with CSS.</p><p><strong>❌ Bad: Reinventing the wheel with CSS and divs</strong></p><pre>&lt;div class=&quot;backdrop&quot; *ngIf=&quot;isOpen&quot;&gt;&lt;/div&gt;<br>&lt;div class=&quot;modal-box&quot; [style.z-index]=&quot;999&quot; [style.position]=&quot;&#39;fixed&#39;&quot;&gt;<br>  &lt;ng-content&gt;&lt;/ng-content&gt;<br>&lt;/div&gt;</pre><p>I also worried about having to implement custom logic for Focus Trapping to prevent keyboard focus from leaving the modal for accessibility (A11y).</p><p><strong>💡 What I Learned:</strong> Before searching for complex CSS hacks or external libraries, review native Web Standard APIs. By using the HTML5 native &lt;dialog&gt; tag and calling the .showModal() method, the browser handles topmost rendering, dimming effects, and even Focus Trapping for you automatically for free. I was able to build an accessible UI without heavy external dependencies.</p><p><strong>✅ Good: Leveraging Native Web Standards</strong></p><pre>@Component({<br>  selector: &#39;app-dialog&#39;,<br>  template: `<br>    &lt;dialog #dialogElement&gt;<br>      &lt;ng-content&gt;&lt;/ng-content&gt;<br>    &lt;/dialog&gt;<br>  `<br>})<br>export class DialogComponent {<br>  @ViewChild(&#39;dialogElement&#39;) dialogRef!: ElementRef&lt;HTMLDialogElement&gt;;<br><br>  open() {<br>    // The browser automatically handles topmost rendering, dimming effects, <br>    // and Focus Trapping for accessibility!<br>    this.dialogRef.nativeElement.showModal(); <br>  }<br><br>  close() {<br>    this.dialogRef.nativeElement.close();<br>  }<br>}</pre><p><strong><em>Conclusion</em></strong></p><p>Through this challenge, I shifted my perspective away from solely focusing on “how to draw things on the screen.” I deeply understood that good UI component design is about asking, <strong>“What interface (tags, methods) should I provide to my colleagues so they can use this component naturally and flexibly?”</strong></p><p>Moving forward, when I write code, I must always keep an architectural perspective in mind, focusing on providing clear shells and remote controls.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=878c7957b999" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Angular Data Architecture: Stop Fighting Your UI (Flat Arrays vs. Normalized Dictionaries)]]></title>
            <link>https://medium.com/@dev.master0917/angular-data-architecture-stop-fighting-your-ui-flat-arrays-vs-normalized-dictionaries-a0c614909438?source=rss-dad9984c4665------2</link>
            <guid isPermaLink="false">https://medium.com/p/a0c614909438</guid>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[rxjs]]></category>
            <category><![CDATA[clean-code]]></category>
            <category><![CDATA[frontend-architecture]]></category>
            <category><![CDATA[datastrucutre]]></category>
            <dc:creator><![CDATA[Developer Joon]]></dc:creator>
            <pubDate>Sun, 22 Mar 2026 23:40:51 GMT</pubDate>
            <atom:updated>2026-03-22T23:40:51.396Z</atom:updated>
            <content:encoded><![CDATA[<p><em>Angular Data Architecture: Stop Fighting Your UI (Flat Arrays vs. Normalized Dictionaries)</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vlSbgC1KmQo94yTe0r5csw.jpeg" /></figure><p>Have you ever found yourself writing endless @if statements in your Angular templates just to render a simple dropdown or accordion? If your component is heavily burdened with filtering logic, the problem isn’t your UI skills — it’s your data structure.</p><p>Today, we’ll explore how a Senior Frontend Developer approaches data transformation using a real-world scenario: building a cascading category selector for a Quiz Maker.</p><h4><strong>The Problem: The Flat Array Trap</strong></h4><p>Imagine your backend sends you a flat array of categories like this:</p><pre>[<br>  { &quot;id&quot;: 9, &quot;name&quot;: &quot;General Knowledge&quot; },<br>  { &quot;id&quot;: 11, &quot;name&quot;: &quot;Entertainment: Film&quot; },<br>  { &quot;id&quot;: 12, &quot;name&quot;: &quot;Entertainment: Music&quot; }<br>]</pre><p>The UI requirement is a cascading dropdown: Select a Main Category (e.g., Entertainment), then select a Sub-category (e.g., Music).</p><p><strong>❌ The Junior Approach</strong></p><p>A junior developer usually keeps this flat array in the component state. When a user selects “Entertainment”, they run a .filter() on the entire array to find matching sub-categories. Furthermore, they struggle with “General Knowledge” because it has no sub-category, leading to messy if/else conditions in the HTML template to hide or show the second dropdown.</p><p><strong>✅ The Senior Approach: The 4-Step Mental Model</strong></p><p>Seniors don’t fight the UI; they mold the data to serve the UI effortlessly.</p><p><strong>Step 1: Reverse Engineer from UI</strong></p><p>If selecting a parent needs to instantly show its children, we need an O(1) lookup structure. We need a Dictionary (Hash Map).</p><p><strong>Step 2: Group By (The Record Type)</strong></p><p>We map the main category as the Key and an array of sub-categories as the Value. Record&lt;string, {id: number, name: string}[]&gt;</p><p><strong>Step 3: Normalize Exceptions</strong></p><p>What about “General Knowledge” which has no sub-categories? Instead of creating a different type, we <em>normalize </em>it. We place it inside an array just like the others, with the same name as the parent. This eliminates the need for complex @if branches in the template.</p><p><strong>Step 4: The Power of </strong><strong>reduce</strong></p><p>We transform the flat array in a single pass using JavaScript’s reduce inside an RxJS map operator.</p><p><strong>The Refactored Service Code:</strong></p><pre>import { Injectable, inject } from &#39;@angular/core&#39;;<br>import { HttpClient } from &#39;@angular/common/http&#39;;<br>import { map, Observable } from &#39;rxjs&#39;;<br><br>export interface Category { id: number; name: string; }<br>export type GroupedCategory = Record&lt;string, Category[]&gt;;<br><br>@Injectable({ providedIn: &#39;root&#39; })<br>export class QuizService {<br>  private http = inject(HttpClient);<br>  private API_URL = &#39;https://opentdb.com/&#39;;<br><br>  getAllCategories(): Observable&lt;GroupedCategory&gt; {<br>    return this.http.get&lt;{ trivia_categories: Category[] }&gt;(`${this.API_URL}api_category.php`)<br>      .pipe(<br>        // 1. Extract the array from the API response<br>        map(res =&gt; res.trivia_categories),<br>        <br>        // 2. Transform the flat array into a normalized Dictionary tree<br>        map(categories =&gt; {<br>          return categories.reduce((acc, cat) =&gt; {<br>            const parts = cat.name.split(&#39;:&#39;);<br>            const mainName = parts[0].trim();<br>            <br>            // If there&#39;s no sub-category, use the main name to normalize the data structure<br>            const subName = parts.length &gt; 1 ? parts[1].trim() : mainName;<br><br>            // Initialize the array if the key doesn&#39;t exist<br>            if (!acc[mainName]) {<br>              acc[mainName] = [];<br>            }<br><br>            // Push the child object (preserving the essential API ID)<br>            acc[mainName].push({ id: cat.id, name: subName });<br>            <br>            return acc;<br>          }, {} as GroupedCategory);<br>        })<br>      );<br>  }<br>}</pre><h4>The Result: From Spaghetti to Elegant Templates</h4><p>Let’s look at the actual HTML template to see why this data normalization matters.</p><p><strong>❌ The Junior Approach (Fighting the Data)</strong></p><p>Without a proper dictionary structure, the template is forced to handle the business logic. You’ll see DOM references (#category), messy conditional variables, and a mix of reactive and imperative state handling.</p><pre>&lt;div class=&quot;row&quot;&gt;<br>  &lt;select #category (change)=&quot;selectNewCategory(category.value)&quot;&gt;<br>    &lt;option&gt;Select category&lt;/option&gt;<br>    @for (category of categories$ | async; track category.name) {<br>      &lt;option&gt;{{ category.name }}&lt;/option&gt;<br>    }<br>  &lt;/select&gt;<br>  <br>  @let subCategories = currentCategory?.subCategories ?? [];<br>  @if (subCategories.length &gt; 0) {<br>    &lt;select #subCategory&gt;<br>      &lt;option&gt;Select sub-category&lt;/option&gt;<br>      @for (subCat of subCategories; track subCat.id) {<br>        &lt;option [value]=&quot;subCat.id&quot;&gt;{{ subCat.name }}&lt;/option&gt;<br>      }<br>    &lt;/select&gt;<br>  }<br>&lt;/div&gt;</pre><p><strong>✅ The Senior Approach (Data-Driven UI)</strong></p><p>Because we transformed our data into a Record&lt;string, Category[]&gt; and normalized the exceptions, the template becomes incredibly dumb and clean. It just reads the state and renders. No hidden logic, no DOM references.</p><pre>@if (categories$ | async; as categories) {<br>  &lt;div class=&quot;row&quot;&gt;<br>    <br>    &lt;select [(ngModel)]=&quot;selectedMainKey&quot; (ngModelChange)=&quot;onMainCategoryChange()&quot;&gt;<br>      &lt;option value=&quot;&quot;&gt;Select Category&lt;/option&gt;<br>      @for (group of categories | keyvalue; track group.key) {<br>        &lt;option [value]=&quot;group.key&quot;&gt;{{ group.key }}&lt;/option&gt;<br>      }<br>    &lt;/select&gt;<br><br>    @if (selectedMainKey() &amp;&amp; hasSubCategories(categories[selectedMainKey()], selectedMainKey())) {<br>      &lt;select [(ngModel)]=&quot;selectedSubId&quot;&gt;<br>        &lt;option value=&quot;&quot;&gt;Select Subcategory&lt;/option&gt;<br>        @for (sub of categories[selectedMainKey()]; track sub.id) {<br>          &lt;option [value]=&quot;sub.id&quot;&gt;{{ sub.name }}&lt;/option&gt;<br>        }<br>      &lt;/select&gt;<br>    }<br>  &lt;/div&gt;<br>}</pre><h4>Conclusion</h4><p>The difference is clear. When you leave your data flat or inconsistently shaped, your HTML templates become bloated with if/else statements, helper methods, and local variable hacks.</p><p>A Senior Frontend Developer knows that <strong>UI complexity is often just a symptom of poor data structures.</strong> By taking control of the data flow in your Service using RxJS map and JavaScript reduce, you keep your components lean and your templates beautiful.</p><p>Stop fighting your UI. Fix your data first.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a0c614909438" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Angular Directives: Stop Writing Fat Components (Junior vs. Senior Approach)]]></title>
            <link>https://medium.com/@dev.master0917/angular-directives-stop-writing-fat-components-junior-vs-senior-approach-032468807950?source=rss-dad9984c4665------2</link>
            <guid isPermaLink="false">https://medium.com/p/032468807950</guid>
            <category><![CDATA[frontend-architecture]]></category>
            <category><![CDATA[clean-code]]></category>
            <category><![CDATA[angular-directive]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[angular]]></category>
            <dc:creator><![CDATA[Developer Joon]]></dc:creator>
            <pubDate>Sat, 21 Mar 2026 20:42:20 GMT</pubDate>
            <atom:updated>2026-03-21T20:42:20.614Z</atom:updated>
            <content:encoded><![CDATA[<p><em>Angular Directives: Stop Writing Fat Components (Junior vs. Senior Approach)</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JVgKn6UsIadKI14srRbuTQ.jpeg" /></figure><p>If we compare Angular architecture to grammar, <strong>Components </strong>are <strong>Nouns</strong>. They represent the physical blocks on the screen: <em>“I am a button”, “I am a user profile card.” or “I am a navigation bar”.</em></p><p>Directives, on the other hand, are Adjectives and Verbs. They don’t have their own UI shell. Instead, they attach themselves to existing components or HTML tags to change how they look (Adjectives) or instruct them to perform specific actions (Verbs).</p><p>Yet, many developers treat components as a dumping ground for DOM manipulation, event listening, and business logic. Today, let’s explore how a Senior Angular Developer uses Advanced Directives to put components on a diet, using a real-world scenario: <strong>A Movie Favorite Toggle button</strong>.</p><p><strong>The Scenario</strong><br>We have a movie list. Each movie has a star icon (&lt;span&gt;). When clicked, it should toggle its color and update the backend via a FavoritesService.</p><p><em>❌ The Junior Approach: The “Fat Component”</em></p><p>A junior or mid-level developer typically focuses on “making it work on the screen”. Their thought process goes like this:</p><ul><li><em>I need to check the favorite status, so I’ll inject the service into my component”</em></li><li><em>I’ll add a click event to the span to trigger a function</em></li><li><em>I’ll bind a CSS class to change the color based on the state.</em></li></ul><p><strong>The Component (TypeScript):</strong></p><pre>@Component({<br>  selector: &#39;app-movie-item&#39;,<br>  standalone: true,<br>  templateUrl: &#39;./movie-item.component.html&#39;<br>})<br>export class MovieItemComponent {<br>  movie = input.required&lt;Movie&gt;();<br>  private favoriteService = inject(FavoritesService);<br><br>  // The component is now burdened with infrastructure logic<br>  toggleFavorite(event: Event) {<br>    event.stopPropagation();<br>    this.favoriteService.toggleFavorite(this.movie());<br>  }<br><br>  isFavorite() {<br>    return this.favoriteService.checkIsFavorite(this.movie().id);<br>  }<br>}</pre><p><strong>The Template (HTML):</strong></p><pre>&lt;div class=&quot;movie-item&quot;&gt;<br>  &lt;h4&gt;<br>    &lt;span <br>      class=&quot;icon-star&quot;<br>      [class.active]=&quot;isFavorite()&quot; <br>      (click)=&quot;toggleFavorite($event)&quot;&gt;<br>      ☆<br>    &lt;/span&gt;<br>    {{ movie().title }}<br>  &lt;/h4&gt;<br>&lt;/div&gt;</pre><p><strong>Why is this bad?</strong></p><ol><li>Violation of Separation of Concerns: The component’s job is simply to display the movie data. It shouldn’t care about how the favorite API works or how the star icon reacts to clicks.</li><li>Zero Reusability (DRY Principle): What if we want to add this exact same favorite star to a “Movie Details” page or a “Recommended Sidebar”? We would have to copy-paste the toggleFavorite logic and the HTML bindings everywhere.</li></ol><p><em>✅ The Senior Approach: The “Directive Diet”</em></p><p>A senior developer looks at this and thinks: <em>“Toggling a favorite state based on a click and changing a CSS class is purely DOM behavior. This belongs in an Attribute Directive.”</em></p><p>By extracting this logic into a custom directive, we achieve extreme reusability and decouple the logic from the UI. Furthermore, we will leverage Angular 18’s <strong>Signals </strong>to ensure a flawless reactive architecture without duplicating state.</p><ol><li><strong>The Custom Directive (The “Verb”)</strong></li></ol><p>Instead of using the older @HostBinding , modern Angular 18 code prefers declaring host bindings directly in the @Directive decorator metadata for cleaner code.</p><pre>import { Directive, inject, input, computed, HostListener } from &#39;@angular/core&#39;;<br>import { FavoritesService } from &#39;./favorites.service&#39;;<br>import { Movie } from &#39;./movie.model&#39;;<br><br>@Directive({<br>  selector: &#39;[appToggleFavoriteMovie]&#39;,<br>  standalone: true,<br>  // 💡 Modern Angular: Bind classes and properties directly in the host object<br>  host: {<br>    &#39;[class.active]&#39;: &#39;isActive()&#39; <br>  }<br>})<br>export class ToggleFavoriteMovieDirective {<br>  private favoriteService = inject(FavoritesService);<br><br>  // 1. Accept the movie data via Signal Input (replaces @Input)<br>  movie = input.required&lt;Movie&gt;({ alias: &#39;appToggleFavoriteMovie&#39; });<br><br>  // 2. Derive the state directly from the Service (Single Source of Truth)<br>  // We do NOT create a new signal(false) here. We compute based on the source.<br>  isActive = computed(() =&gt; {<br>    const allFavs = this.favoriteService.favorites(); // Reactive dependency<br>    return allFavs.some(fav =&gt; fav.id === this.movie().id);<br>  });<br><br>  // 3. Listen to DOM events directly within the directive<br>  @HostListener(&#39;click&#39;, [&#39;$event&#39;])<br>  onClick(event: Event) {<br>    event.stopPropagation(); // Prevent bubbling up to parent elements<br>    <br>    // Command the service to update the truth<br>    this.favoriteService.toggleFavorite(this.movie());<br>  }<br>}</pre><p>2. <strong>The Refactored Component (The “Noun”)</strong></p><p>Look at how beautifully simple the component becomes. The TypeScript file now has zero logic regarding the favorites feature.</p><p><strong>The Component (TypeScript)</strong></p><pre>import { Component, input } from &#39;@angular/core&#39;;<br>import { Movie } from &#39;./movie.model&#39;;<br>import { ToggleFavoriteMovieDirective } from &#39;./toggle-favorite-movie.directive&#39;;<br><br>@Component({<br>  selector: &#39;app-movie-item&#39;,<br>  standalone: true,<br>  imports: [ToggleFavoriteMovieDirective], // Directive handles everything<br>  templateUrl: &#39;./movie-item.component.html&#39;<br>})<br>export class MovieItemComponent {<br>  movie = input.required&lt;Movie&gt;();<br>  // 💡 No services injected, no click handlers, no state variables. Pure UI.<br>}</pre><p><strong>The Template (HTML)</strong></p><pre>&lt;div class=&quot;movie-item&quot;&gt;<br>  &lt;h4&gt;<br>    &lt;span <br>      class=&quot;icon-star&quot;<br>      [appToggleFavoriteMovie]=&quot;movie()&quot;&gt;<br>      ☆<br>    &lt;/span&gt;<br>    {{ movie().title }}<br>  &lt;/h4&gt;<br>&lt;/div&gt;</pre><p><strong>Key Takeaways</strong></p><ul><li><strong>The Component Diet:</strong> Components should focus on <em>“What to display.”</em> Directives should handle <em>“How it behaves and looks.”</em></li><li><strong>Infinite Reusability:</strong> You can now drop &lt;button [appToggleFavoriteMovie]=&quot;movie()&quot;&gt; or &lt;div [appToggleFavoriteMovie]=&quot;movie()&quot;&gt; anywhere in your massive enterprise application, and it will instantly wire up the click events, the service calls, and the active CSS classes.</li><li><strong>Mastering Reactive State:</strong> Notice how the Directive doesn’t maintain its own boolean variable for the favorite status. It uses computed() to listen to the FavoritesService. When the service updates the state, the directive&#39;s isActive signal reacts, and Angular automatically toggles the .active class on the host element.</li></ul><p>Next time you find yourself writing (click) and [ngClass] on a tiny UI element while injecting heavily business-logic services into your component, ask yourself: <em>&quot;Should this be a Directive?&quot;</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=032468807950" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Redux is Dead, Just Use Signals?” Why Your Trivial Angular Service Won’t Scale.]]></title>
            <link>https://medium.com/@dev.master0917/redux-is-dead-just-use-signals-why-your-trivial-angular-service-wont-scale-8a91dda08cde?source=rss-dad9984c4665------2</link>
            <guid isPermaLink="false">https://medium.com/p/8a91dda08cde</guid>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[ngrx]]></category>
            <category><![CDATA[frontend-development]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[state-management]]></category>
            <dc:creator><![CDATA[Developer Joon]]></dc:creator>
            <pubDate>Fri, 13 Mar 2026 13:47:29 GMT</pubDate>
            <atom:updated>2026-03-13T13:50:13.743Z</atom:updated>
            <content:encoded><![CDATA[<p><em>Redux is Dead, Just Use Signals?” Why Your Trivial Angular Service Won’t Scale.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*In1EC8I44j_q4zO1mQq4EA.png" /></figure><blockquote>“I’ve never been a fan of NgRx and Redux. I don’t like state in the frontend; that’s what the backend and DB are for. If you absolutely must hold state — like a shopping cart — I can just build a trivial service, store the cart with a signal(), and sync it with sessionStorage. NgRx SignalStore is just a desperate attempt to keep the Store concept alive in Angular”</blockquote><p>A 15-year senior colleague recently dropped this comment.</p><p>And honestly? I completely understand where he is coming from. If you have been in the industry long enough, you likely have some level of Redux PTSD. We are all tired of the cognitive load and endless boilerplate — dispatching an Action, checking an Effect, updating a Reducer, and writing a Selector just to toggle a loading spinner.</p><p>With the introduction of Signals in Angular 16+, many developers shared this exact sentiment:</p><blockquote>“Redux is dead. I’ll just build a simple service with Signals.”</blockquote><p>However, navigating complex Angular frontend architectures for B2B enterprise clients in Germany over the past couple of years has taught me otherwise. While tackling these enterprise challenges and validating my expertise through Angular Level 1 and 2 certifications (and currently preparing for Level 3), I’ve realized that this “trivial service” mindset is a massive trap.</p><p>Here is way relying on pure Signals for feature-level state will eventually break your architecture, and why NgRx SignalStore is not “Redux on life support,” but the exact standardization your team needs.</p><p><strong>Part 1: The Trap of the “Trivial” Signal Service</strong></p><p>Let’s look at the exact approach the senior colleague suggested. It looks incredibly clean and intuitive at first glance.</p><pre>import { Injectable, signal, effect } from &#39;@angular/core&#39;;<br><br>export interface CartItem { id: string; name: string; quantity: number; }<br><br>@Injectable({ providedIn: &#39;root&#39; })<br>export class TrivialCartService {<br>  // 1. Manually check sessionStorage on initialization<br>  private savedCart = sessionStorage.getItem(&#39;b2b-cart&#39;);<br>  <br>  // 2. Initialize the signal<br>  cart = signal&lt;CartItem[]&gt;(this.savedCart ? JSON.parse(this.savedCart) : []);<br><br>  constructor() {<br>    // 3. Manually sync every change back to sessionStorage<br>    effect(() =&gt; {<br>      sessionStorage.setItem(&#39;b2b-cart&#39;, JSON.stringify(this.cart()));<br>    });<br>  }<br><br>  addItem(item: CartItem) {<br>    this.cart.update(items =&gt; [...items, item]);<br>  }<br>}</pre><p>If you are building a solo toy project, stop reading here. This code is perfectly fine. But in a large-scale enterprise application maintained by multiple developers, this “trivial” approach introduces severe architectural flaws.</p><p><strong>Part 2: When “Trivial” Fails in an Enterprise Team</strong></p><ol><li>The Wild West of Mutations</li></ol><p>In a trivial service, there are no rules. Developer A might update the state using this.cart.update(). Developer B might mutate a nested object directly (breaking reactivity). Developer C might expose the WritableSignal directly to the component, allowing the UI to mutate the state bypassing the service entirely. Without a standardized contract, your Single Source of Truth becomes compromised.</p><p>2. Boilerplate Duplication</p><p>What happens when your app grows? You now have a UserSettingsService, an ActiveOrderService, and a ThemeService that all need persistence. Are you going to copy-paste the sessionStorage.getItem and effect() serialization logic into 20 different services?</p><p>3. SSR &amp; Hydration Crashes</p><p>If you are using Angular 17+ with Server-Side Rendering (SSR) or Hydration, the trivial service above will instantly crash your application. The sessionStorage object does not exist in the Node.js server environment.</p><p>Accessing it directly will throw a ReferenceError. Handling this requires injecting PLATFORM_ID, checking isPlatformBrowser, and wrapping your logic. Doing this manually across dozens of services is exactly the kind of boilerplate we want to avoid.</p><p><strong>Part 3: NgRx SignalStore: Standardization, Not Redux</strong></p><p>This is where the colleague misunderstood the purpose of the SignalStore. SignalStore is not Redux. It completely discards Actions, Reducers and Effects.</p><p>Instead, it provides a lightweight, functional container to standardize State, Computed values, and Methods. More importantly, it offers an incredibly powerful plugin system called signalStoreFeature.</p><p>Instead of repeating the sessionStorage logic everywhere, a senior developer abstracts it into a single, highly reusable feature.</p><p><strong>Step 1: Write the Reusable Storage Feature (Once)</strong></p><pre>import { effect, inject, PLATFORM_ID } from &#39;@angular/core&#39;;<br>import { isPlatformBrowser } from &#39;@angular/common&#39;;<br>import { signalStoreFeature, withHooks, getState, patchState } from &#39;@ngrx/signals&#39;;<br><br>export function withSessionStorage&lt;State extends object&gt;(key: string) {<br>  return signalStoreFeature(<br>    withHooks({<br>      onInit(store, platformId = inject(PLATFORM_ID)) {<br>        // Safe execution: Only run in the browser to prevent SSR crashes<br>        if (isPlatformBrowser(platformId)) {<br>          const savedState = sessionStorage.getItem(key);<br>          if (savedState) {<br>            patchState(store, JSON.parse(savedState));<br>          }<br><br>          // Automatically sync any state changes to sessionStorage<br>          effect(() =&gt; {<br>            sessionStorage.setItem(key, JSON.stringify(getState(store)));<br>          });<br>        }<br>      }<br>    })<br>  );<br>}</pre><p><strong>Step 2: Snap it together like Lego</strong></p><p>Now, look at how we build our CartStore.</p><pre>import { signalStore, withState, withMethods, patchState } from &#39;@ngrx/signals&#39;;<br>import { withSessionStorage } from &#39;./features/with-session-storage&#39;;<br>import { CartItem } from &#39;./cart.model&#39;;<br><br>type CartState = { items: CartItem[]; total: number };<br>const initialState: CartState = { items: [], total: 0 };<br><br>export const CartStore = signalStore(<br>  { providedIn: &#39;root&#39; },<br>  withState(initialState),<br>  <br>  // Custom business logic<br>  withMethods((store) =&gt; ({<br>    addItem(item: CartItem) {<br>      patchState(store, (state) =&gt; ({ items: [...state.items, item] }));<br>    }<br>  })),<br><br>  // 🔥 One line to add safe, automatic SessionStorage synchronization!<br>  withSessionStorage(&#39;b2b-cart-state&#39;)<br>);</pre><p><strong>Conclusion: The Sweet Spot for Angular 18+</strong></p><p>The colleague was right to challenge the necessity of heacy state management in the frontend. We shouldn’t blindly reach for Redux just because an application is “large.”</p><p>However, building custom “trivial services” for every feature is a step backward. It leads to fragmented codebases, duplicated side-effect logic, and a nightmare for onboarding new developers.</p><p>NgRx SignalStore hits the perfect sweet spot. It gives you the lightness and reactivity of pure Signals, but enforces a clean, declarative architectural standard. It allows you to abstract complex side effect (like SSR-safe storage syncing or RxJS API polling) into elegant, reusable plugins.</p><p>Stop building trivial services. Embrace standardization.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8a91dda08cde" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[[Angular] Junior vs. Senior Form Design: Mastering ControlValueAccessor]]></title>
            <link>https://medium.com/@dev.master0917/angular-junior-vs-senior-form-design-mastering-controlvalueaccessor-9e5b73c7b20e?source=rss-dad9984c4665------2</link>
            <guid isPermaLink="false">https://medium.com/p/9e5b73c7b20e</guid>
            <category><![CDATA[best-practices]]></category>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[frontend-development]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[typescript]]></category>
            <dc:creator><![CDATA[Developer Joon]]></dc:creator>
            <pubDate>Tue, 10 Mar 2026 22:14:51 GMT</pubDate>
            <atom:updated>2026-03-10T22:14:51.696Z</atom:updated>
            <content:encoded><![CDATA[<p><em>[Angular] Junior vs. Senior Form Design: Mastering ControlValueAccessor</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*w7F7FGphZmmWBp-_FytI-A.jpeg" /></figure><p>In enterprise frontend development, forms are much more than simple text inputs. They are complex state management machines that handle dynamic field generation, intricate business validation rules, and real-time state tracking (errors, touched states, value changes).</p><p>Today, let’s explore the difference between a junior’s manual state-syncing nightmare and a senior’s elegant, declarative architecture using Angular’s Reactive Forms.</p><p><strong><em>The Junior Approach: Manual State Management</em></strong></p><p>Imagine a new requirement: <strong><em>“Create an order form where users can input their name, dynamically add/remove multiple products, and adjust the quantity using a custom UI component.”</em></strong></p><p>Without fully grasping Angular’s Reactive Forms architecture, developers often fall back on manually ping-ponging states between the template (HTML) and the component (TS).</p><p><strong>❌ The Anti-Pattern</strong></p><pre>@Component({<br>  template: `<br>    &lt;input [(ngModel)]=&quot;customerName&quot; (blur)=&quot;checkName()&quot; [class.red-border]=&quot;isNameError&quot;&gt;<br>    @if(isNameError) { &lt;span class=&quot;error&quot;&gt;Name is required.&lt;/span&gt; }<br><br>    @for (item of items; track $index) {<br>      &lt;div&gt;<br>        &lt;input [(ngModel)]=&quot;item.name&quot;&gt;<br>        <br>        &lt;app-counter <br>          [count]=&quot;item.quantity&quot; <br>          (countChange)=&quot;updateQuantity($index, $event)&quot;&gt;<br>        &lt;/app-counter&gt;<br>        <br>        @if (item.quantity &lt; 1) { &lt;span class=&quot;error&quot;&gt;Quantity must be at least 1.&lt;/span&gt; }<br>      &lt;/div&gt;<br>    }<br>    <br>    &lt;button (click)=&quot;submit()&quot;&gt;Submit Order&lt;/button&gt;<br>  `<br>})<br>export class OrderComponent {<br>  customerName = &#39;&#39;;<br>  isNameError = false; // Redundant state flag<br>  items = [{ name: &#39;&#39;, quantity: 0 }];<br><br>  checkName() {<br>    this.isNameError = this.customerName.trim() === &#39;&#39;;<br>  }<br><br>  updateQuantity(index: number, newCount: number) {<br>    this.items[index].quantity = newCount;<br>  }<br><br>  submit() {<br>    // Imperative validation checks at the time of submission<br>    if (!this.customerName) { this.isNameError = true; return; }<br>    if (this.items.some(item =&gt; item.quantity &lt; 1)) { <br>      alert(&#39;Check quantities.&#39;); return; <br>    }<br>    // Proceed with API call...<br>  }<br>}</pre><p><strong>🚨 Why This Fails in Production</strong></p><ol><li>No Single Source of Truth (SSOT): The form’s data and validity are scattered across multiple variables (customerName, isNameError, items)</li><li>Fighting the Framework: Angular automatically attaches CSS classes like .ng-touched and .ng-invalid to form controls. Creating custom flags like isNameError is reinventing the wheel.</li><li>Maintenance Hell: Business rules (e.g. minimum quantity) are duplicated in the @if block and the submit() function. A single requirement change requires updates in multiple places.</li></ol><p><strong><em>The Senior Approach: Reactive Forms &amp; Declarative Architecture</em></strong></p><p>A senior developer looks at the same requirement and thinks differently. They declare <strong>what</strong> the data structure and validation rules should be, and delegate the state tracking entirely to the Angular Forms engine.</p><p><strong>✅ The Best Practice (Angular 18)</strong></p><pre>import { Component, inject } from &#39;@angular/core&#39;;<br>import { NonNullableFormBuilder, ReactiveFormsModule, Validators } from &#39;@angular/forms&#39;;<br>import { minQuantityValidator } from &#39;./validators&#39;;<br>import { CounterComponent } from &#39;./counter.component&#39;;<br><br>@Component({<br>  selector: &#39;app-order&#39;,<br>  standalone: true,<br>  imports: [ReactiveFormsModule, CounterComponent],<br>  template: `<br>    &lt;form [formGroup]=&quot;orderForm&quot; (ngSubmit)=&quot;submit()&quot;&gt;<br>      <br>      &lt;input formControlName=&quot;customerName&quot; placeholder=&quot;Name&quot;&gt;<br><br>      &lt;div formArrayName=&quot;items&quot;&gt;<br>        @for (item of itemsControl.controls; track $index) {<br>          &lt;div [formGroupName]=&quot;$index&quot;&gt;<br>            &lt;input formControlName=&quot;name&quot;&gt;<br>            <br>            &lt;app-counter formControlName=&quot;quantity&quot;&gt;&lt;/app-counter&gt;<br>          &lt;/div&gt;<br>        }<br>      &lt;/div&gt;<br><br>      &lt;button type=&quot;submit&quot; [disabled]=&quot;orderForm.invalid&quot;&gt;Submit Order&lt;/button&gt;<br>    &lt;/form&gt;<br>  `<br>})<br>export class OrderComponent {<br>  private fb = inject(NonNullableFormBuilder);<br><br>  // A centralized, strictly-typed SSOT<br>  orderForm = this.fb.group({<br>    customerName: [&#39;&#39;, Validators.required],<br>    items: this.fb.array([ this.createItem() ])<br>  });<br><br>  get itemsControl() {<br>    return this.orderForm.get(&#39;items&#39;) as FormArray;<br>  }<br><br>  private createItem() {<br>    return this.fb.group({<br>      name: [&#39;&#39;],<br>      quantity: [0, minQuantityValidator] // Business logic is encapsulated here<br>    });<br>  }<br><br>  submit() {<br>    // If the button is enabled, the data is guaranteed to be valid.<br>    console.log(&#39;Final Payload:&#39;, this.orderForm.getRawValue());<br>  }<br>}</pre><p>The real magic here is the &lt;app-counter formControlName=&quot;quantity&quot;&gt;&lt;/app-counter&gt;. How does a custom UI component sync bi-directionally without @Input or @Output? The answer is the ControlValueAccessor.</p><p><strong><em>The Ultimate Weapon: ControlValueAccessor (CVA) Deep Dive</em></strong></p><p>To the Angular Forms API, your custom star-rating widgets, toggle switches, or custom quantity counters are just unrecognized HTML tags. To integrate them seamlessly into the Angular ecosystem, you need a “translator.” That translator is the ControlValueAccessor interface.</p><p>To master CVA, you must understand the flow or its four core methodes:</p><p>① writeValue(val): The Input (Model ➔ View)</p><p>When the parent form initializes or updates the value (e.g., patchValue), Angualr calls this methods. It pushes the current value of the FormControl into zour custom component. Your job is to take this value and update your internal UI state.</p><p>② registerOnChange(fn): The Output Callback</p><p>Do not invoke this manually. When the component initialiyes, Angular passes a callback function (fn) into this method. Angular is essentially saying,</p><blockquote>“Save this function. Whenever the user changes the value via your UI, call this function with the new value so I can update the parent form”</blockquote><p>③ registerOnTouched(fn): The Touch State Callback</p><p>Similar to registerOnChange, Angular provides a callback to track the touched state. When a user interacts with your component and leaves (e.g., on a blur or click event), you call this saved function. This is what triggers Angular to append the .ng-touched CSS class.</p><p><strong><em>🛠️ Implementing CVA: The Standard Blueprint</em></strong></p><p>Here is how you build a bulletproof custom form control.</p><pre>import { Component, forwardRef } from &#39;@angular/core&#39;;<br>import { ControlValueAccessor, NG_VALUE_ACCESSOR } from &#39;@angular/forms&#39;;<br><br>@Component({<br>  selector: &#39;app-counter&#39;,<br>  standalone: true,<br>  template: `<br>    &lt;button (click)=&quot;decrement()&quot; [disabled]=&quot;isDisabled&quot;&gt;-&lt;/button&gt;<br>    &lt;span&gt;{{ value }}&lt;/span&gt;<br>    &lt;button (click)=&quot;increment()&quot; [disabled]=&quot;isDisabled&quot;&gt;+&lt;/button&gt;<br>  `,<br>  // Crucial: Registering this component as a CVA provider<br>  providers: [{<br>    provide: NG_VALUE_ACCESSOR,<br>    useExisting: forwardRef(() =&gt; CounterComponent),<br>    multi: true<br>  }]<br>})<br>export class CounterComponent implements ControlValueAccessor {<br>  value = 0;<br>  isDisabled = false;<br><br>  // Placeholders for the callbacks injected by Angular<br>  onChange = (val: number) =&gt; {};<br>  onTouched = () =&gt; {};<br><br>  // --- UI Event Handlers ---<br>  increment() {<br>    this.value++;<br>    this.onChange(this.value); // Notify Angular of the value change<br>    this.onTouched();          // Notify Angular of the interaction<br>  }<br><br>  decrement() {<br>    this.value--;<br>    this.onChange(this.value);<br>    this.onTouched();<br>  }<br><br>  // --- CVA Interface Implementation ---<br>  writeValue(val: number): void {<br>    this.value = val; // Update the UI when the parent form changes<br>  }<br><br>  registerOnChange(fn: any): void {<br>    this.onChange = fn; // Save the value-change callback<br>  }<br><br>  registerOnTouched(fn: any): void {<br>    this.onTouched = fn; // Save the touched-state callback<br>  }<br><br>  setDisabledState(isDisabled: boolean): void {<br>    this.isDisabled = isDisabled; // Handle disable/enable triggers from the parent<br>  }<br>}</pre><p>Conclusion</p><p>Mastering a framework means understanding and leveraging its ecosystem, not fighting it. Stop tracking form states with imperative variables and arrays. Centralize your logic in a FormGroup, encapsulate your custom UIs with ControlValueAccessor, and watch your codebase become infinitely more maintainable and robust.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9e5b73c7b20e" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Mastering Angular Templates: From @Input Hell to Scalable UI Components]]></title>
            <link>https://medium.com/@dev.master0917/mastering-angular-templates-from-input-hell-to-scalable-ui-components-d4ffb888ee9d?source=rss-dad9984c4665------2</link>
            <guid isPermaLink="false">https://medium.com/p/d4ffb888ee9d</guid>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[clean-code]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[frontend]]></category>
            <category><![CDATA[software-architecture]]></category>
            <dc:creator><![CDATA[Developer Joon]]></dc:creator>
            <pubDate>Sun, 08 Mar 2026 20:24:32 GMT</pubDate>
            <atom:updated>2026-03-08T20:24:32.194Z</atom:updated>
            <content:encoded><![CDATA[<p><em>Mastering Angular Templates: From </em><em>@Input Hell to Scalable UI Components</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*A-QiuqsbKzWt5rcWLm0hlw.jpeg" /></figure><p>If you’ve been working with Angular, you’ve probably experienced this: you build a simple, clean UI component. But soon, product requirements grow. You are asked to add an icon here, a link there, and a few conditional buttons. Before you know it, your component is bloated with dozens of @Input() properties, and the template is an unmaintainable maze of *ngIf statements.</p><p>To build truly scalable, enterprise-grade Angular UI components (like those in a robust Design System), you must break free from @Input() dependency. In this article, we will explore the three pillars of advanced Angular templates: ng-content, ng-container, andng-template.</p><p><strong><em>ng-content: The Art of Content Projection</em></strong></p><p>At its core, ng-content allows a parent component to inject its own HTML content inside a child component. The child component acts strictly as a “shell” (providing layout and styling), while the parent dictates the actual content.</p><p>When should you use ng-content?</p><p>You should reach for content projection if any of these three criteria are met:</p><ol><li>The child component only provides layout, styling, or behavior, leaving the content up to the parent.</li><li>The content is not a simple string, but an arbitrary combination of DOM elements and components.</li><li>You anticipate a massive explosion of @Input() properties (e.g., title, subtitle, icon, actions) as requirements expand.</li></ol><p><strong>The Good, The Bad, and The Scalable</strong></p><p>Let’s look at real-world scenario to understand why this matters.</p><p>Case: The Card Component (Multi-slot Projection)</p><p>Here is what a typical junior approach looks like when building a Card component without ng-content. You rely heavily on inputs and outputs, and the layout is strictly hardcoded.</p><pre>// ❌ BAD: No scalability (Input Hell)<br>@Component({<br>  selector: &#39;app-card&#39;,<br>  template: `<br>    &lt;div class=&quot;card&quot;&gt;<br>      &lt;h3 class=&quot;title&quot;&gt;{{ title }}&lt;/h3&gt;<br>      &lt;p class=&quot;desc&quot;&gt;{{ description }}&lt;/p&gt;<br>      &lt;div class=&quot;actions&quot;&gt;<br>        &lt;button (click)=&quot;primaryClick.emit()&quot;&gt;{{ primaryText }}&lt;/button&gt;<br>        &lt;button *ngIf=&quot;secondaryText&quot; (click)=&quot;secondaryClick.emit()&quot;&gt;{{ secondaryText }}&lt;/button&gt;<br>      &lt;/div&gt;<br>    &lt;/div&gt;<br>  `<br>})<br>export class CardComponent {<br>  @Input() title = &#39;&#39;;<br>  @Input() description = &#39;&#39;;<br>  @Input() primaryText = &#39;OK&#39;;<br>  @Input() secondaryText?: string;<br>  @Output() primaryClick = new EventEmitter&lt;void&gt;();<br>  @Output() secondaryClick = new EventEmitter&lt;void&gt;();<br>}</pre><p>If requirements change (e.g., adding an icon next to the title, inserting a link in the desccription, or adding a third button), your @Input() list will grow endlessly, and your template will be forced to accept only strings or rigid options.</p><p>Instead, let the Card act only as the layout shell by using ng-content with the select attribute.</p><p><strong>Take a look at the image below.</strong></p><p>In the CardComponent (the child shell), we define specific slots using the select attribute, such as &lt;ng-content select=&quot;[card-title]&quot;&gt;. When you use this component in the parent, Angular looks for any element matching the CSS selector. As illustrated, the &lt;div card-title&gt; from the parent is automatically projected directly into that exact header slot. The same precision applies to the &lt;div card-actions&gt;, which is routed perfectly to the footer.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FRlbJhKbMvH5-Ti9bo4oIA.png" /></figure><p><strong>Understanding the “Default Slot” and Angular’s Matching Rules</strong></p><p>But looking closer at the CardComponent template, you might notice one &lt;ng-content&gt; tag completely missing the select attribute. What exactly does it do? It acts as the <strong>Default Slot</strong> (or catch-all slot).</p><p>When Angular projects content, it doesn’t just throw things in randomly. It follows a strict 3-step execution rule:</p><ol><li><strong>Match explicit selectors</strong>: Fist, Angular scans the provided content and projets the elements that explicitly match the select attributes (in our case, [card-title] and [card-actions]).</li><li><strong>Gather the leftovers</strong>: It then collects all the remaining elements that did not match any selector.</li><li><strong>Project to the default</strong>: Finally, it takes all those leftover elements and projects them into the single &lt;ng-content&gt; tag that does not have a select attribute.</li></ol><p>Because of this rule, &lt;p&gt; tag containing the error description and the hyperlink automatically falls straight in to the .body div of the card. You don’t need to wrap it in a special &lt;div card-body&gt; tag. This makes the parent component’s HTML much cleaner and incredibly flexible.</p><p><strong><em>ng-container: The Invisible Logical Wrapper</em></strong></p><p>The ng-container is a logical grouping tool that does not render as an actual element in the DOM. It solves three major headaches in Angular development:</p><p><strong>Preventing CSS Layout Breakage</strong></p><p>If you add a wrapper &lt;div&gt; just to use *ngIf, that extra element becomes a child node, which might ruin your CSS Flexbox or Grid layouts. Wrapping your code in &lt;ng-container *ngIf=&quot;user&quot;&gt; ensures the logic applies without generating an unwanted DOM node.</p><pre>&lt;div class=&quot;flex-container&quot;&gt;<br>  &lt;div *ngIf=&quot;user&quot;&gt;<br>    &lt;div *ngFor=&quot;let item of items&quot;&gt;{{ item }}&lt;/div&gt;<br>  &lt;/div&gt;<br>&lt;/div&gt;<br><br>&lt;div class=&quot;flex-container&quot;&gt;<br>  &lt;ng-container *ngIf=&quot;user&quot;&gt;<br>    &lt;div *ngFor=&quot;let item of items&quot;&gt;{{ item }}&lt;/div&gt;<br>  &lt;/ng-container&gt;<br>&lt;/div&gt;</pre><p><strong>Avoiding Structural Directives Collisions</strong></p><p>Angular strictly forbids placing more than one structural directive (like *ngIf and *ngFor) on the same element. You can elegantly bypass this by placing one directive on an ng-container and the other on the inner element.</p><pre>&lt;div *ngIf=&quot;user&quot; *ngFor=&quot;let item of items&quot;&gt;<br>  {{ item }}<br>&lt;/div&gt;<br><br>&lt;ng-container *ngIf=&quot;user&quot;&gt;<br>  &lt;div *ngFor=&quot;let item of items&quot;&gt;<br>    {{ item }}<br>  &lt;/div&gt;<br>&lt;/ng-container&gt;</pre><p><strong>Template Readability (Async Pipe &amp; Logical Grouping)</strong></p><p>It provides a clean way to group logic, such as extracting an observable stream using &lt;ng-container *ngIf=&quot;vm$ | asznc as vm&quot;&gt; without adding unnecessary wrapper divs.</p><pre>&lt;ng-container *ngIf=&quot;vm$ | async as vm&quot;&gt;<br>  &lt;app-header [title]=&quot;vm.title&quot;&gt;&lt;/app-header&gt;<br>  &lt;app-body [data]=&quot;vm.data&quot;&gt;&lt;/app-body&gt;<br>&lt;/ng-container&gt;</pre><p><strong><em>ng-template: The Dynamic View Blueprint</em></strong></p><p>While a standard &lt;div&gt; renders into the DOM immediately, an ng-tempalte is merely a blueprint of a view. By default, it renders absolutely nothing. Its definition and instantiation are strictly seperated; Angular only creates the Embedded View when explicitly instructed.</p><p><strong>The Ultimate Use Case: Generic List Components</strong></p><p>Imagine you need a UserList, a ProjectList, and an OrderList. They all share complex logic like pagination, virtual scrolling, and empty states, but their UI representations are completely different.</p><p>You generally have two ways to approach this:</p><ol><li><strong>The Junior Approach:</strong> Build completely separate components (UserListComponent, ProjectListComponent, OrderListComponent).</li><li><strong>The Senior Approach:</strong> Build a single, shared ListComponent to handle the behavior, and let the parent components (UserComponent, ProjectComponent, etc.) dictate the UI by passing down an ng-template.</li></ol><p><strong>Why the Shared List + </strong><strong>ng-template Patterns Wins</strong></p><p>Using this pattern brings three massive architectural benefits:</p><ol><li>Implement Core Logic Only Once: You only write compley list behaciors once. This includes pagination, infinite scrolling, loading/empty/error states, virtural scrolling (CDK), trackBy optimizations, keyboard navigation, and ARIA accessibility.</li><li>Drastically Reduce Maintenance Costs: You only change the UI per screen, while the core list behavior is maintained in one single component. If you have identical lists with different UI representations across your app, the maintenance effort drops significantly.</li><li>API Stability: If a new requirement comes in (e.g., adding a checkbox selection feature), zou only update the shared ListComponent. If zou had seperate components, you would have refactor the code in three different places.</li></ol><p>The Implementation</p><p>Let’s look at the shared ListComponent. It loops through the items and uses ngTemplateOutlet to render the blueprint passed by the parent.</p><pre>&lt;div *ngFor=&quot;let item of items; index as i&quot;&gt;<br>  &lt;ng-container <br>    [ngTemplateOutlet]=&quot;itemTemplate ?? defaultTpl&quot; <br>    [ngTemplateOutletContext]=&quot;{ $implicit: item, index: i }&quot;&gt;<br>  &lt;/ng-container&gt;<br><br>  &lt;ng-template #defaultTpl let-x&gt;<br>    {{ x | json }}<br>  &lt;/ng-template&gt;<br>&lt;/div&gt;</pre><p>Notice how we define a defaultTpl as a fallback. If itemTemplate exists, it renders the parent’s UI; if not, it safely falls back to displaying the raw JSON.</p><p>Take a look at the image below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/818/1*STjBE65jBR-ll21aCcHnLw.png" /></figure><p>In the ListComponent , we set up a generic rendering socket using [ngTemplateOutlet]=&quot;itemTemplate&quot;. The list component doesn&#39;t know what this template looks like; it simply waits for the parent to provide it.</p><p>The magic happens in the ParentComponent. Let&#39;s break down the flow:</p><ol><li>Passing the Template: When wie declare the &lt;app-list&gt;, we use property binding to pass our specific template:[itemTemplate]=&quot;userTpl&quot;.</li><li>Defining the Blueprint: But what exactly is userTpl? Right below the list, we define the actual UI structure inside an &lt;ng-template #userTpl&gt; block.</li><li>The Connection: The #userTpl is a Tmeplate Reference Variable. By defining it here, the parent creates a reference to this chunk of HTML and hands it over to thd child’s itemTemplate input.</li></ol><p>Essentially, the parent is telling the child:</p><blockquote>“Here is the ecact HTML blueprint (#userTpl). Take it, and use it to render every single item in your loop”</blockquote><p><strong><em>Conclusion</em></strong></p><p>Transitioning from a junior to a senior Angular developer requires a fundamental shift in how you think about component architecture. Junior developers often treat components as simple functions, passing every little detail through @Input() properties until the code becomes brittle and impossible to maintain.</p><p>By mastering the template trinity — decoupling layout from content using ng-content, keeping your DOM clean and logical with ng-container, and passing highly reusable view blueprints via ng-template—you stop writing rigid, hardcoded components. Instead, you start building flexible, scalable UI ecosystems.</p><p>Stop fighting the framework with endless inputs and complex *ngIf conditions. Start projecting, wrapping, and templating the Angular way!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d4ffb888ee9d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Stop Using canActivate and snapshot: Advanced Routing for Senior Developers]]></title>
            <link>https://medium.com/@dev.master0917/stop-using-canactivate-and-snapshot-advanced-routing-for-senior-developers-e5a4b0162855?source=rss-dad9984c4665------2</link>
            <guid isPermaLink="false">https://medium.com/p/e5a4b0162855</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[clean-code]]></category>
            <dc:creator><![CDATA[Developer Joon]]></dc:creator>
            <pubDate>Fri, 06 Mar 2026 21:47:51 GMT</pubDate>
            <atom:updated>2026-03-06T21:47:51.769Z</atom:updated>
            <content:encoded><![CDATA[<p><em>Stop Using </em><em>canActivate and </em><em>snapshot: Advanced Routing for Senior Developers</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-RVLKtqZ3K09vs9lnk8pCw.jpeg" /></figure><p>Setting up routing in an Angular project might seem like a straightforward task at first glance. But what happens when you are dealing with a large-scale enterprise application that combines complex Role-Based Access Control (Guards), data pre-fetching (Resolvers), and Lazy-loading?</p><p>Today, I’d like to share my experience through a real-world scenario: building a <strong>“B2B Partner Dashboard.”</strong> We will explore three critical routing mistakes junior developers often make and how to refactor them into a bulletproof, senior-level architecture.</p><h3>📝 The Scenario (User Story)</h3><p>Let’s look at the acceptance criteria for our dashboard:</p><ul><li><strong>Security:</strong> Users without the ‘ADMIN’ role must be completely blocked, meaning they shouldn’t even be able to download the JavaScript chunk for this route.</li><li><strong>Data Pre-fetching:</strong> To prevent UI flickering, partner data must be fetched from the API <em>before</em> the dashboard component renders.</li><li><strong>Optimization:</strong> This heavy dashboard module must be lazy-loaded only when accessed.</li></ul><p>Looks simple enough, right? But if you implement this using the most common approaches, your app might be exposed to silent bugs and security flaws. Let’s dive in.</p><h3>🚨 Problem 1: The Fatal Flaw of canActivate with Lazy-loading</h3><p>When asked to restrict route access, the first instinct is often to use the canActivate guard.</p><pre>// ❌ Junior&#39;s Way: Using canActivate with Lazy-loading<br>const routes: Routes = [<br>  {<br>    path: &#39;partners/:id&#39;,<br>    canActivate: [authGuard], // Blocks navigation if unauthorized<br>    loadComponent: () =&gt; import(&#39;./partner-dashboard.component&#39;).then(m =&gt; m.Component)<br>  }<br>];</pre><p>This code works perfectly on a local dev server. If an unauthorized user tries to access the route, they are redirected.</p><p>However, if you open the Network tab in production, you’ll discover a shocking truth: canActivate <strong>does not prevent the browser from downloading the lazy-loaded JavaScript chunk.</strong> If a malicious user manually navigates to /partners/123 , the Angular router downloads partner-dashboard.component.js in the background before checking canActivate . This is both a security loophole and a waste of bandwidth.</p><h4>💡 The Senior Solution: Use canMatch</h4><p>When dealing with lazy-loaded routes, you must use canMatch . It evaluates whether the route should even be matched in the first place, completely aborting the chunk download if the condition fails.</p><pre>// ✅ Senior&#39;s Way: Blocking at the matching phase<br>export const adminOnlyGuard: CanMatchFn = () =&gt; {<br>  const auth = inject(AuthService);<br>  const router = inject(Router);<br>  <br>  // Elegantly redirect to an unauthorized page without downloading the chunk<br>  return auth.hasRole(&#39;ADMIN&#39;) || router.createUrlTree([&#39;/unauthorized&#39;]);<br>};<br><br>// app.routes.ts<br>const routes: Routes = [<br>  {<br>    path: &#39;partners/:id&#39;,<br>    canMatch: [adminOnlyGuard], // 🔥 Evaluates before anything else<br>    loadComponent: () =&gt; import(&#39;./partner-dashboard.component&#39;)<br>  }<br>];</pre><h3>🚨 Problem 2: Silent Resolver Errors Destroying UX</h3><p>To fetch data before navigation, we use Resolvers.</p><pre>// ❌ Junior&#39;s Way: Resolver without Error Handling<br>export const partnerResolver: ResolveFn&lt;any&gt; = (route) =&gt; {<br>  const id = route.paramMap.get(&#39;id&#39;);<br>  return inject(HttpClient).get(`/api/partners/${id}`); <br>};</pre><p>What happens if a user navigates to a deleted partner ID (/partners/999) and the API returns a 404 Not Found ?</p><p><strong>The Angular Router will silently cancel the navigation.</strong> The user clicks the link, nothing happens, and the screen freezes. They will simply assume your application is broken. This is a terrible user experience.</p><h4>💡 The Senior Solution: Defensive catchError</h4><p>API calls should be delegated to a dedicated Service, and you must explicitly handle error scenarios within the resolver.</p><pre>// ✅ Senior&#39;s Way: Safe Resolver with Fallback Navigation<br>export const partnerDataResolver: ResolveFn&lt;Partner&gt; = (route) =&gt; {<br>  const router = inject(Router);<br>  <br>  return inject(PartnerService).getPartnerById(route.paramMap.get(&#39;id&#39;)!).pipe(<br>    catchError((err) =&gt; {<br>      console.error(&#39;Failed to fetch partner data&#39;, err);<br>      // 🔥 Redirect to a safe fallback page on error<br>      router.navigate([&#39;/partners/error&#39;]);<br>      return EMPTY; // Complete the observable to resolve the guard<br>    })<br>  );<br>};</pre><h3>🚨 Problem 3: The snapshot Trap (Component State Bug)</h3><p>Once the resolver fetches the data, how do you read it in the component? Usually, it’s done via ActivatedRoute.snapshot inside ngOnInit.</p><pre>// ❌ Junior&#39;s Way: Reading state via snapshot<br>ngOnInit() {<br>  this.id = this.route.snapshot.paramMap.get(&#39;id&#39;);<br>  this.partnerData = this.route.snapshot.data[&#39;partnerData&#39;];<br>}</pre><p>This introduces a massive bug if the user navigates from /partners/123 directly to /partners/456 using an internal link.</p><p>Because Angular reuses the same component instance for identical routes, ngOnInit will not run again. The URL changes to 456, but the screen is permanently stuck displaying data for 123.</p><h4>💡 The Senior Solution: withComponentInputBinding &amp; Signals</h4><p>Leverage Angular 16+’s withComponentInputBinding() in your router providers. This allows you to ditch the cumbersome ActivatedRoute entirely and receive route parameters and resolved data directly via modern Signal input()s.</p><pre>// ✅ Senior&#39;s Way: Reactive State Management with Signals<br>import { Component, input, computed } from &#39;@angular/core&#39;;<br><br>@Component({ ... })<br>export default class PartnerDashboardComponent { <br>  // Route params and resolved data are automatically bound as inputs!<br>  readonly id = input.required&lt;string&gt;();<br>  readonly partnerData = input.required&lt;Partner&gt;();<br><br>  // Derived state updates automatically when the input changes<br>  readonly title = computed(() =&gt; `Partner Overview: ${this.partnerData().companyName}`);<br>}</pre><p><em>(Pro-tip: By using </em><em>export default class, you can even omit the </em><em>.then(m =&gt; m.Component) part in your </em><em>loadComponent route configuration, keeping your routes file incredibly clean.)</em></p><h3>Conclusion: Keep Your Routes File as a Clean Map</h3><p>A senior developer’s design philosophy goes beyond just knowing the syntax. It starts with asking: “Will this code crash under edge cases? Will it be maintainable a year from now?”</p><p>Your routing file (app.routes.ts) should be a clean, readable map of your application. By strictly separating the complex logic of Guards and Resolvers and embracing modern Angular features like canMatch and Signal inputs, you can drastically improve the security and stability of your application.</p><p>How are you handling your Angular routes? Let me know in the comments!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e5a4b0162855" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Why Your Angular Components Are Tightly Coupled (And How to Fix It)]]></title>
            <link>https://medium.com/@dev.master0917/why-your-angular-components-are-tightly-coupled-and-how-to-fix-it-a0df65a44a15?source=rss-dad9984c4665------2</link>
            <guid isPermaLink="false">https://medium.com/p/a0df65a44a15</guid>
            <category><![CDATA[clean-code]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[frontend-development]]></category>
            <category><![CDATA[typescript]]></category>
            <dc:creator><![CDATA[Developer Joon]]></dc:creator>
            <pubDate>Thu, 05 Mar 2026 18:49:10 GMT</pubDate>
            <atom:updated>2026-03-05T19:10:53.973Z</atom:updated>
            <content:encoded><![CDATA[<p><em>Why Your Angular Components Are Tightly Coupled (And How to Fix It)</em></p><figure><img alt="Angular Architecture Decoupling with InjectionToken Tutorial Thumbnail" src="https://cdn-images-1.medium.com/max/1024/1*CFRP3k-xdLV2EikvrqzQFg.jpeg" /></figure><p>If you’ve been working with Angular for a while, you probably use inject(MyService) daily. Services are easy to inject because classes exist at runtime in JavaScript.</p><p>But what happens when you need to inject something that <em>isn’t</em> a class? Like a simple string, an environment configuration object, or an interface?</p><p>TypeScript interfaces disappear after compilation. If you try to inject an interface, Angular’s Dependency Injection (DI) container will throw an error because the identifier simply doesn’t exist in memory at runtime.</p><p>This is exactly where InjectionToken comes to the rescue. It acts as a physical, runtime anchor for things that don’t inherently have one.</p><p>Let’s look at a real-world disaster scenario I encountered, and how InjectionToken elegantly solved it.</p><h4><strong>The Disaster: The “Shared” Breadcrumb Component</strong></h4><p>Imagine you are on the core library team. You are tasked with building a standalone BreadcrumbComponent that will be used across the entire company.</p><p>Without overthinking it, you might build it by extracting a hardcoded string key &#39;breadcrumb&#39; from the Angular router data.</p><pre>// ❌ The Bad Way: Tightly Coupled Component<br>import { Component, inject, OnInit } from &#39;@angular/core&#39;;<br>import { ActivatedRoute } from &#39;@angular/router&#39;;<br><br>@Component({<br>  selector: &#39;app-breadcrumbs&#39;,<br>  standalone: true,<br>  template: `&lt;div&gt;Current Location: {{ label }}&lt;/div&gt;`<br>})<br>export class BreadcrumbComponent implements OnInit {<br>  private route = inject(ActivatedRoute);<br>  label = &#39;Home&#39;;<br><br>  ngOnInit() {<br>    // 🚨 Problem: Hardcoded magic string &#39;breadcrumb&#39;<br>    const routeData = this.route.snapshot.data;<br>    if (routeData[&#39;breadcrumb&#39;]) {<br>      this.label = routeData[&#39;breadcrumb&#39;];<br>    }<br>  }<br>}</pre><p>Other teams start using it like this:</p><pre>// app.routes.ts<br>export const routes: Routes = [<br>  {<br>    path: &#39;user&#39;,<br>    data: { breadcrumb: &#39;User Profile&#39; }, // Works perfectly<br>    component: UserComponent<br>  }<br>];</pre><p>Everything works fine — until the <strong>Admin Team</strong> pings you.</p><blockquote>“Hey, we already built our entire routing structure using the key pageTitle instead of breadcrumb. Your component isn’t showing anything for our routes. Can you update your component to support our key too?”</blockquote><p>If you don’t know about InjectionToken,you might be tempted to ruin your clean component with messy fallback logic:</p><pre>// 🤮 Breaking the Open-Closed Principle<br>this.label = routeData[&#39;breadcrumb&#39;] || routeData[&#39;pageTitle&#39;];</pre><p>Next week, the Payment Team asks you to support navLabel.Your component becomes a mess of if/else statements, constantly being modified for external reasons.</p><p><strong>The Senior Developer’s Fix: Inversion of Control</strong></p><p>Instead of the component dictating which string to look for, we should pass the control to the DI container. The component shouldn’t care what the key is; it should only care about retrieving it.</p><p>Let’s refactor this using an InjectionToken.</p><ol><li>Define the Token</li></ol><pre>// breadcrumb.token.ts<br>import { InjectionToken } from &#39;@angular/core&#39;;<br><br>export const BREADCRUMB_KEY = new InjectionToken&lt;string&gt;(&#39;breadcrumb.key&#39;, {<br>  providedIn: &#39;root&#39;,<br>  factory: () =&gt; &#39;breadcrumb&#39; // Fallback default value<br>});</pre><p>2. Update the Component</p><p>Now, we inject the token to get the key dynamically.</p><pre>// ✅ The Good Way: Decoupled and Reusable<br>import { Component, inject, OnInit } from &#39;@angular/core&#39;;<br>import { ActivatedRoute } from &#39;@angular/router&#39;;<br>import { BREADCRUMB_KEY } from &#39;./breadcrumb.token&#39;;<br><br>@Component({<br>  selector: &#39;app-breadcrumbs&#39;,<br>  standalone: true,<br>  template: `&lt;div&gt;Current Location: {{ label }}&lt;/div&gt;`<br>})<br>export class BreadcrumbComponent implements OnInit {<br>  private route = inject(ActivatedRoute);<br>  <br>  // 💡 Inject the token instead of hardcoding the string<br>  private dataKey = inject(BREADCRUMB_KEY); <br>  <br>  label = &#39;Home&#39;;<br><br>  ngOnInit() {<br>    const routeData = this.route.snapshot.data;<br>    if (routeData[this.dataKey]) {<br>      this.label = routeData[this.dataKey];<br>    }<br>  }<br>}</pre><p>3. The Magic: Overriding the Token</p><p>Now, you can tell the Admin Team: <em>“I didn’t change the component logic. Just provide the token with your specific key at your route level.”</em></p><pre>// admin.routes.ts<br>import { Routes } from &#39;@angular/router&#39;;<br>import { BREADCRUMB_KEY } from &#39;../shared/breadcrumb.token&#39;;<br><br>export const adminRoutes: Routes = [<br>  {<br>    path: &#39;admin&#39;,<br>    // 🔥 Override the token value for this specific route branch!<br>    providers: [<br>      { provide: BREADCRUMB_KEY, useValue: &#39;pageTitle&#39; }<br>    ],<br>    children: [<br>      {<br>        path: &#39;dashboard&#39;,<br>        data: { pageTitle: &#39;Admin Dashboard&#39; }, <br>        component: AdminDashboardComponent<br>      }<br>    ]<br>  }<br>];</pre><p><strong>Other Powerful Use Cases for InjectionToken</strong></p><p>Decoupling magic strings is just the beginning. InjectionToken is an essential tool for scalable architecture:</p><ol><li>Environment Configurations: Injecting a pure config object ({apiUrl: &#39;...&#39;, production: true} ) makes your services independent of the physical environment.ts file, making Unit Testing incredibly easy.</li><li>SSR-Safe Global Objects: Directly calling window.location will crash an Angular Universal (SSR) app. Creating a token for Window allows you to inject a mock object on the server and the real window in the browser.</li><li>Interface Polymorphism: You can inject a Logger interface. The component just calls logger.log(),while the DI container decides wheter to provide a ConsoleLogger (for dev) or a ServerLogger (for production).</li></ol><p><strong>Conclusion</strong></p><p>Using InjectionToken is the line that separates a junior developer writing script-like code from an engineer designing an architecture.</p><p>It completely breaks the tight coupling between a component and its execution environment. The component declares, “I don’t care where this value comes from, just give me what is registered under this label.” Master this, and your Angular applications will become infinitely more modular, testable, and robust.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a0df65a44a15" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Why I’m Trying to Escape My €53k Salary: From a 12-Week Bootcamp to a 4th-Year Dev in Germany]]></title>
            <link>https://medium.com/@dev.master0917/why-im-trying-to-escape-my-53k-salary-from-a-12-week-bootcamp-to-a-4th-year-dev-in-germany-fd1263204c08?source=rss-dad9984c4665------2</link>
            <guid isPermaLink="false">https://medium.com/p/fd1263204c08</guid>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[codingbootcamp]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[career-advice]]></category>
            <category><![CDATA[germany]]></category>
            <dc:creator><![CDATA[Developer Joon]]></dc:creator>
            <pubDate>Thu, 05 Mar 2026 12:00:14 GMT</pubDate>
            <atom:updated>2026-03-05T12:00:14.007Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/6/1*IFvd8hCFg8ULIF77LQW-Yg.png" /></figure><p><em>Why I’m Trying to Escape My €53k Salary: From a 12-Week Bootcamp to a 4th-Year Dev in Germany</em></p><p>I’ve been working as a software developer in Germany since December 2022.</p><p>My bachelor’s and master’s degrees had nothing to do with Computer Science. After getting my master’s, I joined a consulting firm in Munich and worked in data processing for three years. Then, for certain reasons, I decided to switch careers to software engineering and quit my job.</p><p>Right after resigning, I completed a 12-week coding bootcamp. Thanks to some good timing, I landed a junior developer role immediately after finishing the course.</p><p>At the bootcamp, I learned Java, Spring Boot, TypeScript, React, and MongoDB. I thought it was a lot. I thought, “Ah, so this is what web development is, and this is how it probably works in the real world.”</p><p>That illusion was completely shattered the moment I joined the company.</p><p>The project I was assigned to was built on Nest.js, GraphQL, Microservices, React, Jenkins, and more. Out of that massive pile of unfamiliar tech stacks, the only thing I recognized was React. It was a project that brutally made me realize, “I literally know nothing.”</p><p>But a company is not a bootcamp. I had to survive. I tackled problems head-on, constantly throwing “stupid” questions at my colleagues. Eventually, I caught the eye of a senior developer who declared, “I only need two juniors on this team. The rest are unnecessary.” (At the time, there were about 6 or 7 juniors in that team). I managed to be one of those “two.”</p><p>One, two, and three years passed. I had two salary increases, and my starting salary of €48k became €53k.</p><p>You might think that’s a decent raise. Honestly, I didn’t think much of it either. But recently, I discovered my true market value and what my current experience is actually worth.</p><p>So, I’m planning to make a massive jump from €53k.</p><p>In this space, I will share my raw survival guide as a developer in Germany. I’ll document the trials and errors I face while applying modern stacks like Angular 18 to real-world projects.</p><p>I want to share the lessons I learned the hard way. I hope my stories can help developers who share similar struggles, or those without a CS degree who feel lost while dreaming of getting a tech job abroad.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fd1263204c08" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>