简体   繁体   中英

How to make angular refresh screen in the middle of execution

I have a little angular html in a third's page.

Everything works fine, the code works fine, the directives work fine (except "ng-show" which never works, but that doesn't matter here).

But I have a heavy code that runs in a button click (with ng-click ). During the execution everything freezes, ok, that's expected.

But I wish very much to make an "img" appear during the execution, but it seems nothing is updated during the code (everything is perfectly updated after the code, but not during the code).

How can I "refresh" the angular html "during" the execution of the code in "ng-click"?

I've tried "$scope.apply()" and "$scope.digest()", but both cause some weird errors in the console....


EDIT

Thanks to the answers below: The errors were indeed because the "apply/digest" was already in progress. By using the assynchronous methods shown, I can use "apply" without problems.

What about doing an ng-show on an image? You could use a div that takes up the entire section you wish to show an image with

<div ng-show="loading" class="some-class-to-display-image"></div> 

then in your js turn $scope.loading = true; while you're processing / making calls / etc. You can use a promise and use a .finally() on your requests and set $scope.loading = false; to stop the animation.

Wrapping the code you want to execute first in a $scope.apply() should work unless your getting a "$digest already in progress" error which occurs as only one digest cycle can be run at a time.

If that's the case you could try wrapping the code you want to execute after the loading image is displayed in a $timeout or $evalAsync(Angular v1.2.x & above) which will make sure the 2 digest cycles don't collide ie

    $scope.$apply(function () {
        //display loading image code
    });
    //Other code

or

    //display loading image code
    $scope.$evalAsync(function () {
        //Other code
    });

The way JavaScript works in a browser environment is that one block of javascript is executed until it is finished, the browser does not render intermediate results regardless if you use AngularJS or vanilla JavaScript.

Example

Let's assume you do heavy lifting on 10.000 data entries and want to show a progress bar:

startButton.onclick = function onStartClick() {
  var progressbar = document.getElementById('progressbar');
  progressbar.style.width = "0";

  // process 100000 entries, update progress bar between
  for (var n = 0; n < 100000; y++) {
    doSomethingWithEntryNumber(n);
    progressbar.style.width = (n / 100000) + "%";
  }
}

It will not update the progressbar div in the browser as might be intended, but will freeze the browser until onDivClick is finished. This is no different for AngularJS, where processing lots of data will not update your UI.

To process a large amount of data, you need to split the work and use any kind of "future"/"promise"/"delay" technique - for this example:

startButton.onclick = function onStartClick() {
  var progressbar = document.getElementById('progressbar');
  progressbar.style.width = "0";

  // process 100000 entries in 1000 entry batches, update progress bar between
  function processBatch(batch) {
    for (var i = 0; i < 1000; i++) {
      doSomethingWithEntryNumber(batch*1000 + i);
    }
    // update progressbar
    progressbar.style.width = (batch / 1000) + "%";
    if (batch < 99) {
      // continue work after browser has updated UI
      setTimeout(processBatch.bind(this, batch + 1), 10);
    }
  }
  processBatch(0);
}

This way the browser can render UI changes, the user can scroll, etc. while you are still able to process your 100000 data entries.

In respect to "showing an image during the process", you would show the image, then setTimeout to do your work delayed, when your work is finished remove the image and set your data to the $scope .

You cannot refresh the HTML "during" the execution, as a web page in mono-threaded, which means when the CPU is computing, nothing else can be done. (Technically web workers can help forking multiple threads but really, you do not need that).

So what you want on click is say, enable an image, then wait for angular to update the DOM with that image, and then run your heavy code.

To achieve that you need to use the $timeout service, this way:

$scope.onClick = function() {
  // Set the model to display an image
  $scope.displayImage = true;
  // Wait for angular to digest the current cycle and update the DOM
  $timeout(function() {
    // Do heavy stuff, and at the bottom of the code update the model to hide the image
    $scope.doHeavyStuff();
    $scope.displayImage = false;
  });
}

In your HTML you simply need

<img ng-show = "displayImage" src = "some_url">
<div ng-hide = "displayImage"> Heavy stuff done :) </div>

Now if you have a problem with ng-show at this step, please open a different question.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM