Wednesday, January 7, 2026

Angular Performance & Forms

 


Performance & Forms


1️⃣ Change Detection Basics (What ACTUALLY happens)

Angular checks bindings → template expressions → pipes → functions
This happens when:

  • Any event (click, input)

  • setTimeout, Promise

  • HTTP response

  • Observable emits

  • Parent component updates

❌ Performance killer

<div>{{ getTotal() }}</div>
getTotal() {
  return this.items.reduce((a, b) => a + b.price, 0);
}

👎 Runs on every change detection, even mouse move.


Fix (Derived value)

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

computed() is NOT executed on every change detection.

it is:

  • Reactive

  • Memoized

  • Dependency-tracked

  • Runs once

  • Cached value

  • Re-runs ONLY when items changes

📌 Rule:
Templates must be dumb
No logic, no functions.


2️⃣ OnPush Strategy (Stop unnecessary checks)

Default behavior (what Angular does normally)

  • Parent changes → ALL children checked

  • Angular keeps checking everything again and again.

  • That’s why big apps feel slow.

  • Parent change

     └─ Child A checked

     └─ Child B checked

     └─ Child C checked

     └─ Grandchildren checked

OnPush behavior (the “smart” mode)

Angular says:

“I will NOT check this component again unless I have a GOOD reason.”

  • OnPush Strategy - Checked me ONLY IF:
        @Input() reference changes 
      this.user = { ...this.user, name: 'Kamal' }; // ✅ new objec

  • Event inside component
        <button (click)="increment()">+</button>

    increment() {
      this.count++;
    }
  • Observable emits (async)
        <div>{{ orders$ | async }}</div>

  • Signal changes

    count = signal(0);

    increment() {

      this.count.update(v => v + 1);

    }
    <p>{{ count() }}</p>
  • When should YOU use OnPush?
    • Container components
    • ✅ Table-heavy UIs
    • ✅ Dashboard screens
    • ✅ Anything using Observables / Signals

✅ Use OnPush ALWAYS unless proven otherwise

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class OrderRowComponent {}

❌ Common OnPush bug

this.orders.push(newOrder); // ❌ reference unchanged

✅ Correct

this.orders = [...this.orders, newOrder];

📌 Immutability = OnPush works


3️⃣ trackBy (Fix DOM thrashing)

Without trackBy, Angular:

  • Angular thinks like this 👇

    “Hmm… array changed.
    I DON’T KNOW which item is which.
    Safer to destroy everything and rebuild.”

  • Destroys ALL rows

  • Recreates ALL rows

❌ Bad

<tr *ngFor="let o of orders">

✅ Good

<tr *ngFor="let o of orders; trackBy: trackById">
trackById(_: number, o: Order) {
  return o.id;
}

  • Now Angular thinks:

“Ah! Each row has an identity (id).
Let me match old DOM with new data.”

 

Old DOM:
    Row(id=1)
    Row(id=2)

New data:
    Row(id=99)
    Row(id=1)
    Row(id=2) 

📌 Result:

  • DOM reused

  • Scroll position preserved

  • Inputs don’t reset

  • ✅ Only ONE new row created

  • ✅ Others reused

  • ✅ Fast

  • ✅ No flicker



4️⃣ Reactive Forms Architecture (How pros design forms)

❌ Anti-pattern (God Form)

this.form = this.fb.group({
  name: [],
  email: [],
  address: [],
  payment: [],
  shipping: []
});
And in the SAME component:
  • Builds the form

  • Renders HTML

  • Handles submit

  • Calls API

  • Does validation

  • Shows errors

  • Manages loading

Why this is BAD (real reasons)

❌ File becomes 600–1000 lines
❌ Impossible to reuse UI
❌ Unit tests are painful
❌ One tiny UI change breaks logic
❌ You can’t split the form later

This is called a God Component.

Hard to validate, test, and reuse.


✅ Correct Architecture

