I am working with Primo, an app written in AngularJS by the company Ex Libris. It's a library catalog, allowing users to search the library's physical and digital collections. I do not have access to the original AngularJS code, but I do have the ability to define and attach AngularJS modules.
My current objective is simple on the face of it: I need a link from Primo out to a third-party app, including a query string containing the ID of the item the patron is looking at, e.g. https://library.example.edu/ill/primo.php?docID=cdi_proquest_ebookcentral_EBC7007769
.
I was able to add the link using the Primo control panel. It let me set the URL, the link text and so on. Here is a screenshot of that bit of the control panel:
The URL Template lets you enter the URL for your link, and has some capacity to put in tokens that will be replaced with data about the item, e.g. {rft.atitle}
would be replaced with the title of an article. Sadly, it does not provide a way to insert the docID. I tried putting in an AngularJS token, like {{pnx.control.recordid[0]}}
, which I think would get the data I need. But the UI rejected it as an invalid parameter. So I specified my URL as https://example.com/incoming.php?docID=replaceMe
. The plan was to write JS to retrieve the docID from the live URL and update the link before the user has a chance to click it.
Unfortunately, Primo created the link as an AngularJS component. Here is the code it generates when the link is built in an actual page:
<md-list-item ng-repeat="service in $ctrl.filteredServices() track by $index" role="listitem">
<button type="button" ng-transclude="" (click)="$ctrl.onClick(service)" aria-label="Interlibrary Loan, opens in a new window">
<span ng-if="service['service-type'] === 'OvL'">
<a
ng-href="https://library.example.edu/ill/primo.php?docID=replaceMe"
ng-click="$event.preventDefault()"
translate="Interlibrary Loan"
href="https://library.example.edu/ill/primo.php?docID=replaceMe"
>Interlibrary Loan</a>
</span>
</button>
</md-list-item>
For the sake of clarity I have removed some container DIVs and most of the styling information. I also cleaned up the indentation.
As you can see, the link shows up with both an ng-href
attribute and an href
attribute recording the URL I entered. I wrote JavaScript to update those. But it had no effect. Updating the attributes and then clicking on the link takes you to the original unmodified URL. I believe that what's happening is that AngularJS is recording the original URL somewhere in its data structure when it processes the URL, then disabling the normal onClick
handler for the link. When you click the link, AngularJS sends you to the URL it stored, not the one currently recorded in the tag's attributes.
You may also note that it added a <button>
wrapped around the link. That duplicates the functionality of the link: tapping or clicking it takes you to the same URL. I think it's there to increase the clickable area, but complicates things because the URL needs to be updated in two places.
All I need is to swap replaceMe for the actual docID and update that URL. I could brute force this by using DOM manipulation to clone the entire HTML structure that AngularJS produced, delete the original and then assign my own onClick handlers to the button and the link. But I would rather work with AngularJS rather than fighting against it.
So my questions are:
- How can I figure out where AngularJS is saving the URL? I don't know where to look.
- Once I've found it, is there an AngularJS-specific way to update it? Some frameworks get tetchy when you twiddle their bits without using their own methods.
Googling led me to people writing AngularJS code, not updating existing data at run time. The documentation makes similar assumptions. I've been working on this for three days now, and I'm getting nowhere. Any help would be appreciated.
EDIT, six days later:
I am getting closer. And yet so far.
The first thing I did not understand was the the original developers at Ex Libris have disabled debug info:
$compileProvider.debugInfoEnabled(false)
After more googling I found a Firefox browser extension called AngularScope which promised to let me examine the scope of arbitrary DOM elements. When I installed it, it let me know that debug info was disabled. That let me learn that I can turn that back on from the console using:
angular.reloadWithDebugInfo()
Once I learned that I was able to determine that the scope for my link contains an object named service
with a property named link-to-service
. That's where it's storing the URL. So I got a reference to the link I was looking at in a variable named myLink
and wrote the following:
angular.element(myLink).scope().$apply(function () {
angular.element(myLink).scope().service["link-to-service"] = newURL;
});
And it works! It updates the URL, and then clicking the link (AND the button) goes to the correct page.
But.
But it only works when AngularJS's debug info is enabled. Which, by default, it is not. The AngularJS scopes do not get attached to the DOM, and so angular.element(myLink).scope()
is undefined and useless.
One of the answers on the question AngularJs scope variables in console suggested that I could get the scopes back by executing the following code:
var scope;
angular.element(document.body).injector().invoke(function ($rootScope) {
scope = $rootScope;
console.log(scope);
});
Unfortunately it runs into the same problem, reporting that angular.element(document.body).injector()
is not defined. I suspect that may have been a change made after that answer was filed in 2015.
I tried defining a new AngularJS module and using its .run()
method to manipulate the DOM after AngularJS bootstrapped itself but before it compiled its templates. It had no effect; probably because by the time my module got loaded in (well after page load time) AngularJS has already compiled at least once.
I thought about setting up an event handler using onDOMContentLoaded
to update the URL before Angular could get to it. But then realized that wouldn't work because my JS is being added to the page by AngularJS. By the time my script ran, that event would already be long in the past.
So I guess the question has now become: How do I get access to the scope of an AngularJS component when debug info is not enabled?
If I could just get access to the scope, updating the URL is trivial. And yet, somehow I've been working on this for nine days now. I am getting sorely tempted to just write JS to rip out the entire HTML structure that AngularJS built and then rebuild it myself, just so I can update the URL. It would be awkward and horrifically inefficient. But it would work, and then I could move on with my life.