How to modify results from a read-only ng-app?

Arron B. :

I apologize if this is a duplicate, just haven't been able to find anything close to this myself.

The company I work for has an online reporting system that is run by an ng-app applied directly to the body tag. I have been tasked with modifying the result that returns from this ng-app. Following code is called using onload attached to the body tag.

function getElements(){
  var list;
  list = document.getElementsByClassName("neutral");
  [].forEach.call(list, function (listItem) {
       addNeutral(listItem);
  });

...

Basically, trying to find anything with class "neutral" and apply results from another function to it. The addNeutral function is basically just

element.classList.add("neutralHighlight");

This code seems to run and gathers the correct list of elements, but the new class is never added and no errors occur. So long story short, is there any way to modify the output of a ng-app using code separate from the ng-app itself? Any guidance would be greatly appreciated.

Update 3/5/20

So I implemented Shaun's response and it still isn't working properly. With some debug messages, I can see that it collects the "list" variable as an HTMLCollection. The forEach function doesn't seem to even trig

function getElements(){
  var list;
  list = document.getElementsByClassName("neutral");
  console.log(list); //Debug - Shows in console
  [].forEach.call(list, function (listItem) {
       console.log(listItem); //Debug - Does not show in console
       addNeutral(listItem);
  });
  }

  function addNeutral(element){
    angular.element(element).addClass("neutralHighlight"); 
    console.log("!!!end addNeutral"); //Debug - Does not show in console
  }

Update 3/9/20 -SOLUTION-

Application is returning the HTML Collection, but it displays with a length of 0 (still displays the objects, but I think that's a Firefox console thing). When trying to loop through the list items, it returns null for the first item, so the function is still being called before the Angular app loads completely.

That being said, I messed around with things a bit this morning and came to a solution! I ended up using the setInterval function with a 5 second interval (since I need it to update, I may change this to optimize it later by adding onChange items to the objects I grab initially). The setTimeout that was proposed would have worked with a delay added to it. This probably isn't the most elegant solution, and there's probably a better way to do it, but this works for anyone interested.

function getElements(){
  var list;
  list = document.getElementsByClassName("neutral");
  for (i = 0; i <= list.length; i++){
    var listItem = list.item(i);
    addNeutral(listItem);
  }
}

function loadFunction(){
  setInterval(function(){getElements()}, 5000);
}

I added <script>loadFunction()</script> right before the closing HTML tag to execute.

Shaun E. Tobias :

You might be able to get around this by using the angular object in your code and adding the class on an angular.element instead. AngularJS doesn't use a virtual DOM but it does use its own node references (which is what makes it so tricky to work with outside of the framework, as Lex pointed out in the comments of your question). Try:

angular.element(element).addClass("neutralHighlight");

Yes, you have access to angular outside of the app! And a last note, addClass() is available on angular.element because AngularJS comes with jqLite.


Further investigation

It looks like the above solution works if the class 'neutral' is being added in angular via the class attribute, but it looks like your app may be adding it programmatically with the ng-class directive after the DOM has rendered.

I wrapped your getElements() function in a setTimeout():

setTimeout(getElements);

This is unfortunately not a guarantee that the ng-class update will have taken place, but what it does is it executes the function after the previous digest cycle has completed and that appears to be working.

An even safer solution would be to use document.ready but again with the angular.element wrapper. This will ensure the initial DOM state has been rendered by AngularJS, including applied directives:

angular.element(document).ready(function() {
  getElements();
});

EDIT: Update 3/9/20 -SOLUTION-

The solution proposed in the answer is almost identical to the setTimeout() answer given here. The only difference is setInterval() will keep executing the code every 5 seconds until you tell it to stop.

You can do this with the following:

var loadFunction = setInterval(function() {
  var el = getElements();
  if (el) clearInterval(loadFunction);
}, 5000);

And just return a bool in your getElements() like so:

function getElements() {
  var list;
  var found = false;
  list = document.getElementsByClassName("neutral");
  [].forEach.call(list, function (listItem) {
       addNeutral(listItem);
       found = true;
  });
  return found;
}

See: codepen.io/shaunetobias/pen/KKpXRxq

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=198587&siteId=1