As a front-end developer, I occasionally write user scripts1 to enhance my browsing experience.
For traditional server-rendered pages, you can directly execute scripts on the page to achieve your goals. However, for modern data-driven front-end development frameworks, such as Angular, the old approach no longer works. You need to find a way to participate in the rendering of the page.
So, how can we modify an Angular page?
First of all, I want to clarify that I mainly work with React, so please correct me if I make any mistakes.
Accessing the Angular Instance#
After doing some Google searches, I found this gist that allows you to directly access the Angular instance.
Let me explain the code:
Since Angular injects itself into window.angular
, we can use the angular.element
method to generate an Angular object.
Follow the instructions in the gist to execute the code snippet.
Once we have access to the Angular instance, it becomes easier to debug and analyze the data flow of the web page. We can watch
on the rootScope
and obtain the corresponding scope
for modification.
Analyzing the Data Flow of the Web Page#
Normally, we cannot click on this user action button because the disabled
attribute is set to true
. To inspect the DOM of this button, enter $scope
in the console (you need to execute the code snippet in the web page as mentioned in the gist).
Try modifying $scope.disabled = false
and then click on the button. It should be clickable now.
However, if we switch to another page and then switch back, we won't be able to click on the button again. The root cause is that we haven't affected the innermost data flow.
Listening to Page Changes and Continuously Modifying Data#
The following script was originally based on an Angular watch-related question on StackOverflow. Unfortunately, I couldn't find the link to the original question, so I apologize for not providing proper attribution.
So, how can we access the Angular object in our user script?
var retryCount = -1,
maxRetry = 5,
timeout = 1000,
timer;
(function initScript() {
window.clearTimeout(timer);
if (!window.angular) {
retryCount++;
if (retryCount < maxRetry) {
timer = setTimeout(initScript, timeout);
}
return;
}
setTimeout(injectScript, 1000);
})();
We can block and wait for Angular to load successfully. Then, execute injectScript
to run our own script.
function injectScript() {
var ngAppElem = angular.element(
document.querySelector('[ng-app]') || document
);
$rootScope = ngAppElem.scope();
$rootScope.$watch(function () {
// do something.
});
}
We execute watch
on the rootScope
, so it won't be lost when switching pages.
function injectScript() {
var ngAppElem = angular.element(
document.querySelector('[ng-app]') || document
);
$rootScope = ngAppElem.scope();
$rootScope.$watch(function () {
var elem = angular.element(
document.querySelector(
'#dashboardmainpart > div > div.EventBottomChartsContainer > div.EventDetailContainer > div > ul > li:nth-child(3)'
)
);
var s1 = elem.isolateScope() || elem.scope();
if (s1) {
s1.disabled = false;
return s1.disabled;
}
return s1;
});
}
Then, we retrieve the scope of the desired location and modify it directly. The watch
function will be executed every time the page is modified.
You can refer to the complete demo here: