Tips & Tricks for debugging unfamiliar AngularJS code

You've been given an Angular code base. How do you figure out where everything is? Where scope variables are coming from? What controller does what?

Angular doesn't make it easy, but there are few tips and tricks that can make your life easier. Tools like Angular Batarang can help, but they're not always installed when you need them. Take some time to understand the underlying concepts, and your life will be easier in the long run.

Angular Batarang is not a great way to do this kind of work. I would not recommend trying it out until you've attempted these techniques...

Here are a few tips that will save you a couple of hours next time you are thrown in the deep end of an Angular code base.

Most of these tips require that debugging info be available. If they don't work for you, open up the console and type angular.reloadWithDebugInfo();

What is the scope for this element?

Almost everything in Angular revolves around the scope. Angular creates a scope hierarchy with controllers and directives . Directives can sometimes have their own isolate scope. Being able to know the value of the scope at a given place in our page is valuable to debugging, and easy to do. Angular lets you access the scope available to an element via the .scope method.

An isolate scope is one that is unique to the directive and is not affected by anything outside of it (aside from bindings)

Inspect your scopes

It can be a bit confusing, but try it out! It can be a lot faster than setting breakpoints and inspecting variables. If you're not sure which of the above commands to run, just try them all. They're perfectly safe, and you'll eventually find the data you're looking for.

For more information on the angular scope, refer to the angular documentation

Where does this variable come from?

Once your Angular application grows in size it can become challenging to figure out where variables used in our templates come from. For a single directive with isolate scope, it's easy. In a hierarchy of controllers and directives, try some of these methods for tracking them down:

There is one small details that make things more complicated however: scope inheritance. A controller up in the hierarchy could be providing values available on the child scopes. There's a few ways you can go about figuring it out:

You may have noticed elements with a class "ng-scope" when looking through the inspector. They represent the elements where new scopes got created

Finding where the property is defined

Using the above techniques, you'll be able to find at what point in the document the property is being set. It's definitely a lot of work, but is usually faster than trying to guess, unless you wrote the code yourself!

Why does Prototypal Inheritance matter here?

Inheritance in JavaScript is different from inheritance in languages like Java or C#. JavaScript uses prototypal inheritance. Accessing a value that doesn't exist on a given object gets delegated to one higher up in the chain, if available.

Prototypal inheritance is amazingly useful when debugging Angular applications (see link below). The techniques above use it to analyze scopes to find where values come from. If you've ever had issues getting your two-way data bindings to behave the way you expect, the culprit may be prototypal inheritance.

There are many tutorials on how inheritance work in JavaScript on the net. You may want to start here. A good understanding of these concepts will make your Angular experience a lot smoother. It's a good foundation for any JavaScript developer.

What controller handles this section of the page?

This may seem obvious, but we often forget about it. If you need to figure out which controller is responsible for a section of the page, just look for the ng-controller attribute in the DOM. If you don't see one, go up one level and look again.

If you're like me, you may have trouble finding an attribute in a ton of HTML. To find find it, click on an element, run $($0).attr('ng-controller'), and if nothing comes up, click on its parent (using the bread crumbs).epeat until you get a controller name back.

With a big of jQuery magic, we can also find the controller that is nearest to our selection in one step: $($0).closest('[ng-controller]').attr('ng-controller')

Finding the controller

Why don't some elements show up?

If you use ng-if, ng-switch, or ng-views, you may have wondered why your elements didn't show up when searching for them in the inspector. Unlike ng-show which shows and hide elements by changing their visibility, these directives add and remove their content from the DOM completely based on conditions or routes.

Angular leaves a comment, such as <!-- ngIf: isFoo --> where the element would have been if isFoo had been true.

If the condition is more complicated, you can use the techniques we described earlier to understand why an element isn't being shown. In For this example, you would run $($0).scope().isFoo, and you would get a falsy value. That's why the element didn't get added to the DOM, and was replaced by a comment instead.

The comment will be in the DOM when the element has been added, not just when it is absent. Look right below it for an element with the same condition. If there isn't one, then you can assume the condition was not met.

How do I see the effects of a value changing?

What if we want to force the element from the previous section to show without going through the scenario that would set isFoo to true? We can do it from the console!

Changing the scope directly

The first step, selecting the element, is very important. If you don't select the right element, you may or may not get the results you expect because of how prototypal inheritance delegation work. Your value may not always get propagated up or down the chain.

What is the digest cycle?

In the previous section, we used the $digest method to trigger changes. You may wonder why we had to do that. Why didn't the DOM change the moment we set isFoo to true?

Objects can't notify Angular that their properties have changed without proxies or custom setters. One Angular's goals is to let us use plain old JavaScript objects. Angular solves this using the digest cycle to inspect expressions and watchers for changes. Digest is run by Angular when an event it is aware of happens. This could be a an $http request or an ng-click event.

Proxies are a new feature of ES2015 http://babeljs.io/docs/learn-es2015/#proxies

When something happens outside of Angular's known world about (maybe a jquery plugin, or a console command), it doesn't automatically run $digest and nothing happens. In this case we'll need to execute $digest ourselves. As soon as the digest executes, things will get updated.

ES2015 is the next version of JavaScript, also known as ES6. It contains features that can be used by Angular in the future to improve performance by simplifying how object changes are detected, such as proxies. While it is possible to use most of ES2015's features using Babel, a javascript compiler, fast proxies must be implemented natively, so the current version of Angular uses dirty checking instead.

How does Angular DI work?

Angular is built on top of the concept of dependency injection. It can feel a little bit magical. While a full explanation of Angular's DI is out of the scope of this blog post, here's a few things to keep in mind as you wrap your head around it.

Note that the following is a big oversimplification!

I strongly recommend learning the different types of angular services. They are a significant source of confusion when trying to wrap your head around an AngularJS code base.

Breakpoints are your friends.

You don't have to debug through your JavaScript code by adding console.log or debugger statements everywhere, though it can be useful. Consider familiarizing yourself with breakpoints, available in the inspector of all major browsers.

Chrome even supports conditional breakpoints, where the breakpoint will only stop execution if a certain JavaScript expression returns true. This is useful when debugging repetitive code where only a specific iteration is interesting (such as nested loops). You don't have to go through the entire loop to get where you want.

Conditional breakpoint

It's all JavaScript.

At the end of the day, the most important thing to understand about AngularJS, is that It's just JavaScript. There's no magic, and it is bound by the same rules as any other JavaScript applications. Dependency injection is just a big list of key value pairs from which objects are pulled from and passed to your components. The way scopes work is based on prototypal inheritance. The $digest loop is just a task that iterates over expressions and watchers when Angular-related events occur. You can force it to happen.

By understanding the good old JavaScript under Angular's hood, you can reduce the time it takes to debug your code base. It is worth the time to take a look at how Angular implements its own methods, including the directives for basic form elements from the Angular repository. It takes a bit of time, but gives a lot of background on how things end up working.

CC background photo by flickr user Jon Candy, "Longleat Maze"