Wednesday, December 31, 2025

Angular State Management & RxJS

State Management & RxJS


1️⃣ Where Should State Live? (Component vs Service)

๐Ÿง  Golden Rule

If more than one thing needs the data → it is state → move it out of the component

Component State (Local, UI-only)

  • Dropdown open/close

  • Selected row

  • Loading spinner for one view

isOpen = false;
selectedId?: number;

Service / Facade State (Shared / Long-lived)

  • API data

  • WebSocket data

  • Filters, selections shared across components

@Injectable()
export class ControlStateService {
  private ordersSubject = new BehaviorSubject<Order[]>([]);
  orders$ = this.ordersSubject.asObservable();
}

Wrong

@Component()
export class ControlComponent {
  orders: Order[] = [];

  ngOnInit() {
    this.http.get(...).subscribe(o => this.orders = o);
  }
}

๐Ÿ“Œ This dies when:

  • Component is destroyed

  • Another component needs same data


2️⃣ Observable vs Subject vs BehaviorSubject (VERY IMPORTANT)

๐ŸŸข Observable

  • Read-only

  • No manual .next()

๐Ÿ“Œ Preferred for exposing the state  but no one should modify it.

// service private ordersSubject = new BehaviorSubject<Order[]>([]); orders$: Observable<Order[]> = this.ordersSubject.asObservable();

// component orders$ = this.orderService.orders$; // ✅ read-only

๐Ÿ“Œ Key idea

  • Component can read

  • Component cannot call .next()

  • Prevents accidental state mutation

๐Ÿ‘‰ This is the preferred public API for state

๐ŸŸก Subject

  • Event stream

  • No stored value

  • Misses emissions if a late subscriber

// service or component
refresh$ = new Subject<void>();
// emit event
this.refresh$.next();
// listen to event
this.refresh$.subscribe(() => {
  console.log('Refresh triggered');
});

๐Ÿ“Œ Key idea

  • No stored value

  • Late subscribers miss previous events

  • Perfect for clicks, refresh, submit, reload

๐Ÿ“Œ Use for actions, not state.


๐Ÿ”ต BehaviorSubject

  • Has current value

  • Emits immediately on subscribe

private ordersSubject = new BehaviorSubject<Order[]>([]);

// update state
this.ordersSubject.next([...this.ordersSubject.value, newOrder]);
 
// read current value (sync)
const current = this.ordersSubject.value;

๐Ÿ“Œ Key idea

  • Always has a value

  • New subscribers get latest value immediately

  • Ideal for application state

๐Ÿ“Œ Perfect for the state

๐Ÿง  Rule (Memorize)

TypePurposeStores value?    Late subscriber
ObservableRead-only state         ❌       ❌
SubjectEvent/action    ❌         ❌
BehaviorSubject        State    ✅        ✅

3️⃣ Core Operators (MUST Master)


๐Ÿ” switchMap Cancel Previous Request

Use when:

  • Search

  • Filters

  • Route params

this.search$
  .pipe(
    switchMap(term => this.api.search(term))
  )
  .subscribe();

๐Ÿ“Œ Automatically cancels old requests.


๐Ÿ”€ mergeMapRun in Parallel

Use when:

  • Fire-and-forget

  • Multiple independent requests

mergeMap(order => this.api.save(order))

Dangerous for UI-triggered actions.


takeUntilCleanup Subscriptions

Use in components only.

private destroy$ = new Subject<void>();

ngOnInit() {
  this.facade.orders$
    .pipe(takeUntil(this.destroy$))
    .subscribe();
}

ngOnDestroy() {
  this.destroy$.next();
  this.destroy$.complete();
}

๐Ÿ“Œ Prevents memory leaks.


4️⃣ Avoid Nested Subscriptions (CRITICAL)

Worst Pattern (Messy Async)

this.api.getUser().subscribe(user => {
  this.api.getOrders(user.id).subscribe(orders => {
    this.api.getPayments(orders[0].id).subscribe(...);
  });
});

❌ Unreadable
❌ Impossible to cancel
❌ Error handling nightmare


Refactored (Clean & Confident)

this.api.getUser().pipe(
  switchMap(user => this.api.getOrders(user.id)),
  switchMap(orders => this.api.getPayments(orders[0].id))
).subscribe();

