Debugging is an integral part of application development and maintenance. We can’t avoid bugs completely, but we can nip most of them in the bud by skilfully using the available tools. I’m going to tell you about the ones I use myself. In my opinion, they should be useful to any developer who creates Angular applications and more.
In general, we cannot expect automatic tools to locate and completely eliminate problems. There are exceptions, such as linters (e.g. SonarLint or TSLint) which can automatically detect or even fix errors, but they can only do so for the most obvious errors, usually related to language syntax. However, critical errors often result from unforeseen corner cases of application behaviour, when certain application states cause unexpected (and therefore unhandled) exceptions. Particularly challenging bugs do not generate any exceptions, but instead cause incorrect behaviour of the application. This is especially true for nondeterministic bugs which occur randomly so they can’t be reproduced every time. Race conditions are a good example. They occur when an application has two or more processes that should terminate in a certain order, and that exact order, as defined by the developer, is required for the application to function properly. When unforeseen factors, often independent from the application itself, change this order, the resulting bug can be difficult to understand and eliminate. To effectively identify and solve such problems you need to have, first and foremost, a good understanding of the system, solid experience, and practical knowledge supported by an appropriate set of tools.
Where do we start?
Different types of bugs require a different approach. Usually the first thing I do when diagnosing an application is press F12 in my web browser to open the Developer tools and select the Console tab, where I can see the log with various messages reporting irregularities in the application’s operation. Since Chrome is the leading browser, I use it the most and it’s the focus of this article, but other popular browsers usually also have built-in developer tools that can be launched in the same way.
If your application is behaving incorrectly, you might find a hint in the browser console’s error messages, especially when dealing with the common “null reference exception” error. Sometimes, however, this is like looking for a needle in a haystack. You might not find any information relevant to your problem, and you’ll probably encounter many other messages not related to the error you’re diagnosing. If your application does not generate a large number of console messages – it’s a nice exception to the rule you can observe every time you run the console on a popular website – they log a lot of messages every time you open or refresh the page.
Looking for error messages in the console is usually not enough to accurately diagnose the problem. In order to determine the nature of the problem, it is necessary to trace the state of the application, that is, the behaviour of individual variables.
Logging application state
However, I usually prefer to log messages in the browser console, in particular using the method console.log(), for several reasons. First of all, using console.log you can display the values of variables of any type, including complex objects. You can also log the value of a variable in several different places in the application, and then see all the results side by side in the console window to compare them.
Although console.log is usually enough, the console object has a few other useful methods you should know, such as:
- console.table(array) — displays an array of variables as a readable table.
- console.time(label: string), console.timeEnd(label: string) — starts and stops a timer with a specified ID, logging the elapsed time to the console.
For example, running the code below produces results as in the figure below:
16 this.testVar.property1 = 'value1';
19 console.table([['value1', 'value2', 'value3'], ['value4', 'value5', 'value6']]);
20 this.testVar.property1 = 'value2';
The above solution is relatively simple, but it has one major flaw, just like the “alert” method. You need to modify the code twice – first to add the methods to log the desired values, and then to remove all those changes.
VS Code debugger
You can also track variables using the debugger included in Visual Studio Code. Once you configure the editor, you can set breakpoints at individual lines of code in order to trace the values of specific variables in these and subsequent lines.
For example, to use the VS Code debugger with Chrome:
1) Install the “Debugger for Chrome” plugin.
2) Save your project as a workspace: File -> Save workspace as…
3) Make sure the root folder of your project includes a .vscode folder which in turn contains the file launch.json with debugger configuration. For a standard project created by Angular CLI, this file might look as follows:
"name": "Launch Chrome",
Then just run the application with the ng serve command and start debugging by pressing F5.
Similarly to debugging code in C # in Visual Studio, you can set breakpoints using the mouse or by pressing F9 to stop the application at the selected line. You can also press other keys, such as F10 or F11 to proceed to the next instruction. If you prefer clicking, you can use the intuitive pop-up menu.
The WATCH window displays the current value of the variable specified earlier.
The entire VS Code window with an open debugging session looks like this:
Interestingly, you can control the debugging session not just from the VS Code window, but also from the browser window. The Sources tab in Developer Tools lets you preview the code line where the application has been paused,
you can check the current value of the variables,
and you have a menu similar to the one in VS Code.
In summary, using the debugger in IDE requires some initial configuration, but it makes debugging more convenient in the future. The more complicated your situation is, and the more code and the variables you have to analyse, the more you’ll appreciate the benefits of this solution.
The functioning of our front end application is usually dependent on the data it gets from the back end. Problems detected in the front end often result just from incorrect functioning of the back end or communication errors between the front end and the back end. Solving these problems is again made easier by the browser’s developer tools. Of course, these tools will not allow you to directly solve errors in other layers of the system, but you can use them to determine exactly which queries are sent by your front end application and what data (and after what time) are received in reply. You can do this using the Network tab.
When analysing asynchronous back end queries, you can click XHR to filter out all other types of queries, which will help you not get swamped by data.
As illustrated above, failed queries are easy to spot. They are marked in red and the relevant information is displayed in the Status column.
By right-clicking a query, you can view the content of its headers and the full content of the response. You can also check exactly when the query was sent and when the response arrived.
Another useful ability is repeating the selected query (Replay XHR), especially if you are debugging the back end at the same time.
Bugs are not always related to application logic or data flow. Sometimes some elements on the page just look wrong. If you need to figure out when and why the appearance of the application becomes corrupted, and which specific settings are to blame, the Developer tools again come to the rescue.
After selecting the Elements tab and clicking the appropriate icon or using the keyboard shortcut Ctrl + Shift + C, you just need to mouse over a page element to display the code which defines its style.
What’s more, after clicking an element you can edit this code directly in the browser. This lets you test potential solutions and see the effects immediately.
Cache, cookies, LocalStorage
The Application tab in the Developer Tools also displays information about the browser cache, which can be useful in some situations. This information includes cookies and LocalStorage. You can also easily edit or delete individual entries.
Another advantage of the Chrome DevTools console is its extensibility. Plugins let you add interesting new functionalities. One such plugin is Augury, available for Chrome and Firefox. It can be useful to every Angular developer, especially when handling more complex applications.
This extension adds a new tab to Developer Tools, where you can see information such as the map of dependencies between the components of your application.
You can also display the status of the selected component,
as well as locate the component responsible for displaying a certain element on the page.
This extension makes it easier to understand the code structure of an Angular application and to locate suspicious places in the code.
The tools presented above provide many options to diagnose various types of bugs in Angular applications. However, strategy against bugs in such applications should be primarily based not on these tools, but on a well-planned architecture that will prevent many errors, and will significantly facilitate diagnosing and solving those that can’t be avoided.
Use the best practices to improve code transparency and to ensure the data flow is appropriate for the complexity of the application right from the get-go. For complex applications, consider using a centralized state management solution, such as NGRX – the REACT equivalent of Redux. This will increase the overhead when programming state transitions, but you will gain much better control over your growing ecosystem, synchronization between components, and the ability to use another very valuable tool: Redux DevTools.
When using a Redux solution, all changes to the application state are formalized, using specified events, and the current state is stored in one dedicated location. All changes can be logged so that you can later use the Redux DevTools browser plugin to accurately trace their sequence, and even “time-travel” in the application by “rewinding” it to a specific state from the past.
If you are not doing this already, I encourage you to use the bug diagnosing techniques described above. I hope you will find them as useful as I have. If I failed to mention something you think would be worthwhile to include here, please add a comment!
https://developer.mozilla.org/pl/docs/Web/API/Console – console methods
https://code.visualstudio.com/docs/editor/debugging#_launch-configurations – debugging in VS Code
https://augury.rangle.io/ – Augury plugin for Chrome and Firefox
https://ngrx.io/ – NGRX