Container (smart)

  • Responsibilities

    • Creates the FormGroup

    • Owns business rules

    • Handles submit

    • Talks to API

    • Decides what happens

@Component({ ... })
export class UserFormContainerComponent {
  form = this.fb.group({
    name: ['', Validators.required],
    email: ['', Validators.email],
    address: this.fb.group({
      city: [''],
      zip: ['']
    })
  });

  submit() {
    if (this.form.valid) {
      this.api.save(this.form.value);
    }
  }
}

  • This component does NOT care about HTML details.

Presentational (dumb)

  • Receives FormGroup

  • Emits UI events

@Component({ ... })
export class UserFormComponent {
  @Input() form!: FormGroup;
  @Output() submitForm = new EventEmitter<void>();
}
<form [formGroup]="form" (ngSubmit)="submitForm.emit()">
  <input formControlName="name">
  <input formControlName="email">
  <button type="submit">Save</button>
</form>

📌 This component:

  • ❌ Does NOT create the form

  • ❌ Does NOT call API

  • ❌ Does NOT know backend exists

📌 Same rule as components → separation of concerns

🔁 How data flows (THIS IS THE KEY)

Container

  ↓ FormGroup (state)

Presentational

  ↓ User types

FormGroup updates

  ↓ Submit event

Container handles logic


5️⃣ Custom Validators (Real-world)

Field-level validator

function strongPassword(control: AbstractControl) {
  return control.value?.length >= 8 ? null : { weak: true };
}
password: ['', strongPassword]

6️⃣ Cross-field Validators (Very important)

Example: password & confirm password

function passwordMatch(group: AbstractControl) {
  const p = group.get('password')?.value;
  const c = group.get('confirm')?.value;
  return p === c ? null : { mismatch: true };
}
this.form = this.fb.group(
  {
    password: [''],
    confirm: ['']
  },
  { validators: passwordMatch }
);

📌 Cross-field = group validator, not control validator


7️⃣ Performance Debugging (No guessing anymore)

Tools you MUST use

  • Angular DevTools

  • Change Detection Profiler

  • Chrome Performance tab

Debug checklist

Ask:

  1. Is this component OnPush?

  2. Are functions called in template?

  3. Is trackBy missing?

  4. Are objects mutated?

  5. Is form recalculating on every keystroke?


Mental Model (Remember this)

❌ Slow Angular = too many checks + DOM recreation
Fast Angular = OnPush + immutable data + derived values


✔ “This component re-renders because input reference changed”
✔ “This list is slow because no trackBy”
✔ “This validator belongs to group, not control”
✔ “This performance fix is correct — not a guess”

Advanced Angular Performance & Forms – Extra Techniques 🔥


8️⃣ Kill Change Detection at the Source (Zone tricks)

❌ Problem

Angular runs change detection for:

  • scroll

  • mousemove

  • resize

  • 3rd-party libraries

Solution: NgZone.runOutsideAngular

constructor(private zone: NgZone) {}

ngAfterViewInit() {
  this.zone.runOutsideAngular(() => {
    window.addEventListener('scroll', () => {
      // no change detection triggered
    });
  });
}

📌 Use ONLY when UI doesn’t need updates
(ex: analytics, infinite scroll listeners)


9️⃣  Detach Change Detection (Rare but deadly effective)

constructor(private cdr: ChangeDetectorRef) {}

ngOnInit() {
  this.cdr.detach();
}

Manually reattach:

this.cdr.detectChanges();

📌 Use cases:

  • Large dashboards

  • Static reports

  • Read-only views

⚠️ Use carefully (senior-level tool)


🔟  Signals + OnPush = Near-zero CD

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class DashboardComponent {
  data = signal<Data[]>([]);
}

📌 Signals update only consumers
No tree-wide checking like Observables.


1️⃣2️⃣ Don’t Subscribe in Components (Hidden performance bug)

❌ Bad 

this.service.data$.subscribe(v => {
  this.data = v;
});
Angular does NOT know when to stop checking.

