Angular1 Debugging

angular1

https://docs.angularjs.org/guide/scope
https://www.youtube.com/watch?v=29tf5jfmhlM - AngularJS Debugging Quick Tip
https://www.youtube.com/watch?v=9pFG6EAkJD4 - How to debug your AngularJS applications- S03 EP01
https://www.youtube.com/watch?v=P8cIlVUuQLw - AngularJS Application Debugging - Patrick Persson
https://www.youtube.com/watch?v=BHqAcYjJDno - Debugging AngularJS on the browser console
https://www.youtube.com/watch?v=q-7mhcHXSfM - AngularJS Batarang

http://eng.localytics.com/tips-and-tricks-for-debugging-unfamiliar-angularjs-code/ - done reading once, should read again when needed.

// Angular 1 - Debugging:

angular.reloadWithDebugInfo();

Features of Chrome Developer Tools:

$0 - $4: Access the last 5 DOM elements selected in the inspector window.
$(selector): shortcut for querySelector
$$(selector): shortcut for querySelectorAll

We can access data for any scope on the page, inspect the scope hierarchy, inject 
services, and control directives.

var domElement = document.querySelector("...");
var ngElement = angular.element(domElement);

var scope = ngElement.scope(); // Get the $scope object from the element or its parent
var isolatedScope = ngElement.isolateScope();

Using the element's scope, we can inspect the scope's properties, such as 
custom variables, that we set on the scope in our controllers.  We can peek into 
the elements looking into its $id, its $parent object, the $watchers that are 
set on it and even manually walk up the scope chain.

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.

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

1. In Chrome, right click an element in the page you're interested
2. Select inspect element
3. The element is now highlighted in the inspector
4. In the console, enter $($0) to get the element. If not using jQuery, you can 
   use angular.element($0)

Properties inherited from parent scopes won't show up, but you can still type 
their name. So even if you don't see a property foo on your scope, you can still 
run angular.element($0).scope().foo and see its value if it is available from a 
parent.

You can look at the properties of the parent scope with $($0).scope().$parent. 
You can chain this too: $($0).scope().$parent.$parent

You can look at the root scope: $($0).scope().$root

If you highlighted an element inside of a directive with isolate scope, 
$($0).scope() will show what is available to it (ie: the isolate scope)

It can be a bit confusing, but try it out! It can be a lot faster than setting breakpoints 
and inspecting variables.

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:

1. Using the method in the previous section, find the element using the variable 
   and look at its scope.

2. Try to inspect the value: $($0).scope().foo if the property is called foo.

3. If the value is defined, you have your answer, the property came from the 
   controller or directive at that level.

4. Otherwise, go up one level in the DOM, and try again.

5. Repeat until you find it.

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:

1. You can use $($0).scope() directly: the inspector will only show the direct 
   scope's properties, not the ones defined on parents. So you can go up the 
   hierarchy until you see your property listed.

2. Use $($0).scope().hasOwnProperty('foo') to see if the property is set 
   directly on the scope you're looking at, and go up the hierarchy until it 
   returns true.

3. Keep going up until you no longer see your property. Just add .parent() to 
   the element to do it from the console: 
   $($0).parent().scope().hasOwnProperty('foo')

4. Look on the last element where you saw it: your property was defined in a 
   controller or directive defined on that element, or a close parent. It could 
   also be defined in the markup (like in the case of an ng-repeat). You can do 
   this by outputting the element directly in the console.

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

If we manually change the model (for debugging purpose):

scope.name = 'Mary'

we will need to run the $apply function:

scope.$apply()

This will update the DOM.

If our update is simple, we can directly change the model and call apply 
function in one shot:

scope.$apply('name = "Mary";');

We can also call any functions attached to the scope using the expression:

$scope.formName = function () { ... }
scope.$apply('formName();');

We can also do many steps if we pass a function to the $apply function, which 
will be executed in the scope's context BUT we need to inject the scope we want 
to use.

scope.$apply(function ($scope) {
  $scope.name = 'Alex';
});

We do not even need to know the exact id of the element to get the outer scope. 
For example if we have an HTML element inside the element with angular 
controller, we can still get the correct scope.  Consider:

<div id="app" ng-controller="MyApp">
  <div id="inner">
    My name is {{ name }}
  </div>
</div>

var el = document.getElementById('inner'); // inner is inside app
var ngEl = angular.element(el);
var scope = ngEl.scope();
scope.name // 'Joe' correct scope

If you want to send an event to all listening scopes, you can quickly do this 
using $rootScope:

