Angular 107 🅰️ Debugging Angular Applications
A bug appears in an Angular application. The component does not render. The signal does not update. The HTTP request returns the wrong data. The route does not activate. The change detection cycle does not run. Debugging is the process of finding the cause and fixing it. Angular provides a set of tools and techniques for every stage of the investigation, and knowing which tool to reach for is the difference between a five-minute fix and a five-hour hunt.
The previous chapter covered Angular DevTools. This chapter covers the broader discipline of debugging: the tools, the techniques, the error messages, and the systematic process for narrowing down the cause. The topic is not about any single tool. It is about how the tools fit together and how to use them in sequence.
Key point: Debugging is a process of elimination. Start with the symptom. Identify the layer where the symptom occurs. Narrow the scope. Form a hypothesis. Test the hypothesis. The tools are the instruments. The process is the discipline. A developer who knows the tools but not the process wastes time. A developer who knows the process but not the tools is limited. The goal is to have both.
Why debugging matters
An Angular application has multiple layers: the component, the template, the service, the router, the injector, the change detector, the HTTP client, the DOM. A bug can live in any of them. The symptom appears in the browser. The cause is usually somewhere else.
The layer problem. A component that does not render might have a broken template, a ChangeDetectionStrategy.OnPush that is not being triggered, a signal that is not updating, an error thrown in the constructor, or a failed dependency injection. The symptom is the same. The cause is different. The first step is to identify which layer is responsible.
The timing problem. Change detection runs on every event, every timer, every HTTP response. A bug that appears “sometimes” might be a race condition between two asynchronous operations. The timing is invisible without the right tools.
The state problem. A component’s state is not visible in the DOM unless it is rendered. A signal that holds the wrong value, a service that returns stale data, a store that has been mutated incorrectly — these are invisible until they affect the view.
The error problem. Angular throws errors in specific places. A template error is thrown during change detection. A dependency injection error is thrown during component instantiation. A route error is thrown during navigation. The error message and the stack trace tell you where to look.
The trade-off. Debugging is not a single skill. It is a collection of skills: reading stack traces, using browser DevTools, using Angular DevTools, writing test cases that reproduce the bug, and reasoning about the code. The more tools you know, the faster the diagnosis.
a. The Common Error Messages
Angular’s error messages are specific. Each one points to a class of problems. Knowing them saves time.
NG0100 — Expression Changed After It Was Checked. This error occurs when a value that is bound in the template changes after Angular has already checked it in the current change detection cycle. The common causes are: a method that returns a different value each time it is called, a getter with a side effect, or a property that is modified in ngAfterViewInit. The fix is to use a signal, a pure pipe, or to defer the change to the next cycle with setTimeout or queueMicrotask. Angular’s documentation calls this error “the result of a change that was made after the change detection process had already run” .
NG0200 — Circular Dependency. This error occurs when two services depend on each other. The injector cannot resolve either one because each requires the other to be constructed. The fix is to break the cycle: use forwardRef, use an InjectionToken, or restructure the services so that the dependency is one-directional .
NG0304 — Unknown Element. This error occurs when the template contains an element that Angular does not recognize. The common causes are: a component or directive that is not imported in the component’s imports array, a typo in the selector, or a custom element that is not declared in the schema. The fix is to add the missing import or use CUSTOM_ELEMENTS_SCHEMA .
NG0303 — Binding to an Unknown Property. This error occurs when the template binds to a property that the element does not have. The common cause is a typo in the property name, or a directive that is not applied. The fix is to check the property name and the directive’s inputs.
NG05105 — Unexpected Synthetic Property. This error occurs when the template uses a synthetic property like @fadeIn but the animation trigger is not defined or not imported. The fix is to define the trigger in the animations array and import the necessary modules.
NG0913 — Application Stabilization. This error occurs when the application does not stabilize within the expected time. The common cause is a setInterval that is never cleared, a recursive setTimeout, or a long-running computation. The fix is to clean up the timers and avoid blocking the main thread.
The error messages are documented in the Angular error reference. Each error has a code, a description, and a set of common causes. The first step in any debugging session is to read the error message and look up the code.
b. The Debugging Tools
Angular provides several tools for debugging. Each one is useful for a different class of problem.
Angular DevTools is the primary tool. The Components tab shows the component tree and the state. The Profiler tab shows change detection cycles. The Injector Tree shows the dependency injection hierarchy .
The Chrome DevTools provide the standard debugging tools: the Sources tab for breakpoints, the Network tab for HTTP requests, the Performance tab for runtime profiling, and the Console for logging and interactive inspection.
The ng global object is exposed in development mode. It provides debugging helpers. ng.getComponent(element) returns the component instance for a DOM element. ng.getContext(element) returns the context for an embedded view. ng.getOwningComponent(element) returns the component that owns the element. ng.applyChanges(component) runs change detection on the component. ng.getInjector(element) returns the injector for the element. These helpers are available from the browser console when the application is built in development mode .
The $ng0, $ng1, $ng2 references are exposed by Angular DevTools. They refer to the most recently selected component instances. They allow interactive inspection from the console .
Console logging is the simplest tool. A console.log in the constructor, in ngOnInit, in a method, or in a signal’s computed function shows the order of execution and the values. The logging is not elegant, but it is immediate.
Breakpoints in the Sources tab pause execution and show the call stack. The debugger is the most precise tool for tracing execution. The debugger; statement in the source code triggers a breakpoint when the browser’s DevTools are open.
The network tab shows every HTTP request. The headers, the payload, the response, and the timing. The cy.intercept() in Cypress and the httpMock in Angular tests are the testing equivalents.
c. The Systematic Process
Debugging is not random. It follows a process.
Step 1: Reproduce the bug. The bug must be reproducible. If it is not, the first task is to find the conditions that trigger it. A reproducible bug is a solvable bug.
Step 2: Identify the symptom. What is the observable behavior? Is the component not rendering? Is the data wrong? Is the page slow? The symptom determines which tools to use.
Step 3: Identify the layer. Where does the symptom occur? In the template? In the component class? In the service? In the router? In the HTTP client? The layer narrows the search.
Step 4: Form a hypothesis. Based on the layer and the symptom, form a hypothesis about the cause. “The component is not updating because it uses OnPush and the input reference has not changed.” “The data is wrong because the service is returning cached data from the wrong request.” The hypothesis guides the investigation.
Step 5: Test the hypothesis. Use the tools to test the hypothesis. Add a console log. Set a breakpoint. Inspect the state in Angular DevTools. Run the code in the console. The test either confirms or rejects the hypothesis.
Step 6: Fix the cause. If the hypothesis is confirmed, fix the cause. The fix is usually small once the cause is known. The hard part is finding the cause.
Step 7: Verify the fix. Run the application. Confirm that the symptom is gone. Run the tests. Confirm that nothing else broke. Add a test that reproduces the bug and verifies the fix if the bug was subtle.
Step 8: Document the fix. A comment in the code, a commit message, or a note in the team’s knowledge base. The documentation prevents the same bug from being reintroduced.
Complete Example Session
This session demonstrates the debugging process on a slow component.
// ============================================
// PART 1: THE SYMPTOM
// ============================================
// The application is slow when the user list is rendered.
// The list has 1000 items.
// The page freezes for several seconds.
// ============================================
// PART 2: REPRODUCE THE BUG
// ============================================
// Open the application.
// Navigate to the user list.
// The page freezes.
// The bug is reproducible.
// ============================================
// PART 3: IDENTIFY THE SYMPTOM
// ============================================
// The symptom is slowness.
// The page freezes when the list renders.
// The freeze lasts several seconds.
// ============================================
// PART 4: IDENTIFY THE LAYER
// ============================================
// Open Angular DevTools.
// Click the Profiler tab.
// Start recording.
// Refresh the page.
// Stop recording.
// The timeline shows a single tall bar.
// The bar represents one change detection cycle.
// The cycle took several seconds.
// The layer is change detection.
// ============================================
// PART 5: FORM A HYPOTHESIS
// ============================================
// Click the tall bar.
// The bar chart shows UserListComponent at the top.
// The component renders 1000 items.
// Each item calls a method in the template.
// Hypothesis:
// The method in the template is called on every change detection cycle.
// The method does expensive work.
// The work is repeated 1000 times per cycle.
// ============================================
// PART 6: TEST THE HYPOTHESIS
// ============================================
// Open the component source.
// The template is:
// @for (user of users(); track user.id) {
// <li>{{ formatUser(user) }}</li>
// }
// The formatUser method is called for each user.
// The method does string manipulation.
// The hypothesis is confirmed.
// ============================================
// PART 7: FIX THE CAUSE
// ============================================
// Before:
@Component({
selector: 'app-user-list',
template: `
@for (user of users(); track user.id) {
<li>{{ formatUser(user) }}</li>
}
`,
})
export class UserListComponent {
users = signal<User[]>([]);
formatUser(user: User): string {
// Expensive string manipulation
return `${user.firstName} ${user.lastName}`.toUpperCase();
}
}
// After:
@Component({
selector: 'app-user-list',
template: `
@for (user of formattedUsers(); track user.id) {
<li>{{ user.displayName }}</li>
}
`,
})
export class UserListComponent {
users = signal<User[]>([]);
formattedUsers = computed(() =>
this.users().map(user => ({
id: user.id,
displayName: `${user.firstName} ${user.lastName}`.toUpperCase(),
}))
);
}
// The computed signal caches the result.
// The template reads the cached displayName.
// The expensive computation runs only when the users change.
// ============================================
// PART 8: VERIFY THE FIX
// ============================================
// Refresh the page.
// Record another profile in Angular DevTools.
// The tall bar is gone.
// The change detection cycle is fast.
// The fix is confirmed.
// ============================================
// PART 9: THE ERROR MESSAGE EXAMPLE
// ============================================
// A different bug: the component does not render.
// The console shows:
// NG0304: 'app-user-card' is not a known element.
// The error message identifies the problem.
// The component 'app-user-card' is not imported.
// The fix:
import { UserCardComponent } from './user-card.component';
@Component({
imports: [UserCardComponent],
// ...
})
export class UserListComponent {}
// ============================================
// PART 10: THE NG GLOBAL OBJECT
// ============================================
// In the browser console:
const element = document.querySelector('app-user-list');
const component = ng.getComponent(element);
// Returns the UserListComponent instance.
component.users();
// Returns the current users array.
ng.applyChanges(component);
// Runs change detection on the component.
const injector = ng.getInjector(element);
// Returns the injector for the element.
// ============================================
// PART 11: THE BREAKPOINT
// ============================================
// Open the Sources tab.
// Find the component source.
// Click the line number to set a breakpoint.
// Refresh the page.
// The execution pauses at the breakpoint.
// Inspect the scope, the call stack, and the variables.
// Step through the code to find the cause.
// ============================================
// PART 12: THE DEBUGGING CHECKLIST
// ============================================
// 1. Reproduce the bug
// 2. Identify the symptom
// 3. Identify the layer
// 4. Form a hypothesis
// 5. Test the hypothesis
// 6. Fix the cause
// 7. Verify the fix
// 8. Document the fix
The twelve parts cover the symptom, reproducing the bug, identifying the symptom, identifying the layer, forming a hypothesis, testing the hypothesis, fixing the cause, verifying the fix, the error message example, the ng global object, the breakpoint, and the debugging checklist.
Quick Reference
The Common Error Messages
| Code | Meaning | Common Cause |
|---|---|---|
| NG0100 | Expression changed after checked | Method with side effect in template |
| NG0200 | Circular dependency | Two services depending on each other |
| NG0304 | Unknown element | Missing import |
| NG0303 | Binding to unknown property | Typo or missing directive |
| NG05105 | Unexpected synthetic property | Missing animation trigger |
| NG0913 | Application stabilization | Uncleared timer |
The Debugging Tools
| Tool | Purpose |
|---|---|
| Angular DevTools | Component tree, profiler, injector tree |
| Chrome DevTools | Sources, Network, Performance, Console |
ng global | Programmatic inspection |
$ng0, $ng1, $ng2 | Recently selected components |
| Console logging | Order of execution and values |
| Breakpoints | Pause execution and inspect |
| Network tab | HTTP requests and responses |
The ng Global Object
| Method | Purpose |
|---|---|
ng.getComponent(element) | Get the component for an element |
ng.getContext(element) | Get the context for an embedded view |
ng.getOwningComponent(element) | Get the owning component |
ng.getInjector(element) | Get the injector for an element |
ng.applyChanges(component) | Run change detection |
The Debugging Process
| Step | Action |
|---|---|
| 1 | Reproduce the bug |
| 2 | Identify the symptom |
| 3 | Identify the layer |
| 4 | Form a hypothesis |
| 5 | Test the hypothesis |
| 6 | Fix the cause |
| 7 | Verify the fix |
| 8 | Document the fix |
Best Practices
✅ Do This:
// Use computed signals instead of methods in templates
formattedUsers = computed(() => this.users().map(...)); // ✅
// Use the ng global object for interactive inspection
const component = ng.getComponent(element); // ✅
// Use Angular DevTools to find slow change detection
// Profile before and after the fix
// Read the error message and look up the code
// NG0304 points to a missing import
❌ Don’t Do This:
// Don't call methods in templates
{{ formatUser(user) }} // runs on every cycle // ❌
// Don't ignore the error message
// The code identifies the class of problem
// Don't guess without a hypothesis
// Form a hypothesis, then test it
// Don't skip the verification step
// Confirm the fix with a profile
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| NG0100 | Method with side effect | Use computed or pure pipe |
| NG0200 | Circular dependency | Use forwardRef or restructure |
| NG0304 | Missing import | Add to imports array |
| Slow rendering | Method in template | Use a computed signal |
| Change detection not running | OnPush with no trigger | Use signals or markForCheck |
Real-World Examples
1. The NG0100 Error
// Cause: method with side effect in template
2. The NG0304 Error
// Fix: add the missing import
imports: [UserCardComponent]
3. The ng Global
ng.getComponent(document.querySelector('app-root'))
4. The $ng0 Reference
$ng0.users()
5. The Breakpoint
debugger;
6. The Console Log
console.log('Component initialized', this.users());
7. The Computed Signal Fix
formattedUsers = computed(() => this.users().map(...));
8. The Profiler
# Record, interact, stop, analyze
9. The Network Tab
# Inspect HTTP requests and responses
10. The Injector Tree
# Angular DevTools > Injector Tree
Visual
The Debugging Layers
┌──────────────────────────────────────────────┐
│ TEMPLATE │
│ └─ Binding errors, unknown elements │
│ │
│ COMPONENT │
│ └─ State, lifecycle, change detection │
│ │
│ SERVICE │
│ └─ Data, HTTP, business logic │
│ │
│ ROUTER │
│ └─ Navigation, guards, resolvers │
│ │
│ INJECTOR │
│ └─ Dependency resolution │
│ │
│ DOM │
│ └─ Rendering, layout, paint │
│ │
└──────────────────────────────────────────────┘
The Debugging Process
┌──────────────────────────────────────────────┐
│ 1. REPRODUCE │
│ └─ Find the conditions │
│ │
│ 2. IDENTIFY SYMPTOM │
│ └─ What is the observable behavior? │
│ │
│ 3. IDENTIFY LAYER │
│ └─ Template, component, service, router │
│ │
│ 4. FORM HYPOTHESIS │
│ └─ What is the likely cause? │
│ │
│ 5. TEST HYPOTHESIS │
│ └─ Use the tools to confirm or reject │
│ │
│ 6. FIX CAUSE │
│ └─ Change the code │
│ │
│ 7. VERIFY FIX │
│ └─ Confirm the symptom is gone │
│ │
│ 8. DOCUMENT FIX │
│ └─ Comment, commit, knowledge base │
│ │
└──────────────────────────────────────────────┘
The Error Code Families
┌──────────────────────────────────────────────┐
│ NG0XXX : Change detection errors │
│ NG0100 : Expression changed after checked │
│ │
│ NG02XX : Dependency injection errors │
│ NG0200 : Circular dependency │
│ │
│ NG03XX : Template errors │
│ NG0303 : Unknown property │
│ NG0304 : Unknown element │
│ │
│ NG05XX : Animation errors │
│ NG05105 : Unexpected synthetic property │
│ │
│ NG09XX : Hydration errors │
│ NG0913 : Stabilization timeout │
│ │
└──────────────────────────────────────────────┘
The ng Global Object
┌──────────────────────────────────────────────┐
│ ng.getComponent(element) │
│ └─ Component instance for the element │
│ │
│ ng.getContext(element) │
│ └─ Context for the embedded view │
│ │
│ ng.getOwningComponent(element) │
│ └─ Component that owns the element │
│ │
│ ng.getInjector(element) │
│ └─ Injector for the element │
│ │
│ ng.applyChanges(component) │
│ └─ Run change detection on the component │
│ │
└──────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Angular DevTools | Component, profiler, injector tree |
| Chrome DevTools | Sources, network, performance, console |
ng global | Programmatic inspection |
$ng0 | Currently selected component |
| Error codes | NG0100, NG0200, NG0303, NG0304 |
| Common cause | Method in template |
| Common fix | Computed signal |
| Debugging process | 8 steps |
| Development only | Yes |
Key takeaways:
- Angular’s error messages are specific and coded. Each code points to a class of problems. NG0100 is a change detection error. NG0200 is a circular dependency. NG0304 is an unknown element. Reading the error message and looking up the code is the first step in any debugging session .
- Angular DevTools is the primary debugging tool. The Components tab shows the component tree and state. The Profiler tab records change detection cycles. The Injector Tree shows the dependency injection hierarchy .
- The
ngglobal object provides programmatic inspection.ng.getComponent(element)returns the component for a DOM element.ng.getInjector(element)returns the injector.ng.applyChanges(component)runs change detection. These helpers are available in development mode . - The
$ng0,$ng1,$ng2references expose recently selected components. They are provided by Angular DevTools and allow interactive inspection from the console. The most recently selected component is$ng0. - The most common performance bug is a method call in a template. A method in a template runs on every change detection cycle. If the method does expensive work, the cycle becomes slow. The fix is to use a computed signal or a pure pipe .
- The debugging process is systematic. Reproduce the bug. Identify the symptom. Identify the layer. Form a hypothesis. Test the hypothesis. Fix the cause. Verify the fix. Document the fix. The process is the discipline. The tools are the instruments .
- The
OnPushchange detection strategy is a common source of “the view does not update” bugs. The component is not checked unless an input reference changes, an event fires, a signal read in the template changes, ormarkForCheckis called. The fix is to use signals or to callmarkForCheckexplicitly .
Remember: Debugging is a process of elimination. Start with the symptom. Identify the layer. Form a hypothesis. Test the hypothesis. Use the tools: Angular DevTools, Chrome DevTools, the ng global object, console logging, and breakpoints. Read the error messages. Look up the codes. The most common performance bug is a method in a template. The most common state bug is OnPush without a trigger. The most common DI bug is a circular dependency. The process is the same every time.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!