Angular from browser console

Access angular model and update DOM from the browser console.

If there is an AngularJs app running in the page, it is often hard to debug or see the current model (stored in the $scope variables). There are browser extensions ng-inspector and Batarang, but what if you just want to see values, maybe change something quickly? Here is how to do this from the browser's JavaScript console.

I made the following example that you can try this on. You can enter the commands in the middle column (that acts like browser console). We have the following HTML and JavaScript on the page

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.min.js"></script>
<meta charset="utf-8">
<title>Manual angular update</title>
<script>
function MyApp($scope) {
$scope.name = 'Joe';
}
</script>
</head>
<body ng-app>
<div id="app" ng-controller="MyApp">
My name is {{ name }}
</div>
</body>
</html>

We have variable name attached to the scope in the div app. Let us change the variable to something else, like Mary and see the change on the page.

Grab the element and scope

We first start by getting the reference to the scope.

1
2
3
4
var el = document.getElementById('app');
var ngEl = angular.element(el);
var scope = ngEl.scope();
scope.name // 'Joe'

If we include full jQuery library before including angular, we can get the wrapped element in a single request var ngEl = angular.element('#app');

Even without full jQuery library, inside Chrome DevTools we can select an element using $(<selector>) syntax. For example to grab the isolate scope from a directive 'MyWidget' that creates <my-widget> node

1
angular.element($('my-widget')).isolateScope();

Change the model / DOM

Let us change the name to a new value

1
scope.name = 'Mary';

Changed variable in scope

Notice, that the page has not changed yet. We have only changed the model. The Angular engine has no idea that it needs to update any HTML nodes bound to the model. To update them, we can execute $apply function that acts on a scope

1
scope.$apply();

Applied changes

If our update is simple (as above), we can directly change the model and call apply function

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

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

1
2
3
4
// app code
$scope.formName = function () { ... }
// console
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.

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

Inner elements

You 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

1
2
3
4
5
<div id="app" ng-controller="MyApp">
<div id="inner">
My name is {{ name }}
</div>
</div>

From the console use id inner

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

Broadcast an event

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

1
2
3
4
5
6
7
// 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 the angular application wraps body element instead of the document, you can still use it easily

<body ng-app="...">
angular.element(document.body)
    ...

Get the root application node

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]'))

Chrome DevTools shortcut

Sometimes getting the exact element selector is difficult. Instead you can use the following method

  • Open the Chrome DevTools elements panel
  • Select the looking glass tool
  • Pick the element on the page

The selected element will be highlighted in the DOM

selected element

The Chrome DevTools has the shortcut $0 to refer the currently selected element. You can use this shortcut to quickly get the Angular scope.

grab scope of the currently selected element
1
angular.element($0).scope();

Quick, isn't it?

Inject a service

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.

1
2
3
4
5
6
7
8
9
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"

Inject something

Usually the document.body has angular application around it, so if you need to grab something

angular.element(document.body).injector().get('some name');

Cause an exception

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.

Stop inside a called 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. Tell the Chrome browser to stop once the function doSomething starts

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

That's it. If you want to prevent the stopping (remove the breakpoint), use undebug function.

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

Pretty cool.