In a recent project (an XPages application with an Angular front end), I had a need to (a) run a script to determine user access and (b) store that value globally. In this post, I’ll show one way to achieve this with a synchronous AJAX call and a module run block.
Use Case
In this application, the front end pages need to know whether the user has Admin rights in the application in order to determine whether to display advanced options such as configuration links.
The security check needs to be run before the page is loaded and it needs to be stored globally so that the security check does not have to run constantly as many page elements are loaded.
Initial Implementation
I created a custom REST service that checks the user’s roles and returns a boolean value for whether the user is an Admin in the application. I called it via AJAX and stored the value in the $rootScope
.
I then set up the logic from each admin-controlled feature call a function to check whether the value existed in $rootScope
. If not, the function would make the AJAX call to look up the value and store it.
The code to call the REST service looks like this:
$http.get('rest.xsp/isUserAdmin').success(function (data) { $rootScope.isAdmin = (data == 'true') ? true : false; })
The logic seemed to be set up properly. However, it didn’t initially work as I expected.
The Problem
Watching the Net panel in the browser tools made it clear that the AJAX call was being made many times as each page loaded.
The AJAX calls were fired off so rapidly that numerous calls were made before any of them had a return value available in the $rootScope
.
It turns out that Angular’s AJAX calls are asynchronous by design. For the most part, this is great because it keeps the application more responsive. However, in this case, I needed the security check to complete before loading the rest of the page.
The Solution
Ultimately, I solved the problem by making two changes.
- Moved the role-checking code to a module run block so it would run before page load
- Replaced the Angular AJAX call with a standard jQuery AJAX call so I could add a parameter to force the call to be synchronous.
As the application loads, it will call the REST service and wait for the response. When done, it will store the value in the $rootScope
.
Here is a link to the Angular.js module documentation.
Run blocks are the closest thing in Angular to the main method. A run block is the code which needs to run to kickstart the application. It is executed after all of the service have been configured and the injector has been created.
A run block generally goes in the app.js
file rather than in a controller. Just add a run()
method to the application and include the required directive and code.
This code sets up a function in the run block that includes a synchronous jQuery AJAX call to an XPages REST service, and stores the return value in the $rootScope
for global access in the application. This runs immediately and the global variable is initialized and available when the rest of the page loads.
// Make the user access check available globally PSApps.run(['$rootScope', function($rootScope) { // Use jQuery for a synchronous http request -- angular hard-codes all requests to be asynchronous $.ajax('rest.xsp/isUserAdmin', {async: false}) .success(function (data) { $rootScope.isAdmin = (data == 'true') ? true : false; console.log('is user admin? ' + data); }); } ]);
Then, all places that need to check the value in order to determine whether to display an element can just reference the global property.
ng-show="$rootScope.isAdmin"