Every time:

  • mouse moves 🖱️

  • key pressed ⌨️

  • timer ticks ⏱️

  • ANY async event

Angular runs change detection
👉 your component gets checked even if data didn’t change

If you forget this 👇

ngOnDestroy() {

  this.sub.unsubscribe();

}

The subscription:

  • keeps running

  • keeps memory

  • keeps CPU busy

In large apps → slow app, random bugs, crashes

You’re doing Angular’s job manually

You are:

  • managing lifecycle ❌

  • managing cleanup ❌

  • triggering UI updates ❌

Angular already solved this.

Good  - The RIGHT way (what async pipe really does)

data$ = this.service.data$;
<div *ngFor="let d of data$ | async"></div>

You are saying:

“Angular, YOU handle the subscription. I only care about display.”

📌 async pipe:

  • auto unsubscribe

  • optimized CD

ThingResponsibility
Component     Expose streams
Template     Render streams
async pipe     Subscribe + unsubscribe
Angular     Optimize change detection

1️⃣3️⃣ Smart Debouncing Forms (Critical)

❌ Bad

this.form.valueChanges.subscribe(v => {
  this.search(v);
});

✅ Good

this.form.valueChanges.pipe(
  debounceTime(300),
  distinctUntilChanged()
).subscribe(v => this.search(v));

📌 Reduces API calls by 90%+


1️⃣4️⃣ updateOn – Hidden Form Performance Weapon

this.form = this.fb.group({
  email: ['', {
    updateOn: 'blur'
  }]
});

Options:

  • 'change' (default)

  • 'blur'

  • 'submit'

📌 Perfect for:

  • Heavy validators

  • Expensive async validators


1️⃣5️⃣ Async Validators Done Right

❌ Bad

email: ['', null, this.checkEmail]

Triggers on every keystroke.

✅ Good

email: ['', {
  asyncValidators: [this.checkEmail],
  updateOn: 'blur'
}]

1️⃣6️⃣ Lazy Load Forms & Heavy Components

loadComponent: () =>
  import('./big-form.component').then(m => m.BigFormComponent)

📌 Huge win for enterprise apps.


1️⃣7️⃣ Virtual Scroll (Lists > 100 items)

Use cdk-virtual-scroll-viewport

<cdk-virtual-scroll-viewport itemSize="50">
  <div *cdkVirtualFor="let item of items">
    {{ item.name }}
  </div>
</cdk-virtual-scroll-viewport>

📌 Renders only visible rows.


1️⃣8️⃣Avoid Pure Pipe Abuse

❌ Misuse

{{ data | heavyCalculation }}

Runs often.

Better

  • Precompute

  • Use computed signal

  • Or map in stream


1️⃣9️⃣ Split Huge Forms (Very underrated)

Instead of one massive form:

  • Step forms

  • Nested FormGroup

  • Load sections lazily

📌 Faster validation + easier debugging.


2️⃣0️⃣ Use readonly & disabled correctly

control.disable({ emitEvent: false });

📌 Prevents unnecessary valueChanges emissions.


2️⃣1️⃣ ChangeDetection Debug Trick

Add temporarily:

ngDoCheck() {
  console.log('CD ran');
}

📌 You’ll immediately see what causes re-renders


2️⃣2️⃣ Profiling Checklist (Memorize this)

When Angular is slow:

  1. List or form?

  2. OnPush?

  3. trackBy?

  4. Mutation?

  5. Template functions?

  6. Unnecessary subscriptions?

  7. Too many validators?

  8. No debouncing?

  9. Heavy DOM?

  10. Change detection triggered by zone?



Angular performance problems are 80% architecture mistakes, not framework limits


After mastering this, you can:

✔ Optimize without trial-and-error
✔ Explain why a fix works
✔ Design forms that scale to 100+ controls
✔ Debug CD like a profiler, not a guesser



No comments:

Post a Comment

LeetCode C++ Cheat Sheet June

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