Search code examples
javascriptangulartimeout

Angular 2 equivalent for $timeout


I have to use (large amounts of) existing code in an Angular 2 environment. That code makes extensive use of the $timeout service from AngularJS 1.x. As opposed to various other AngularJS 1.x services that are used in the code, I am having a hard time finding a information about an Angular 2 equivalent for the $timeout service.

The Angular docs do not seem to contain any mention of a service with timeout-something in its name. The article Upgrading from AngularJS does mention the scenario I am facing:

Maybe you want access to AngularJS's built-in services like $location or $timeout.

Unfortunately, the article does not actually explain how to access those particular services, as the subsequent example HeroesService assumes a service without any dependencies supplied by AngularJS 1.x.

Articles such as this one suggest using the native setTimeout function does not live up to the capabilities of the $timeout services, either.

How can I reproduce the $timeout functionality in the Angular 2 environment?

EDIT: As has been noted in the answers, the drawbacks of the native setTimeout function are not relevant when using Angular 2. In that case, if I had the full $q from AngularJS 1.x, I could replicate the $timeout function roughly like this:

function $timeout(fn, delay) {
    var result = $q.defer();
    setTimeout(function () {
        $q.when(fn()).then(function (v) {
            result.resolve(v);
        });
    }, delay);
    return result.promise;
}

Solution

  • Use setTimeout native function. There is no need to use special services in Angular anymore. This is due to the introduction of zones, specifically NgZone.

    Articles such as this one suggest using the native setTimeout function does not live up to the capabilities of the $timeout services, either.

    Why makes you say so? The main task of $timeout service was to start digest after the delayed function is executed. You can see it from the sources:

    function $TimeoutProvider() {
      this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler',
        function($rootScope,   $browser,   $q,   $$q,   $exceptionHandler) {
    
            timeoutId = $browser.defer(function() {
              try {
                deferred.resolve(fn.apply(null, args));
              } catch (e) {
              ...
    
              if (!skipApply) $rootScope.$apply();  <-------------------- here
            }, delay);
    

    In Angular zone.js intercepts all async operations and starts change detection in Angular which is kind of enhanced version of digest.

    If you need to replicate the $timeout, you can roughly do it like this:

    function $timeout(fn, delay, ...args) {
      let timeoutId;
    
      $timeout.cancel = $timeout.cancel || function (promise) {
        if (promise && promise.$$timeoutId in $timeout.promises) {
          $timeout.promises[promise.$$timeoutId][1]('canceled');
          delete $timeout.promises[promise.$$timeoutId];
          return clearTimeout(promise.$$timeoutId);
        }
        return false;
      };
    
      $timeout.promises = $timeout.promises || {};
    
      const promise = new Promise((resolve, reject) => {
        timeoutId = setTimeout(function () {
          try {
            resolve(fn.apply(null, args));
          } catch (e) {
            reject(e);
          } finally {
            delete $timeout.promises[promise.$$timeoutId];
          }
        }, delay);
    
        $timeout.promises[timeoutId] = [resolve, reject];
      });
    
      promise.$$timeoutId = timeoutId;
    
      return promise;
    }
    
    // some basic testing
    
    $timeout((v) => {
      console.log('a', v);
    }, 2000, 7);
    
    const promise = $timeout(() => {
      console.log('b');
    }, 3000);
    
    promise.catch((reason) => {
      console.log(reason);
    });
    
    $timeout.cancel(promise);