pipe()
is where you describe how data should flow and change — without executing it yet.
  1. Get the user
  2. then switch to getting orders
  3. then switch to getting payments
  4. then subscribe onc
switchMap as: “When I get a value, start a new request and automatically cancel the previous on

๐Ÿ“Œ One stream
๐Ÿ“Œ One error path
๐Ÿ“Œ One unsubscribe


5️⃣ Async Refactor Pattern 

Before

loadData() {
  this.loading = true;

  this.api.getA().subscribe(a => {
    this.api.getB(a.id).subscribe(b => {
      this.data = b;
      this.loading = false;
    });
  });
}

After

data$ = this.api.getA().pipe(
  switchMap(a => this.api.getB(a.id)),
  finalize(() => this.loading = false)
);

Template:

<div *ngIf="data$ | async as data">

๐Ÿ“Œ Component becomes declarative.


6️⃣ Service Pattern (State Done Right)

control-state.service.ts

@Injectable()
export class ControlStateService {
  private ordersSubject = new BehaviorSubject<Order[]>([]);
  orders$ = this.ordersSubject.asObservable();

  loadOrders() {
    this.api.getOrders().subscribe(o => this.ordersSubject.next(o));
  }
}

control-page.component.ts

orders$ = this.controlStateService.orders$;
ngOnInit() { this.facade.loadOrders(); }

๐Ÿ“Œ Components don’t care how data arrives.


7️⃣ Error Handling the Right Way

Wrong

.subscribe(
  data => {},
  err => this.error = err
);

Correct

pipe(
  catchError(err => {
    this.error = err;
    return EMPTY;
  })
)

๐Ÿ“Œ Keeps stream alive
๐Ÿ“Œ No broken UI


8️⃣ Async Pipe > Manual Subscribe

❌ Wrong

this.facade.orders$.subscribe(o => this.orders = o);

✅ Correct

orders$ = this.facade.orders$;
<tr *ngFor="let o of orders$ | async">

๐Ÿ“Œ Auto unsubscribe
๐Ÿ“Œ Cleaner code


9️⃣ Smell Tests (Memorize These)

SmellMeaning
subscribe inside subscribe❌ wrong
subject exposed publicly❌ wrong
state stored in the component❌ fragile
mergeMap for UI clicks❌ dangerous
manual unsubscribe everywhere❌ bad design

 

Streams describe time.
State lives outside components.
Components only react.

If you can:

  • Read async code top → bottom

  • Delete a component without breaking state

  • Replace nested subscribes with operators

 


๐Ÿ”Ÿ State Lifetime Rules (Very Important)

Ask how long this data should live?

LifetimeWhereExample
One component render    Component     modal open flag
While route active    Facade     orders list
Across navigation    App service     logged-in user
Real-time updates    Service + stream     WebSocket data
UI derived values    Computed / map     count, filtered list, button enable

๐Ÿ“Œ If state survives navigation → component is wrong place


1️⃣2️⃣ Derived State Must NOT Be Stored

Wrong

this.totalPrice = this.items.reduce(...)

Correct (Derived)

totalPrice$ = this.items$.pipe(
  map(items => items.reduce(...))
);

Or with signals:

totalPrice = computed(() =>
  this.items().reduce(...)
);

๐Ÿ“Œ Only store source of truth


1️⃣3️⃣ Avoid Boolean Explosion

Bad

isLoading;
hasError;
isEmpty;

Better

state$ = of<'loading' | 'loaded' | 'error'>('loading');

Or signal:

state = signal<'loading' | 'loaded' | 'error'>('loading');

1️⃣4️⃣ Side Effects Must Be Isolated

Side effects:

  • API calls

  • Navigation

  • Toasts

  • Logging

Wrong

map(data => {
  this.router.navigate(...)
  return data;
})

✅ Correct

tap(() => this.router.navigate(...))

๐Ÿ“Œ map = transform
๐Ÿ“Œ tap = side effects


๐ŸŸข SIGNALS — PRACTICAL & THEORY (Angular 16+)


1️⃣5️⃣ What Signals Are (Simple Truth)

Signals are synchronous, local, pull-based state

RxJS:

  • async

  • streams over time

Signals:

  • sync

  • value right now


1️⃣6️⃣ When Signals Are PERFECT