// somewhere in the application
$scope.$on('draw-chart', function (event, arg1, arg2) { ... });
// from the browser console
angular.element(document)
    .injector()
    .get('$rootScope')
    .$broadcast('draw-chart', 'foo', 'bar');

If you do not know the node where the application has been boostrapped, you can 
find the element after bootstrapping using the attribute selector:

var app = angular.element(document.querySelector('[ng-app]'))

var controller = ngElement.controller(); // Get the element's controller (or its parent)
var controller = ngElement.controller('ngModel');

Some directives define a controller with certain additional (often shared) functionality.
To access the instance of a controller for a given directive, use the controller() function:
angular.element('my-pages').controller();

If we 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 do not see 
one, go up one level higher and look again.  If we have trouble finding an 
attribute in a ton of HTML, click on an element, run $($0).attr('ng-controller') 
and if nothing comes up, click on its parent, and repeat until you get a 
controller name back.

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

You can get to any service / constant / etc. from the console using an injector 
instance. Just grab the root element and ask for its injector instance.

var el = document.getElementById('app');
var ngEl = angular.element(el);
var di = ngEl.injector();
di.has('$rootScope');
// true
var $q = di.get('$q');
$q.when('hi')
  .then(console.log.bind(console));
// prints "hi"

var injector = ngElement.injector(); // Get the injector of the element or its parent.

With the injector, we can instantiate any Angular object, such as services, other 
controllers, or any other object.

We can grab a reference to any service using the injector function of the element
where ngApp was defined, or indirectly though any element with the ng-scope
class:

angular.element(document.querySelector('html')).injector().get('MyService');
angular.element(document.querySelector('.ng-scope')).injector().get('MyService');

We can then call methods on that service just like we could if we injected it.
var data = ngElement.inheritedData();

Angular uses the inheritedData() method to find data up the scope chain as it 
walks up the DOM until it found a particular value or until the top-most parent 
has been reached.

If we are using Chrome, we can find the element that we are interested in, right 
click on it in the browser, select "Inspect element".  The element is stored as 
$0, and we can get the Angular-ized element by calling angular.element($0)

Angular Batarang is a Chrome extension developed by the Angular team at Google.  
Batarang is a debugging tool for Angular apps.  To install Batarang, simply 
install it from the Chrome web store or from the GitHub repo:
https://github.com/angular/angular-batarang

Once installed, we can start up Batarang by launching the Chrome Developer Tool 
and clicking enable to enable Batarang to start collecting debugging information.

Batarang allows us to look up scopes, performance, dependecies, and other key 
metrics in our Angular apps.

After we've started up Batarang, the page will reload, and we'll notice that we 
have a panel that enable us to select different scopes in our page.  We can 
select a scope by clicking on the plus button, finding the element that we are 
interested in and clicking on it.  Once we select a scope using the inspector, 
we can look at all the different properties on our scope element and their 
current values.

We can also peek into the performance of our application by using the 
performance section of Batarang.  In this panel, we can get a peek into the 
watch list of the application at the different scopes as well as the amount of 
time that each expression takes, both in absolute time and percentage of the 
overall application time.

We can visualize the dependency graph line.  We can look at the dependencies of 
our application and view the different libraries of our application to see 
what they depend on and track libraries that aren't dependencies of the 
application at all.

Batarang allows us to look deep into the application on the page itself.  Using 
the Options panel, we can look at:

1. Applications: the different applications that are on a single page (the ngApp 
   directive uses).

2. Bindings: the bindings that are set in the view, where we use either ng-bind 
   or elements that are surrounded in the template tags {{}}.

3. Scopes: the scopes in the view that we can target and inspect more deeply.  
   The options panel also allows us to view the Angular version of the app and 
   what we're using or not using from a CDN.

Sometimes I want to test if the application's exception handler really catches 
errors. To cause the exception inside the app we can execute this in the browser 
console:

angular.element(document.body).injector().get('$rootScope').$apply(
  function bad() { 'use strict'; bad = 'bad'; }
);

The 'use strict' is necessary to make sure bad variable is a true error, and 
does not create new property on the window object.

// Quick way to locate a function:

Imagine we have a button with a click handler: 

<button ng-click="doSomething()">Do it</button>. 

We do not know where the function doSomething is defined, but we can still 
easily pause the execution inside of it.

Select the button in the "Elements" tab. This makes the button element available 
under $0 variable.  We can tell the Chrome browser to stop once the function 
doSomething starts:

debug(angular.element($0).scope().doSomething)

Now, click on the button and the debugger should pause the execution on the 
doSomething function for us to inspect.  If we want to remove the breakpoint, 
use the undebug function:

undebug(angular.element($0).scope().doSomething)

// Other tools:
ng-inspector
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License