Using AngularJS in UI Pages
Recently I had the requirement to create a popup window on the incident form which asks the user additional questions (different ones depending on the Business Service) to enrich the description field with relevant information.
The right way to accomplish this is to create a UI Action on the form that opens a GlideModal which contains a UI Page. So far so good, three simple components that can together fulfill my requirement.
But soon after starting with coding I once again realized how much pain it is to develop Jelly code. UI Pages rely on Jelly and cannot be used without it. Right? I didn’t want to accept this and tried to find a way to use AngularJS instead. It would be so much easier. ServiceNow already ships it for most other purposes so why can’t we also use it in UI Pages?
So together with a colleague I dug into the hidden parts of ServiceNow and after some trial and error we actually managed to get it running. Implementing my new feature was a piece of cake then using the AngularJS functionality. And as it was so helpful for me and definitely will be in the future I want to share with you how we did it.
For demonstration purposes I will here show and describe a popup that will look and act as follows:
Part 1: The UI Action
First we need our UI Action which enables the user to click a button to open the popup. This step is very short and simple.
For a very basic example I created a UI Action as follows:
- Name: Show Angular UI Page
- Table: Incident
- Action name: open_popup
- Client: true
- Form button: true
- Form style: Primary
- Onclick: showDialog()
- Script:
function showDialog(){
var gm = new GlideModal("angular_ui_page");
gm.setTitle('This is an AngularJS UI Page');
gm.render();
}
Nothing special here and just 5 lines of code for the most simple example. Note that you need to instantiate the GlideModal with the name of the UI Page you want to display and set the title to what you want to see in the header of the popup.
Part 2: The UI Page
If you want to follow my basic example you can fill out the fields as follows:
- Name: angular_ui_page
- Category: General
- HTML:
<html>
<!-- necessary for pages that do not contain angular yet:
<sript src="/scripts/angular_includes_no_min.1.5.11.js"></sript>-->
<!-- HTML Angular code for the popup -->
<sript id="angular-template" type="text/template">
/*
<div ng-controller="angularController">
Type a color: <input ng-model="name"/>
<h2 style="color: {{name}}">You entered: {{name}}</h2>
</div>
*/
</sript>
<!-- The HTML tag that will contain the inserted code -->
<div id="angular-app"></div>
</html>
var angularApp = angular.module('myApp', []);
angularApp.controller('angularController', function($scope) {
$scope.name = 'Blue';
});
var appElement = angular.element('#angular-app');
var escapedText = angular.element('#angular-template').text().replace(/^\s*\/\/\s*\/\*\s*|\s*\*\/\s*\/\/\s*$/g, "").replace(/</g, "<").replace(/>/g, ">");
var app = angular.element(escapedText);
angular.bootstrap(app, ['myApp']);
appElement.append(app);
That is already it!
And now the explanation of my scripts:
The UI Page HTML
By default every UI Page starts with the following two lines:
<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
This tells the browser to parse the code as Jelly code and instantiates some variables and namespaces. But as we don’t want to use Jelly we can simply replace this with:
<html>
(remember to also replace /j:jelly with at the end)
When developing a web application with AngularJS you would then normally write
<div ng-app="myApp" ng-controller="angularController">
Type a color: <input ng-model="name"/>
<h2 style="color: {{name}}">You entered: {{name}}</h2>
</div>
<script>
var angularApp = angular.module('myApp', []);
angularApp.controller('angularController', function($scope) {
$scope.name = 'Blue';
});
</script>
and everything would work. But ServiceNow has its own protection mechanisms and doesn’t allow AngularJS to run in the HTML section of a UI Page. If you look into the developer tools of your browser you will see that the code will not be executed:
Let’s circumvent these protection mechanisms to run our code where we want it!
First of all we move the script part into the Client script section of the UI Page. There it is allowed to run. But we also need to move the HTML there because the AngularJS expressions (wrapped by curly braces {{}}) need to be interpreted as well.
The most developer friendly way to do this is to write the code in the HTML section (because there we have syntax highlighting) in a script template and then inject it into the actual HTML tag where we want to display the contents. The HTML will then look like this:
<html>
<!-- HTML Angular code for the popup -->
<sript id="angular-template" type="text/template">
<div ng-controller="angularController">
Type a color: <input ng-model="name"/>
<h2 style="color: {{name}}">You entered: {{name}}</h2>
</div>
</sript>
<!-- The HTML tag that will contain the inserted code -->
<div id="angular-app"></div>
</html>
Note: If you are not using the UI Page as a popup on a form then the AngularJS library might not be available yet. In this case you can reference the Angular Script Include at the beginning which is used by ServiceNow in other pages:
(also note that I had to write "sript" instead of "script" because the article would otherwise remove the attributes)
The UI Page Client Script
Alright, the HTML is written and now we need to breathe life into it by getting AngularJS to run.
As we know, everything an AngularJS application needs is a module and a controller that are bound to an HTML element. This is exactly the part which we moved here from the script tag we initially wanted to use in the HTML section:
var angularApp = angular.module('myApp', []);
angularApp.controller('angularController', function($scope) {
$scope.name = 'Blue';
});
As we need to inject the template into the HTML so that Angular can run we additionally need to fetch that DOM element and bootstrap AngularJS in it before adding it back to the DOM:
var templateText = angular.element('#angular-template').text();
var app = angular.element(templateText);
angular.bootstrap(app, ['myApp']);
var appElement = angular.element('#angular-app');
appElement.append(app);
Unfortunately ServiceNow has some protection mechanisms in place here too so at this point the HTML will look as follows in the browser console:
All angle brackets (< and >) are escaped and there are also leading and trailing slashes around the code. So what we do is to add our own markers (/*) to the template and replace all these elements back to how they should be using some additional code:
var escapedText = angular.element('#angular-template').text().replace(/^\s*\/\/\s*\/\*\s*|\s*\*\/\s*\/\/\s*$/g, "").replace(/</g, "<").replace(/>/g, ">");
Now we finally get the result we were looking for:
The code is added correctly and fully working.
https://www.servicenow.com/community/now-platform-articles/using-angularjs-in-ui-pages/ta-p/2311969