Use signals for:

  • UI state

  • Derived state

  • Local feature state

  • Change detection simplicity

count = signal(0);

increment() {
  count.update(c => c + 1);
}

1️⃣7️⃣ computed() — Derived State Done Right

items = signal<Item[]>([]);

total = computed(() =>
  this.items().reduce((a, b) => a + b.price, 0)
);

๐Ÿ“Œ No subscriptions
๐Ÿ“Œ Auto recalculation
๐Ÿ“Œ No memory leaks


1️⃣8️⃣ effect() — Controlled Side Effects

effect(() => {
  if (this.total() > 1000) {
    console.log('High value order');
  }
});

⚠️ Rules:

  • Don’t update signals inside effect unless intentional

  • ❌ Don’t fetch APIs blindly


1️⃣9️⃣ Signals + RxJS Together (REAL WORLD)

You will NOT replace RxJS.

Example: API → signal

orders = signal<Order[]>([]);

loadOrders() {
  this.api.getOrders().subscribe(o => this.orders.set(o));
}

Or RxJS → signal automatically

orders = toSignal(this.api.getOrders());

๐Ÿ“Œ RxJS = async source
๐Ÿ“Œ Signal = state holder


๐Ÿ”ฅ MESSY REAL CODE → LIVE RXJS REFACTOR


❌ Messy Real Code (Seen in Many Projects)

ngOnInit() {
  this.route.params.subscribe(params => {
    this.loading = true;

    this.api.getUser(params['id']).subscribe(user => {
      this.api.getOrders(user.id).subscribe(orders => {
        this.api.getPayments(orders[0].id).subscribe(payments => {
          this.payments = payments;
          this.loading = false;
        });
      });
    });
  });
}

Problems:

  • 4 nested subscriptions ❌

  • No cancellation ❌

  • Memory leaks ❌

  • Impossible to read ❌


✅ Step-by-Step Refactor (CONFIDENTLY)

Step 1: Flatten

this.route.params.pipe(
  switchMap(p => this.api.getUser(p['id'])),
  switchMap(user => this.api.getOrders(user.id)),
  switchMap(orders => this.api.getPayments(orders[0].id))
)

Step 2: Move to service

payments$ = this.service.payments$;

Service:

payments$ = this.route.params.pipe(
  switchMap(p => this.api.getUser(p['id'])),
  switchMap(u => this.api.getOrders(u.id)),
  switchMap(o => this.api.getPayments(o[0].id)),
  shareReplay(1)
);

Step 3: Async pipe

<div *ngIf="payments$ | async as payments">

✔ No manual subscription
✔ Auto cleanup
✔ Readable


๐ŸŸฃ SIGNALS VERSION (Same Logic)

payments = toSignal(
  this.route.params.pipe(
    switchMap(p => this.api.getUser(p['id'])),
    switchMap(u => this.api.getOrders(u.id)),
    switchMap(o => this.api.getPayments(o[0].id))
  ),
  { initialValue: [] }
);

Template:

<div *ngFor="let p of payments()">

⚔️ SIGNALS vs RXJS — HONEST COMPARISON

TopicSignalsRxJS
Sync state✅ bestawkward
Async streams nobest
UI derived stateperfect๐Ÿ˜
API orchestration no
Cancellation
Learning curveEasySteep
Large-scale data flow

๐Ÿ”‘ Golden Rule

RxJS for time.
Signals for state.

They are complements, not competitors.


๐Ÿšจ Common Signal Mistakes (Avoid These)

Using signals for HTTP polling
❌ Replacing all Observables
❌ Effects of doing business logic
❌ Storing backend DTOs directly
❌ Global signals everywhere


Lernings 

  • Refactor nested subscribes without fear

  • Explain switchMap vs mergeMap in 1 sentence

  • Decide signal vs observable instantly

  • Keep components subscription-free

  • Use effects only for side effects



Angular Component Architecture Best Practices

Component Architecture, focused on how to judge a component and say “this responsibility is wrong”.


Component Architecture (Deep, Practical)

1️⃣ Smart vs Dumb Components (Container vs Presentational)

๐Ÿ”น Smart Components (Container)

Responsibilities

  • Fetch data (API, store, websocket)

  • Hold business logic

  • Decide what happens

Should NOT

  • Handle HTML-heavy UI logic

  • Format display strings

  • Know CSS/layout details

