7 Angular Bugs and How to Fix Them Fast

Let’s be real — Angular is powerful, but it can also be extremely unforgiving when things go even slightly off-script.

Here are 7 Angular bugs I’ve run into (more than once 😅), and exactly how I debug or prevent them.


1. ExpressionChangedAfterItHasBeenCheckedError

💥 The Pain:
This one hits when you change a bound value inside ngOnInit() or ngAfterViewInit(). You think everything is fine… then BOOM 💣.

AfterViewInit() {
this.isVisible = true; // ❌ throws ExpressionChanged error
}

My Go-To Fix:
Use Angular’s ChangeDetectorRef to manually trigger change detection after the initial pass:

constructor(private cdr: ChangeDetectorRef) {}

ngAfterViewInit() {
this.isVisible = true;
this.cdr.detectChanges(); // ✅ safe and Angular-approved
}

Or use setTimeout() as a hack — but only if you’re really stuck.


2. [(ngModel)] Doesn’t Update the Value

💥 The Pain:
You’ve got the classic two-way binding setup, but… nothing happens.

<input [(ngModel)]="user.name" />

Fix It:
You probably forgot to import FormsModule into your module.

import { FormsModule } from '@angular/forms';

@NgModule({
imports: [FormsModule]
})
export class UserModule {}

⚠️ This isn’t global. Every lazy-loaded module needs this separately.


3. Angular Material mat-menu Closes Instantly

💥 The Pain:
The menu opens and closes immediately — especially in Firefox.

Fix It:
Use $event.stopPropagation() to prevent the click from bubbling:

<mat-menu #menu="matMenu">
<button mat-menu-item (click)="$event.stopPropagation()">Click Me</button>
</mat-menu>

Also: double-check that you’re not triggering routing or DOM changes inside the menu.


4. Async Pipe Doesn’t Update the UI

💥 The Pain:
Your Observable is emitting, the async pipe is in place, but the view doesn’t reflect the updates.

Fix It:
You’re likely mutating the data instead of replacing it. Angular only tracks reference changes.

// ❌ Don't mutate the array
this.todos.push(newTodo);

// ✅ Instead:
this.todos = [...this.todos, newTodo];

For better reactivity, I’ve started combining signals with Observables in Angular 16+ — game changer.


5. Routing Works Locally, but 404s After Deployment

💥 The Pain:
You refresh on /dashboard in production… and get a 404. 😠

Fix It:
Your server isn’t configured to redirect unknown routes to index.html.

  • For Firebase:
"rewrites": [{ "source": "**", "destination": "/index.html" }]
  • For Apache (.htaccess):
FallbackResource /index.html

This tells the server to hand off routing to Angular — not the server itself.


6. Styles Don’t Apply in Lazy-Loaded Modules

💥 The Pain:
Your Material buttons look completely unstyled in a lazy module.

Fix It:

  • Move shared styles to styles.scss
  • Use ::ng-deep (deprecated, but still works)
::ng-deep .mat-button {
background: red;
}

Or better yet — use Angular CDK theming instead of overriding styles manually.


7. ngFor Renders the Whole List Again

💥 The Pain:
Even when only one item changes, Angular re-renders the entire list. Performance tanks.

Fix It:
Always use trackBy in *ngFor.

<div *ngFor="let item of items; trackBy: trackById">
{{ item.name }}
</div>
tsCopyEdittrackById(index: number, item: any) {
  return item.id;
}

Angular uses this to track DOM elements efficiently instead of guessing.


🔁 Final Thoughts

Angular gives you power — but you have to understand the rules it plays by. These bugs have all bitten me (some repeatedly), but now they’re just a quick fix away.

💡 Learn the lifecycle. Understand change detection. Respect the module system.

It’ll save you hours of debugging.

Leave a Comment

Your email address will not be published. Required fields are marked *