How to pass vm to a setTimeout in AngularJs? Chang

2019-06-06 21:21发布

问题:

I'm trying the following code. Everything seems to be working as I see the component appear/disappear depending on the value I set for the variable. However, when I do that in the setTimeout(...) function, it starts to misbehave. The poofy text shows but the value set to vm doesn't. My guess is that I need to pass it in somehow but I'm not sure how.

(function () {
  'use strict';

  angular
    .module("app")
    .controller("Banana", banana);

  function banana() {
    var vm = this;
    vm.showStuff = false;

    setTimeout(function () {
      console.log("poof!");
      vm.showStuff = true;
    }, 1000);
  }
})();

Do I need to make the view-model globally accessible?

回答1:

Try use the script bellow.

(function () {
    'use strict';

     angular
         .module("app")
         .controller("Banana", banana);

     function banana($timeout) {
        var vm = this;
        vm.showStuff = false;

        $timeout(function () {
            console.log("poof!");
            vm.showStuff = true;
        }, 1000);
     }
})();

To be noted - there's additional step required.

  1. Substitute setTimeout(...) for $timeout(...).
  2. Pass $timeout into banana(...).
  3. Provide banana.$inject = ["$timeout",...].


回答2:

Use the $apply method with setTimeout:

//WORKS
setTimeout(function () {
    $scope.$apply("vm.showStuff = true");
}, 1000);

OR use the AngularJS $timeout service:

//RECOMMENDED
$timeout(function () {
    vm.showStuff = true;
}, 1000);

The window.setTimeout method creates an event outside the AngularJS framework and its digest cycle. The $timeout service wraps window.setTimeout and integrates it with the AngularJS framework and its digest cycle.

Angular modifies the normal JavaScript flow by providing its own event processing loop. This splits the JavaScript into classical and Angular execution context. Only operations which are applied in Angular execution context will benefit from Angular data-binding, exception handling, property watching, etc... You use $apply() to enter Angular execution context from JavaScript.

Keep in mind that in most places (controllers, services) $apply has already been called for you by the directive which is handling the event. An explicit call to $apply is needed only when implementing custom event callbacks, or when working with third-party library callbacks.

— AngularJS Developer Guide v1.1 - Concepts - Runtime



回答3:

Hey What Mateus Koppe is showing it's a good answer, I would like to extend, just because this can help someone that needs to apply changes to the view.

The $Timeout service on angular refreshes the data on the view, but with setTimeout you don't have the same effect, so you should use $scope and $apply() to force the refresh on the screen.

On my EXAMPLE I use them, to show you the effect.

    //Timeout angularjs
    $timeout(function () {
        console.log("poof! $timeout");
        vm.showStuff = true;
    }, 1000);

    //Tiemout regular
    setTimeout(function () {
      console.log("poof1! setTimeout");
      vm.showStuff1 = true;
    }, 1000);

    //Timeout + $scope.$apply
    setTimeout(function () {
      console.log("poof2! setTimeout");
      vm.showStuff2 = true;
      $scope.$apply();    //<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }, 3000);

I hope it helps.

As its explained on $apply()

$apply() is used to execute an expression in angular from outside of the angular framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). Because we are calling into the angular framework we need to perform proper scope life-cycle of exception handling, executing watches.

Also you should avoid using on $digest()

Usually, you don't call $digest() directly in controllers or in directives. Instead, you should call $apply() (typically from within a directive), which will force a $digest().

Please check my example here.
https://jsfiddle.net/moplin/x8mnwf5a/



回答4:

As explained in previous answers it has to do with the $digest cycle, native setTimeout() doesn't trigger the digest cycle and therefore the view doesn't get re-rendered with new value of vm.showStuff.

I'd slightly extend Pablo Palacious's answer with the following usage of $scope.$apply(),

//Timeout + $scope.$apply
setTimeout(function () {
  $scope.$apply(function () {
    console.log("poof2! setTimeout");
    vm.showStuff2 = true;
  });
}, 3000);

You could also consider using $scope.$applyAsync() which queues up expressions and functions for execution in the next digest cycle, as described here,

$applyAsync([exp]);

Schedule the invocation of $apply to occur at a later time. The actual time difference varies across browsers, but is typically around ~10 milliseconds.

This can be used to queue up multiple expressions which need to be evaluated in the same digest.