Code Buckets

Buckets of code

Angular

Simple Debug Panel for AngularJS

I am aware that there are many many other ways to get debug information from an AngularJS application but that’s not stopped me implementing another one. I do actually find a customised output of JSON objects useful particularly when working with deep object graphs. The implementation also is useful as a worked example of a couple of AngularJS principles.

The Demo Application

The demo application is a bookshelf application that searches the google book API and enables the user to add books to their own library. This is my ‘go to’ demo application that I’ve implemented a couple of times in different frameworks. This one is using the MEAN stack and the source code can be found here.

The Problem

When I search the google API I get a very large complex JSON object as the output. I want to know what is in there as I’m saving bits of it in my own database and might be interested in other bits. I want it displayed but easy to toggle on and off. I’m ultimately going to read something from the query string to do this as it is easy to toggle with minimal fuss.

When it’s finished it’s going to look like this…

debug panel
Google Bool API results displayed in debug panel

I’m going to use bootstrap for styling throughout as I’m not hugely concerned about how it looks – it’s just got to be reasonably neat. So its bootstrap classes throughout I’m afraid.

The Solution

The solution is going to demonstrate the following

  1. The use of directives in AngularJS
  2. Using global values and config
  3. Setting globally accessible values from the query string.

    Step 1: Implement a debug panel

    The initial implementation is directly on the search page. When I do a search I want to see the following debug output

    debugpanelonsearch
    Debug panel displayed on book search page

Before I do a search I don’t want to see anything.

The implementation is

<!— book details page -->
 <div class="row" ng-show=" searchResults ">
    <div class="col-md-8">
        <hr />
        <pre>{{searchResults | json}}</pre>
    </div>
</div>
<!— rest of book details page -->

The scope has a property SearchResults with a giant json object direct from the GoogleAPI. I want to show this. I use the json filter to prettify the result and it’s wrapped in pre tags so it displays like code. The ng-show attribute hides it if the searchResults is null or generally falsy which is what I want. That’s pretty good and works for me.

Step 2: Make it a directive so it can be reused

Now I want this on every page and I want it to display anything I want not just the searchResults. I want it to be generally useful. To achieve this I’m going to use directives which allow me to include fragments of HTML across different parts of my site. I now want it on the book details page as well.

The first job is to extract out the debug panel into a separate html file and to remove the dependency on searchResults. The debug panel html is extracted and saved into its own file at

{root}/views/directives/debugPanel.html

And it looks like

<div class="row" ng-show="display">
    <div class="col-md-8">
        <hr />
        <pre>{{display | json}}</pre>
    </div>
</div>

This is the contents of the entire file now. I’ve removed the binding to searchResults and I have now got a property called ‘display’. This is any JSON object I want to display in my results panel.

Next job is to tell my angular application about my new directive. So I create a new JavaScript file and put this in

angular
      .module('app')
      .directive('debugDisplay', function(){
          return {
              scope:
              {
                display : "=display"
              },
              templateUrl: "/views/directives/debugDisplay.html",
          };
      });

This does a few things. It tells angular where to find the html fragment for display via the templateUrl property. More interesting it also uses scope isolation to feed in the JSON object I want to display.

              scope:
              {
                display : "=display"
              },

The scope property is the link between the page and the directive. I can insert my parent page scope into an attribute called display and this becomes available to the directive. It breaks the hardcoded dependency on the parent scope and makes it all more flexible. The ‘=’ in ‘=display’ tells angular it is an attribute.

That’s probably not hugely clear but finishing the example should help us. The last step is to reference the directive on the book details page. I need to remember to include the script

<script src="app/directive.debugDisplay.js"></script>

Then the directive is referenced as markup which looks quite neat

<debug-display display="vm.book"></debug-display>

So the display attribute is bound to the book property which is then fed through to the debug panel and displayed. I can put anything into it not just searchResults as before.

debug panel on book details
Debug panel displayed on book details page

I can now go back to the search page and implement the directive in the same way.

<debug-display display="vm.searchResults"></debug-display>

And that will display my search results in prettified json which I quite like. It’s even better now and I am mentally slapping myself on the back. However I’m not finished. I don’t always want to see the debug panel. Sometimes I just want to see the page as it will be when it goes live with no debug information at all. I want to be able to toggle it on and off with ease.

Step 3: Using a global setting to toggle the debug panel

I have ambitions for my BookShelf demo site to be truly huge; a site which will make eBay look small. To that end I don’t want to have to turn my debug panel on and off manually everywhere it is used. I want one point of control.

I’m going to set up a DebugOn flag when the application initialises. For this I could use a global constant like so

app.constant('DebugOn', 1);

However (spoiler alert) I’m going to want to amend them later on so a global value is more the way to go. I’m setting this when the app initialises so it will be

(function () {
    'use strict';

    var app = angular.module('app', ['ngRoute', 'infinite-scroll']);

    // routing and other set up

    app.value("DebugOn", 1);
})();

I can use the built in dependency injection to access the global variable in any service, filter etc.. so I’m going to use that to grab it in the directive script …