Example

@Component(...)
export class UserPageComponent {
  users$ = this.userService.getUsers();

  onUserSelected(userId: number) {
    this.router.navigate(['/users', userId]);
  }
}

๐Ÿ”น Dumb Components (Presentational)

Responsibilities

  • Display data

  • Emit user actions

Should NOT

  • Call services

  • Know routes

  • Contain business rules

@Component(...)
export class UserListComponent {
  @Input() users: User[];
  @Output() select = new EventEmitter<number>();
}

Rule:
If you can reuse it in another app → it should be dumb.


2️⃣ Single Responsibility per Component

A component should change for only ONE reason

Bad Smells

  • Component handles:

    • Data fetching

    • Validation

    • Formatting

    • Navigation

    • State caching

// ๐Ÿšซ God Component
export class OrderComponent {
  loadOrders() {}
  validateOrder() {}
  calculateTax() {}
  formatDate() {}
  navigateToPayment() {}
}

Correct Split

  • OrderPageComponent → orchestration

  • OrderListComponent → UI

  • OrderService → business logic

  • OrderMapper → formatting / transformation


3️⃣ Inputs / Outputs Discipline (VERY IMPORTANT)

Anti-pattern

constructor(private orderService: OrderService) {}
someMethod() {
  this.orderService.updateOrder(this.order);
}

Inside a child UI component ❌

Problems:

  1. The component now knows too much — it knows where data comes from and how to update it.

  2. Harder to reuse — what if you want the same component in another page that uses a different service?

  3. Harder to test — you now need to mock the service every time you test the component.

✅ Correct

@Input() orders: Order[];  // Input = data from parent
@Output() orderClicked = new EventEmitter<Order>();  // Output = events to parent
<div *ngFor="let order of orders" (click)="orderClicked.emit(order)">
  {{ order.title }}
</div>

Mental Rule

  • Inputs = data

  • Outputs = events

  • No services inside dumb components

    • Never injected in dumb UI components — business logic stays outside.


4️⃣ Avoiding God Components

A God Component:

  • 500 lines

  • 5+ services injected

  • 10+ methods

  • Handles multiple screens or modes

๐Ÿ” Quick Smell Test

Ask:

  • “Can I extract a section into its own component?”

  • “Is this method actually UI-related?”

  • “Does this logic belong to a service?”

If YES → component is already too big.


5️⃣ The “Responsibility Test” (Memorize This)

When you look at any component, ask:

QuestionIf YES → Problem
Is it fetching data?Should be a container
Is it formatting values?Move to pipe/mapper
Is it validating rules?Move to service
Is it navigating?Only parent allowed
Is it handling multiple views?Split component

๐Ÿ‘‰ If a component answers YES to more than 2 → it’s wrong


6️⃣ Practical Refactor Pattern (Real-World)

Before

engagement.component.ts
  • WebSocket handling

  • Form logic

  • Table rendering

  • Action buttons

  • State management

After

engagement-page.component.ts   (smart)
engagement-form.component.ts   (dumb)
engagement-table.component.ts  (dumb)
engagement.actions.ts          (logic)
engagement.mapper.ts           (formatting)

This pattern fits Angular 14–19, works with RxJS, Material, and Signals.


7️⃣ Final Rule You Should Live By

If you delete the HTML, the TS file should almost feel useless

If TS still feels “powerful” → logic is in the wrong place.



8️⃣ “Where does this logic belong?” (Decision Table)

When you see logic, classify it instantly:

Logic TypeBelongs In
API callsSmart Component / Facade
WebSocket handlingFacade / Service
Business rulesService
Value formattingPipe / Mapper
UI enable/disableDumb Component
State coordinationContainer
Side effectsEffect / Service

❌ Wrong

if (order.amount > 100000 && user.role === 'ADMIN') {
  this.buttonDisabled = false;
}

✅ Correct

this.canApprove$ = this.orderRules.canApprove(order, user);

9️⃣ “No If-Else Hell in Components”

Bad Smell

onAction(type: string, data: Order) {
  if (type === 'CREATE') {
    this.createOrder(data);
  } else if (type === 'UPDATE') {
    this.updateOrder(data);
  } else if (type === 'DELETE') {
    this.deleteOrder(data);
  }
}

Replace With Strategy

const handlers = { CREATE: (data: Order) => this.createOrder(data), UPDATE: (data: Order) => this.updateOrder(data), DELETE: (data: Order) => this.deleteOrder(data), };

onAction(type: string, data: Order) { handlers[type]?.(data); }

๐Ÿ“Œ Rule:

If you see 3+ condition branches → move logic out.


๐Ÿ”Ÿ Component Size Limits (Hard Rule)

FileMax
Component TS       150–200 lines
Template HTML       150 lines
Methods        ≤ 5 public methods

If exceeded → split immediately.


1️⃣1️⃣ ViewModel Pattern  

Instead of passing raw API models:

❌ Bad

interface OrderApiResponse {
  id: number;
  customerName: string;
  orderDate: string; // in ISO format
  status: string;
  permissions: { canEdit: boolean };
}

@Input() order: OrderApiResponse;

<h1>{{ order.customerName.toUpperCase() }}</h1>
<p>{{ (order.orderDate | date:'shortDate') }}</p>
<button *ngIf="order.permissions.canEdit">Edit</button>

❌ Problems:

  • Your template now contains logic (formatting, transformations).

  • If the API changes (like orderDate format), your component breaks.

  • The component is not reusable — it depends on the API shape.

  • Raw API = raw vegetables straight from the farm. You have to cook them inside the component (template).

✅ Good

interface OrderViewModel {
  title: string;        // already formatted
  canEdit: boolean;     // extracted permission
  displayDate: string;  // already formatted for UI
}

@Input() vm: OrderViewModel;

<h1>{{ vm.title }}</h1>
<p>{{ vm.displayDate }}</p>
<button *ngIf="vm.canEdit">Edit</button>

✅ Benefits:

  1. Dumb components stay dumb — no logic in the template.

  2. No formatting logic — all transformation happens outside.

  3. Easier to test — the component only displays data.

  4. Stable UI — if the API changes, only the ViewModel mapping needs updating.

  5. ViewModel = pre-cut, pre-cooked vegetables ready to eat. Component just serves them.


1️⃣2️⃣ No Mutation Inside Components

❌ Wrong

this.orders.push(newOrder);
Here, this.orders is the same array object as before, but with one extra item. Angular’s OnPush change detection may not detect this change because the reference of this.
orders hasn’t changed — only its content has.

✅ Correct

this.orders = [...this.orders, newOrder];
[...] is the spread operator, which creates a new array.
Now this.orders is a new object reference, so Angular’s OnPush will detect it and update the UI properly.

๐Ÿ“Œ Mutation hides bugs and breaks OnPush.


1️⃣3️⃣ Push Change Detection by Default

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush
})

By default, Angular checks everything all the time.

Angular will re-check the component ONLY if:

  1. An @Input reference changes

  2. An event happens inside the component (click, input, etc.)

  3. An observable emits (via async pipe)

  4. You manually call markForCheck()

If component breaks with OnPush → it had wrong responsibility.

Why do components “break” with OnPush?

A component “breaks” when:
  • Data changes

  • UI does NOT update

Typical reason
this.orders.push(newOrder); // mutation

Why?

  • Reference didn’t change

  • Angular thinks: “Nothing changed”

  • UI doesn’t refresh

4️⃣ Wrong responsibilities (very common)

❌ Component doing business logic

this.orders.push(...)
this.orders.sort(...)
this.orders[0].status = 'DONE';

❌ Component pulling data itself

constructor(private orderService: OrderService) {}

❌ Component transforming raw API data

get displayDate() {
  return new Date(this.order.createdAt).toLocaleString();
}

 Correct responsibilities (OnPush-friendly)

✅ Component should:

  • Receive immutable data via @Input

  • Render UI

  • Emit events

  • Subscribe via async pipe

@Input() orders!: OrderViewModel[];
@Output() orderClicked = new EventEmitter<OrderViewModel>();

<div *ngFor="let order of orders" (click)="orderClicked.emit(order)">
  {{ order.title }}
</div>
  • No mutation. No logic. No magic.


1️⃣4️⃣ Services Are NOT Dumping Grounds

❌ Bad Service

export class AppService {
  formatDate() {}       // presentation logic
  calculateTax() {}     // business logic
  showToast() {}        // UI notification
  saveData() {}         // API call
}

