Debugging AngularJS

Heads Up!

This article is several years old now, and much has happened since then, so please keep that in mind while reading it.

After Umbraco 7 shipped with a backend using AngularJS, the Umbraco community has welcomed Angular with somewhat open arms. But many of us started out as Angular rookies. In this blog post I will share some of my learnings on debugging Angular.

I won't cover the non-Angular backoffice setup, but just note that you should set debug="true" in <compilation ... in your web.config in order to disable Umbraco's backend caching of your Angular code. The other thing that's specific to the Umbraco Angular setup is that you should assign the desired output value to $scope.value for umbraco to automatically save the value.

First of all, you need to be able to clear your cache when you need it. I prefer using chrome's devtools and clear my cache with the extension clear-cache. This gives you more control than just turning on the "Disable cache (while DevTools is open)" option from devtools settings (that you open by clicking the little gear-icon).

I also use two different Angular specific browser extensions: ngInspector and Batarang.

ngInspector

ngInspector is very simple and lightweight and sits fixed on top your website showing you the different scopes and their values. Click a value and it will console.log() it for you (see some console tips a little later in this blog post).

Nginspector

Batarang

Batarang is a really powerful extension that shows you lots of information about your Angular app. It lives inside devtools under "Angular JS" and you need to enable it and reload the page to start using it. Batarang does all that ngInspector does and has a lot more features. It draws a nice dependency graph and shows you some relevant performance information (if you have expensive watchers/bindings etc).

Batarang

Good old console

But what I find myself using most of the time is the console (open devtools and press escape to have it side-by-side with another open tab or use its own tab). Angular has the possibility of different, and even nested, scopes depending on where in the DOM you are. In the elements pane of devtools you can select a DOM node by clicking it. Then you can reference the DOM node from the console by using $0. Combined with Angular's element methods this is pretty powerful. You can write angular.element($0).scope(); in the console to see the current scope.

Cons

Sometimes you will encounter issues where Angular doesn't update values in the view. This typically happens when you're using non-Angular async methods (like jquery.ajax(), setTimeout() etc. A great way to see if that's the case is to ask the current scope to update its bindings. You can confirm the solution if the view is updated when you do angular.element($0).scope().$apply().

You should ideally try to avoid this issue altogether by using Angular services whenever possible ($http, $timeout, etc), but when you occasionally can't avoid it the fix above is really helpful.

Chrome has a feature where anything you log to the console can be saved to a temporary global variable, so you can play around with it later. You do that by right clicking the response and choosing "Store as global variable" (you can then reference it by temp1).

Cons2

This way you can easily take a look at all the handy methods that are already on the scope.

Cons3

Pretty neat, right? But this also allows you to assign any method (Angular internal or your own) to the $scope and execute it in context and "on demand" from the console like you just did with $apply.

If you want to make sure the object isn't changed in the middle of you playing around (because objects in javascript are referenced and not copied) you can wrap your call in an angular.copy(...) call. This will of course break some of the methods, as you no longer reference the real scope object. This is especially useful when you are debugging a changing scope, because the logged version might update with the reference (this very chrome specific and has to do with the size of the object).

Another console tip is its .table() method. This is especially useful when you want to review a list (array) of similar items (objects), like recent news-articles, images in a gallery or something similar. If you for instance do console.table(galleryItems); you get a clear and readable table view.

Table

Angular source

A really good place to look for inspiration or best practices is of course the Angular code. An important thing to note here, is that there are differences in the source code if you use the minified production or not. In production mode error messages are very cryptic and often only contains a link to the Angular website. This isn't very helpful if you are offline. The non-minified development version has the full error-message and the source-code is full of great comments that contain the documentation for all the core features. So make sure to use the dev version when developing and the minified version in production (rather than just minifying the dev-version yourself - to save the error-msg bytes)...

Use the json-filter and the pre-tag

You might find it handy to be able to see the changes of an object on your scope as it develops over time. A great way to do this is by using the standard html pre-tag (for pre-formatted content) and the json-filter in Angular. Lets say you have a deep myObject. You can do <pre> {{myObject | json}} </pre> to show it and the binding magic of Angular will keep that up to date as you interact with the object.

Json

A couple of Angular tips

This is not really debugging, but rather tips that will make you happier when debugging different issues.

Use controllerAs

A very common problem when you have nested scopes has to do with inheritance. Lets say you have a scope with a title-property and a child-scope that also has a title-property. Because of the way inheritance works you can't access the parent title from your child view (I know you can do $parent.title, but thats just wrong and ugly). What the controllerAs syntax will let you do is assign each controller to a given name, so you can access their values that way. This needs some code for clarification (however this requires Angular v. 1.2+, which is newer than the Angular in the current umbraco backend, but Per promised me this will change soon).

Old way

Both myListCtrl and myItemCtrl injects $scope and sets $scope.title='different values here';

<div ng-controller="myListCtrl">
	<h1>{{title}}</h1>
	<div ng-controller="myItemCtrl"> I cant access the title-property on myListCtrl because myItemCtrl also has a property with this name </div>
</div>

New way

Now the controllers don't use the $scope to pass along values to the view, but instead use the controller instance. So values are saves like this.title='different values here';

Note that this is the new best practice and the direction Angular is going in the future (Angular 2.0 won't use $scope anymore).

<div ng-controller="myListCtrl as myList">
	<h1>{{myList.title}}</h1>
	<div ng-controller="myItemCtrl as myItem">Now I can access both {{myList.title}} and {{myItem.title}} and its much clearer which I want</div>
</div>

Use $destroy and .one()

Another problem you will run into when building larger Angular applications has to do with performance. When you have Angular directives and a view that changes a lot you can easily run into memory leak situations. Because of the way the garbage collector works in browsers (and because of the javascript spec) it can't free up memory by removing objects if they still have anything attached to them. Angular will cleanup after itself as best it can, but you should help along. When scopes are destroyed (a directive or view is removed) Angular will automatically remove all listeners to that scope. But if you have assigned any other listeners (like click handlers etc.) it won't know to clean this up when its destroyed. However it will broadcast a $destroy event on the scope just before removing it. You can (and should) listen for that event and clean up after yourself. If all you have is an event listener that will trigger the removal of the scope, why not just make this a one-time listener (by using .one() instead of .on())? Again lets look at some (admittedly stupid example) code - this time in a link-function of a directive:

link:function(scope,element,attr) {
	var killMe = function() {
		element.remove()
	};
	element.on('click',killMe);
	scope.$on('$destroy', function() {
		element.on('click',killMe);
	})
}
...or with a one-time binding:
link:function(scope,element,attr) {
	element.one('click', function() {
		element.remove()
	});
}
 

Thats all folks

I hope you learned a thing or two. Maybe you can teach me if I got something wrong. Lets keep learning Angular together! #h5yr

Filip Bech

Filip is on Twitter as