Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use angularjs with greasemonkey to modify web pages?

I want to modify web pages' behavior using angularjs and greasemonkey. I want to know, what's the best way to do it? Should I use jquery to inject attributes like "ng-*" to DOM elements before I can write some angular code? Or can I solely stick to angularjs?

Thanks.

like image 599
user133580 Avatar asked Mar 14 '14 03:03

user133580


1 Answers

There's a general answer about dynamically modifying AngularJS content in the DOM from JavaScript code here:

AngularJS + JQuery : How to get dynamic content working in angularjs

To sum up, when you put ng-* attributes into the DOM from JavaScript code, they won't automatically get hooked up; but AngularJS provides the $compile function for hooking up new HTML content with AngularJS attributes from JavaScript.

So what does this mean when it comes to Greasemonkey/Userscript?

For the purposes of this I'm assuming that your Greasemonkey script is modifying an existing page that already uses AngularJS, and the AngularJS content you want to add uses some of the variables or functions in AngularJS scopes already on that page.

For those purposes:

  1. Get a reference to $compile from AngularJS' dynamic injection system
  2. Get a reference to the AngularJS scope that you want your HTML code to be connected to
  3. Put your HTML code with ng-* attributes in a string and call $compile on it and the scope.
  4. Take the result of that and put it into the page using the usual jQuery-style ways.

To illustrate, here's a little script for CERN's Particle Clicker game, which adds a stat under the 'workers' section.

$(function () { // Once the page is done loading...

   // Using jQuery, get the parts of the page we want to add the AngularJS content to
   var mediaList = $('ul.media-list');      
   var medias = $('li.media', mediaList);

   // A string with a fragment of HTML with AngularJS attributes that we want to add.
   // w is an existing object in the AngularJS scope of the
   // <li class="media"> tags that has properties rate and cost.
   var content = '<p>dps/MJTN = <span ng-bind="w.rate / w.cost * 1000000 | number:2"></span></p>';

   // Invoke a function through the injector so it gets access to $compile **
   angular.element(document).injector().invoke(function($compile) {

       angular.forEach(medias, function(media) {

           // Get the AngularJS scope we want our fragment to see
           var scope = angular.element(media).scope();

           // Pass our fragment content to $compile,
           // and call the function that $compile returns with the scope.
           var compiledContent = $compile(content)(scope);

           // Put the output of the compilation in to the page using jQuery
           $('p', media).after(compiledContent);

       });
   });

});

** NB: Like any AngularJS function that uses its dependency injection, .invoke uses the parameter names of the function you pass to it determine what to inject, and this will break if you're using a minifier that changes the parameter names.

To avoid this you can replace

.invoke(function($compile) { ... });

with the form

.invoke(['$compile', function($compile) { ... }]);

which won't break if the minifier changes the parameter name to something other than $compile.

like image 61
rakslice Avatar answered Sep 29 '22 11:09

rakslice