Problems:

  1. Everything is mixed together – date formatting, tax rules, UI, API → no cohesion.

  2. Hard to maintain – one change might break unrelated functionality.

  3. Hard to test – unit tests must mock/save unrelated methods.

  4. Hard to reuse – you often only want calculateTax, but the service drags everything alon

Good

OrderRulesService.ts 
    calculateDiscount(), validateOrder(), calculateTax()

OrderApiService.ts 
    getOrders(), saveOrder(), deleteOrder()

OrderMapper.ts 
    mapOrderApiToVm(), mapOrderVmToApi()

ToastService.ts 
    showSuccess(), showError()

๐Ÿ“Œ Services should be cohesive, not generic.


1️⃣5️⃣ Avoid Passing Observables Everywhere

Wrong

@Input() orders$: Observable<Order[]>;
  • You’re giving a stream (Observable) to a UI component.
  • The component now has to subscribe (or use async pipe) and manage async logic.
  • If the component is “dumb,” it should only display data, not care if it comes from an API, a BehaviorSubject, or something else.
  • Makes testing harder, because now the component depends on async streams.

Better

The container component (or parent) subscribes to the observable:
    orders$: Observable<Order[]> = this.orderService.getOrders();

    orders: Order[] = [];

    ngOnInit() {
      this.orders$.subscribe(data => this.orders = data);
    }

Then the dumb child component simply displays orders:
    @Input() orders: Order[];

    <div *ngFor="let order of orders">
      {{ order.title }}
    </div>
rule
  • Containers = smart → handle observables, subscriptions, async logic
  • UI components = dumb → render data, emit events
  • Never force dumb components to deal with async

Exception: async pipe only in containers

  • You might still pass an observable if the container uses the async pipe itself.

  • But never pass $ streams directly to dumb components.


1️⃣6️⃣ Naming Reveals Responsibility

Bad names hide problems.

Bad Good
DataComponent     OrderListComponent
HandleLogic()     approveOrder()
CommonService     OrderApprovalService

๐Ÿ“Œ If the name is vague → responsibility is unclear.


1️⃣7️⃣ The “Delete Test” (Advanced)

Ask:

“If I delete this component, what breaks?”

Breaks   Meaning
UI only     Good
Business rules     ❌ wrong
API calls     ❌ wrong
App navigation     ❌ wrong

1️⃣8️⃣ Anti-Patterns You Must Recognize Instantly

@Input() setter with logic

@Input() set orders(value: Order[]) {

  this.orders = value.filter(o => o.active); // ❌ logic in setter

}

Problem:

  • The setter is doing business/UI logic.

  • Component should just receive data, not manipulate it.

  • Hard to track changes and test.

Better:

  • Transform the data before passing it to the component, maybe in a container or via a ViewMode

❌ Child component calling parent service

constructor(private parentService: ParentService) {}

Problem:

  • Violates dumb component rule.

  • Child now depends on specific parent implementation → not reusable.

  • Breaks testability.

Better:
  • Use @Input for data and @Output for events.
  • Let parent service handle the logic.

subscribe() inside dumb component

ngOnInit() {

  this.orders$.subscribe(data => this.orders = data);

}

Problem:

  • Component now manages async streams.

  • Risk of memory leaks (forgot unsubscribe)

  • Breaks OnPush detection if you mutate data.

Better:

  • Container subscribes → passes plain array to UI component

  • Or use async pipe in the template of the container only.

any everywhere

orders: any; // ❌

Problem:

  • No type safety → hides bugs

  • Makes code hard to read and maintain

Better:

  • Use interfaces / types (Order[], OrderViewModel, etc.)

setTimeout() for state sync

setTimeout(() => this.orders.push(newOrder), 0);

Problem:

  • Hides async bugs

  • Often used to “force” Angular to detect changes

  • Smells like OnPush workaround

Better:

  • Use immutable updates or ChangeDetectorRef.markForCheck()

  • Never rely on timing hacks

❌ Template doing logic {{ a > b ? ... }}

<p>{{ a > b ? 'Yes' : 'No' }}</p>

Problem:

  • Logic in template → hard to test, hard to maintain

  • Templates should just render data, not calculate it

Better:

  • Prepare values in the component or ViewModel:


1️⃣9️⃣ Real-World Refactor Example (Before → After)

❌ Before

control-data.component.ts
- subscribes to WebSocket
- updates table
- validates input
- formats values

✅ After

control-page.component.ts       (container)
control-state.facade.ts         (state)
control-table.component.ts      (UI)
control-validator.service.ts    (rules)
control.mapper.ts               (format)

2️⃣0️⃣ Final Mental Model (Tattoo This)

Components describe UI.
They do NOT decide business truth.

If a component decides truth → responsibility is wrong.


✅ Recommended Folder Structure (Feature-First)

src/
└── app/
    ├── core/                     # App-wide singletons
    │   ├── services/
    │   │   ├── api/
    │   │   │   └── websocket.service.ts
    │   │   ├── auth/
    │   │   │   └── auth.service.ts
    │   │   └── logger.service.ts
    │   └── core.module.ts
    │
    ├── shared/                   # Reusable UI & utilities
    │   ├── components/
    │   │   └── table/
    │   │       └── base-table.component.ts
    │   ├── pipes/
    │   │   └── date-format.pipe.ts
    │   ├── utils/
    │   │   └── string.utils.ts
    │   └── shared.module.ts
    │
    ├── features/
    │   └── control/              # ๐Ÿ”ฅ CONTROL FEATURE
    │       ├── pages/
    │       │   └── control-page/
    │       │       ├── control-page.component.ts
    │       │       ├── control-page.component.html
    │       │       ├── control-page.component.scss
    │       │       └── control-page.component.spec.ts
    │       │
    │       ├── components/
    │       │   └── control-table/
    │       │       ├── control-table.component.ts
    │       │       ├── control-table.component.html
    │       │       ├── control-table.component.scss
    │       │
    │       ├── state/
    │       │   ├── control-state.facade.ts
    │       │   ├── control-state.model.ts
    │       │   └── control-state.initial.ts
    │       │
    │       ├── services/
    │       │   └── control-validator.service.ts
    │       │
    │       ├── mappers/
    │       │   └── control.mapper.ts
    │       │
    │       ├── models/
    │       │   ├── control.dto.ts
    │       │   └── control.viewmodel.ts
    │       │
    │       ├── control-routing.module.ts
    │       └── control.module.ts
    │
    └── app.module.ts

๐Ÿ“Œ Why This Structure Works  

๐Ÿง  pages/

Purpose

  • Smart / container components

  • Connected to routing

  • Coordinates state & services

๐Ÿ“ Example:

pages/control-page/

๐ŸŽจ components/

Purpose

  • Dumb UI components

  • Reusable inside the feature

  • No services, no routing

๐Ÿ“ Example:

components/control-table/

๐Ÿงฉ state/

Purpose

  • Facade pattern

  • State orchestration

  • RxJS / Signals

๐Ÿ“ Example:

state/control-state.facade.ts

✔ Keeps components thin
✔ Easy to migrate to NgRx later


๐Ÿง  services/

Purpose

  • Business rules

  • Validation logic

  • No UI, no state

๐Ÿ“ Example:

services/control-validator.service.ts

๐Ÿ” mappers/

Purpose

  • Transform API → ViewModel

  • Formatting logic

  • Prevent template logic

๐Ÿ“ Example:

mappers/control.mapper.ts

๐Ÿ“ฆ models/

Purpose

  • Clear separation of:

    • API DTOs

    • UI ViewModels

๐Ÿ“ Example:

models/control.dto.ts
models/control.viewmodel.ts

๐Ÿงช Bonus: Minimal Variant (Smaller Projects)

If project is small, you can flatten:

features/control/
├── control-page.component.ts
├── control-table.component.ts
├── control-state.facade.ts
├── control-validator.service.ts
├── control.mapper.ts
├── control.models.ts
└── control.module.ts

But do NOT do this in large codebases.


๐Ÿ”ฅ Mental Rule (Remember This)

Routing → pages
UI → components
Truth → services/state
Transformation → mappers

If you follow this, your Angular code will:

  • Be testable

  • Be refactor-friendly

  • Scale without chaos


LeetCode C++ Cheat Sheet June

๐ŸŽฏ Core Patterns & Representative Questions 1. Arrays & Hashing Two Sum – hash map → O(n) Contains Duplicate , Product of A...