
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.