angular
  .module('app')
  .directive('debugDisplay', ['DebugOn', function(DebugOn){
      return {
          scope:
          {
              display : "=display"
          },
          templateUrl: "/views/directives/debugDisplay.html",
          link: function(scope, elem, attrs) {
              if(DebugOn == 0){
                  for(var i = 0; i < elem.length; i++)
                    elem[i].innerText = '';
              }
          }
      };
  }]);

The directive has become a bit more complex but it’s not too terrifying. I’m taking the DebugOn value as a DI injected parameter – very standard Angular stuff. I want to hide the debug panel if it is set to 0. In essence I want to change the DOM if the DebugOn variable is 1 and the link property enables us to do that.

          link: function(scope, elem, attrs) {
              if(DebugOn == 0){
                  for(var i = 0; i < elem.length; i++)
                    elem[i].innerText = '';
              }
          }

The elem parameter is a collection of all the directive element outputs in the app at that time. For the debug panel it will be one but I will be good and iterate through it anyway – we just obliterate the innerText and thus hide them from the user.

Now we have done this turning the debugging panel off becomes a simple matter of amending the value of DebugOn variable in one place and restarting the application.

    app.value("DebugOn", 1);

Just what I wanted.

Step 4: Using the query string to toggle the debug panel

But it’s not enough. It never is. I’m not satisfied with amending the application – I want to just pop something on the query string, have my application pop into debug mode and have my debug panels spring into life like desert flowers in the rain. I want it and I can have it.

http://myapplication#angularstuff?DebugOn=1

Should turn on the debugger but as I’m navigating around my single page application my querystring is going to disappear. I could maintain it but I don’t want to – it seems like hard work. So I’m just going fall back to some good old fashioned JavaScript. The full url is available in

window.location.href;

So I’m going to use a couple of functions to grab the value and put it in the global variable

function getDebugParam(){
    var debugParam = getParameterByName("Debug");
    if(!debugParam) debugParam = 0;
    return debugParam;
}

function getParameterByName(name, url) {
    if (!url) {
      url = window.location.href;
    }

    name = name.replace(/[\[\]]/g, "\\

amp;");
    var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
        results = regex.exec(url);

    if (!results) return null;
    if (!results[2]) return '';
    return decodeURIComponent(results[2].replace(/\+/g, " "));
}
And the setting of the debug variable becomes

app.value("DebugOn", getDebugParam());

And done. I done even need to change my app to get the debug panels on. A quick fiddle with my querystring and there they are. Lovely.

Further Development

As much as I like the query string idea I wouldn’t want to see it in production. As an immediate project I would want to disable this in prod environments. It’s not hard but needs to be done. I’ve got other ideas around multiple panels, debug levels and improved display but I’ll leave those for another day.

That’s it – end of post other than notes. Happy New Year everyone for 2017.

Full Script

There is a bunch of code here so to aid the discerning coder – this is the all code needed for the debug panels

directive.debugDisplay.js

(function () {
    'use strict';

    angular
      .module('app')
      .directive('debugDisplay', ['DebugOn', function(DebugOn){
          return {
              scope:
              {
                  display : "=display"
              },
              templateUrl: "/views/directives/debugDisplay.html",
              link: function(scope, elem, attrs) {
                  if(DebugOn == 0){
                      for(var i = 0; i < elem.length; i++)
                        elem[i].innerText = '';
                  }
              }
          };
      }]);
})();
debugDisplay.html
<div class="row" ng-show="display">
    <div class="col-md-8">
        <hr />
        <pre>{{display | json}}</pre>
    </div>
</div>
utils.js
function getDebugParam(){
    var debugParam = getParameterByName("Debug");
    if(!debugParam) debugParam = 0;
    return debugParam;
}


function getParameterByName(name, url) {
    if (!url) {
      url = window.location.href;
    }

    name = name.replace(/[\[\]]/g, "\\

amp;");
    var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
        results = regex.exec(url);

    if (!results) return null;
    if (!results[2]) return '';
    return decodeURIComponent(results[2].replace(/\+/g, " "));
}

app.module.js
(function () {
    'use strict';

    var app = angular.module('app', ['ngRoute']);

    //.. other setup such as routing

    app.value("DebugOn", getDebugParam());
})();

Note about Immediately Invoked Function Expressions

I haven’t wrapped all my examples in an IIFE. This is just to save space and reduce nesting in the examples. Just mentally put them back in if they are absent i.e.

(function () {

      //.. my examples

})();

Useful Links

AngularJS directives
https://docs.angularjs.org/guide/directive

Scope isolation
https://thinkster.io/egghead/understanding-isolate-scope

AngularJS Constants and variables
http://ilikekillnerds.com/2014/11/constants-values-global-variables-in-angularjs-the-right-way/

Quick explanation about IIFE
http://stackoverflow.com/a/8228308/83178

The getParameterByName function is from the incredibly highly upvoted answer on Stack Overflow
http://stackoverflow.com/a/901144/83178

Google Books API as referenced throughout demo code
https://developers.google.com/books/

LEAVE A RESPONSE

Your email address will not be published. Required fields are marked *