23

I have code that I use to check a connection to the server. My code runs every 60 seconds. Once the code has run then it creates a message and this shows up on a page. Here's what I have so far:

The code that checks:

$interval(function () {
        us.isConnected().then(closeConnect, openConnect);
    }, 60 * 1000);

The code that does the check

isConnected = (): ng.IPromise<any> => {
    var self = this;
    var deferred = this.$q.defer();
    this.$http({
        method: 'GET',
        url: self.ac.baseUrl + '/api/Connect/Verify'
    })
        .success(() => {
            self.connects = 0;
            self.connectMessage = null;
            deferred.resolve();
        })
        .error(() => {  
            if (self.connects == 0) {
                self.connectMessage = "Unable to establish a connection to the server. " + retryMessage();
            } else if (self.connects == 1) {
                self.connectMessage = "Unable to establish a connection to the server for " + self.connects + " minute" + retryMessage();
            } else {
                self.connectMessage = "Unable to establish a connection to the server for " + self.connects + " minutes." + retryMessage();
            }
            self.connects++; 
            deferred.reject();
        });
    return deferred.promise;
};

What I would like to do is to have a simple function called retryMessage() that will allow me to give a message like this:

 Unable to establish a connection to the server for 164 minutes. 
 Connection will be retried in 59 seconds.
 Unable to establish a connection to the server for 164 minutes. 
 Connection will be retried in 58 seconds.
 Unable to establish a connection to the server for 164 minutes. 
 Connection will be retried in 57 seconds.
 ...
 Unable to establish a connection to the server for 164 minutes. 
 Connection will be retried in 1 seconds.
 Unable to establish a connection to the server for 164 minutes. 
 Retrying connection now.
 Unable to establish a connection to the server for 165 minutes. 
 Connection will be retried in 59 seconds.

With the number of seconds counting down until 0 when there will be a recheck.

Can anyone suggest a way in AngularJS that I can achieve this countdown?

1
  • Check out the following link, it has pretty much all the timers you could possibly want: siddii.github.io/angular-timer Commented Apr 19, 2015 at 8:37

8 Answers 8

9

One possible way of doing what you're attempting is to use $q.notify in conjunction with $interval.

Other messages can be passed with the resolve and reject. Of course, any place this is done you could just print straight to a logging service instead, but I thought it might be appropriate to return these messages with the promise behaviours (where, if you wanted, you could even instead return an object complete with status code and other data along with the message).

In the below sample, I've let the controller manage the logging and limited its output to 12 lines. I've also specified parameters for the connection testing so it attempts to connect every 60 seconds, 20 times (these params could be changed to attempt at different intervals, a different amount of times, or indefinitely). If the test is a failure, it prints the retry message(s) every second until the retry attempt:

(function() {
  "use strict";

  var myApp = angular.module('myApp', []);
  myApp.controller('MainController', ['$scope', 'us', '$log', MainController]);
  myApp.service('us', ['$interval', '$q', '$http', '$log', usService]);

  /* Controller */
  function MainController($scope, us, $log) {

    var _data = {
        connectLog: null
      },
      _connectMessages = [],
      _MAX_LOG_LINES = 12;

    $scope.data = _data;

    _log("Starting connection test...");
    us.testConnection(60, 20) //60 seconds between tests, 20 tests (if no max specified, could run forever...)
      .then(onTestsSuccessful, onTestsFailed, onNotifying);

    function onTestsSuccessful(result) {
      _log(result);
      // do success stuff...
    }

    function onTestsFailed(result) {
      _log(result);
      // do failed stuff...
    }

    function onNotifying(result) {
      _log(result);
      //do retrying stuff...
    }

    function _log(message, deferOutput) {
      //$log.debug(message);

      _connectMessages.push(message);

      if (_MAX_LOG_LINES && _connectMessages.length > _MAX_LOG_LINES) {
        _connectMessages.splice(0, _connectMessages.length - _MAX_LOG_LINES);
      }

      if (!deferOutput) {
        _data.connectLog = _connectMessages.join('\n');
      }
    }
  }

  /* Service */
  function usService($interval, $q, $http, $log) {

    var _testConnectionInterval,
      _testsRun;

    return {
      testConnection: _startTestConnection
    };

    function _startTestConnection(secondsBetweenTests, maxTests) {
      var deferred = $q.defer(),
        connectAttempts = 0;

      _cancelConnectionTest();

      _connectionTest().then(onConnectionTestSuccess, onConnectionTestFail); //immediately do first test
      _testsRun++;

      if (secondsBetweenTests > 0) {
        _testConnectionInterval = $interval(
          function repeatConnectionTest() {
            if (maxTests && _testsRun >= maxTests) {
              return _cancelConnectionTest();
            }
            deferred.notify("Retrying connection now.");
            _connectionTest().then(onConnectionTestSuccess, onConnectionTestFail);
            _testsRun++;
          },
          secondsBetweenTests * 1000); //start the countdown to the next
      }

      function onConnectionTestSuccess(result) {
        connectAttempts = 0;

        if ((maxTests && _testsRun >= maxTests) || !secondsBetweenTests) {
          deferred.resolve("Last connection test success, " + _testsRun + " tests complete.");
        } else {
          deferred.notify("Connection test success.");
        }
      }

      function onConnectionTestFail(result) {
        var minutesPassed = connectAttempts * secondsBetweenTests / 60,
            minutesRoundedToTwoDec = +(Math.round(minutesPassed + "e+2")  + "e-2");
        
        var connectMessage = "Unable to establish a connection to the server" + (connectAttempts === 0 ? "." : " for " + minutesRoundedToTwoDec + " minute" + (minutesPassed > 1 ? "s." : "."));

        connectAttempts++;

        if ((maxTests && _testsRun >= maxTests) || !secondsBetweenTests) {
          deferred.reject("Last connection test failed, " + _testsRun + " tests completed.");
        } else {
          deferred.notify(connectMessage);
          deferred.notify("Connection will be retried in " + secondsBetweenTests + " seconds.");

          var retryInterval = $interval(
            function retryMessage(counter) {
              deferred.notify(connectMessage);

              var secondsLeft = (secondsBetweenTests - 1) - counter;
              deferred.notify("Connection will be retried in " + secondsLeft + " second" + (secondsLeft > 1 ? "s." : "."));

              if (!secondsLeft) {
                $interval.cancel(retryInterval);
                retryInterval = null;
              }
            },
            1000, (secondsBetweenTests - 1));
        }
      }

      return deferred.promise;
    }

    function _connectionTest() {
      var deferred = $q.defer(),
        getBroken = {
          method: 'GET',
          url: '/api/never/gonna/give/you/up'
        };

      $http(getBroken)
        .success(function onSuccess() {
          deferred.resolve('Success!');
        })
        .error(function onError() {
          deferred.reject('Failure!');
        });

      return deferred.promise;
    }

    function _cancelConnectionTest() {
      _testsRun = 0;
      if (!_testConnectionInterval) {
        $log.debug("No previously running connection test to cancel.");
        return;
      }
      $log.debug("Cancelling connection test.");
      $interval.cancel(_testConnectionInterval);
      _testConnectionInterval = null;
    }
  }

})();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.28/angular.min.js"></script>
<div ng-app="myApp">
  <pre ng-controller="MainController">{{data.connectLog}}</pre>
</div>

Sign up to request clarification or add additional context in comments.

Comments

6

You can do a countdown from 60 seconds like this:

countdown(60);

  function countdown(current_time){
    if(current_time === 0){
      //call function to connect to server
      return;
    }
    else{
      current_time--;
    }
    $scope.time = current_time;
    $timeout(function(){countdown(current_time)}, 1000);
  }

codepen

I'd recommend putting the error message in the html and hiding it with a ng-show or ng-if and then just changing the number so that you don't need to append all that text over and over again.

Comments

4
+100

I don't think using a directive is necessary, you can probably do everything inside the controller. However you implemented closeConnection and openConnection you should edit those methods adding 'start' and 'stop' $interval.

Also remember that $interval takes a max number of recursions that in this case is pretty useful.

https://docs.angularjs.org/api/ng/service/$interval

function controllerFunc($scope, $interval) {
    var timer;
    var lastConnection = 0;
    $scope.message = '';

    //this is what you already have
    $interval(function () {
        us.isConnected().then(closeConnect, openConnect);
    }, 60 * 1000);
    //...

    function closeConnect() {
        //...
        $interval.cancel(timer);
        lastConnection = 0;
    }

    function openConnect() {
        //...
        timer = $interval(timerCall, 1000, 60);
        lastConnection++;
    }

    function timerCall(times) {
        $scope.message += 'Unable to establish a connection to the server for ' + lastConnection + ' minutes. ';

        var retry = 60 - times;
        $scope.message += 'Connection will be retried in '+ retry +' seconds';
    }
}

Formatting the Message

$scope.message is a plain string in this example so you wont get any formatting but you can put it inside an ng-bind-html directive and then add any html tag to the message string.

https://docs.angularjs.org/api/ng/directive/ngBindHtml

<div ng-bind-html="message"></div>

And so changing the js

$scope.message += '<p>Connection will be retried in '+ retry +' seconds</p>';

Comments

2

I do have a directive for very similar purpose, you can check it here on plunker

basically it uses (1) timer & (2) refresh-state, it is also configuratble as in no of seconds for timeout, read from attrs of directive.

It does call a given function after given interval, it also destroys the #interval on $scope change.

Below is the code for it

app.directive("csAutoRefresh", ["$interval", function ($interval) {

    var templateFn = function () {
        var template = '<div class="text-left alert alert-success nopadding"';
        template += 'style="margin-bottom: 0; margin-right: 0"> ';
        template += ' <button class="btn btn-link" data-ng-click="factory.refresh.toggle()">';
        template += '{{factory.refresh.refreshText()}}</button>';
        template += '<span>...Refreshing upload status in ';
        template += ' {{state.timer.timePending}} seconds</span>';
        template += ' </div>';
        return template;
    };

    var linkFn = function (scope) {
        scope.pauseOn = scope.pauseOn === true;
        scope.isprocessing = false;

        scope.state = {
            refresh : {
                suspend : false
            },
            timer : {
                timePending: 0,
                refreshInterval : 60
            }
        }

        function doRefresh() {
            var refresh = {
                pause: function () { scope.state.refresh.suspend = true; },
                cont: function () { scope.state.refresh.suspend = false; },
                toggle: function () { scope.state.refresh.suspend = !scope.state.refresh.suspend; },
                refreshText: function () { return scope.state.refresh.suspend ? "Resume Refresh" : "Pause Refresh"; }
            };
            return refresh;
        }

        function doTimer() {
            var timer = {
                reset: function () { scope.state.timer.timePending = scope.state.timer.refreshInterval; },
                update: function () {
                    if (scope.state.timer.timePending < 0) timer.reset();
                    scope.state.timer.timePending--;
                },
                done: function () { return scope.state.timer.timePending <= 0; },
                force: function () { scope.state.timer.timePending = 0; }
            };
            return timer;
        };

        scope.factory = {
            refresh: doRefresh(),
            timer: doTimer()
        };

        if (angular.isDefined(scope.interval) && parseInt(scope.interval) > 0) {
            scope.state.timer.refreshInterval = scope.interval;
        }
        scope.factory.timer.reset();

        scope.$watch(function () {
            return scope.state.timer.timePending;
        }, function () {
            if (!scope.factory.timer.done()) return;
            var result = scope.$eval(scope.onTimeout);
            if (angular.isObject(result) && angular.isFunction(result.then)) {
                scope.isprocessing = false;
                scope.factory.timer.reset();
                result.finally(function () { scope.factory.timer.reset(); });
            } else {
                scope.isprocessing = false;
                scope.factory.timer.reset();
            }
        });

        scope.$watch('pauseOn', function () {
            if (scope.pauseOn) {
                scope.factory.refresh.pause();
            } else {
                scope.factory.timer.reset();
                scope.factory.refresh.cont();
            }
        });

        var updateTimer = function () {
            if (scope.isprocessing) return;
            if (scope.state.refresh.suspend) return;
            scope.factory.timer.update();
        };

        var interval = $interval(updateTimer, 1000);
        scope.$on('$destroy', function () {
            $interval.cancel(interval);
        }); 
    };

    return {
        restrict: 'E',
        scope: { onTimeout: '&', pauseOn: '=', interval: '@' },
        template: templateFn,
        link: linkFn,
    };
}]);

Comments

1
$interval(function () {
    us.isConnected().then(closeConnect, openConnect);
}, 1000);

Not

$interval(function () {
    us.isConnected().then(closeConnect, openConnect);
}, 20 * 1000);

The latter does the check every 20 secs (20*1000ms) so unless the actual checking code is buggy, it should run every second.

1 Comment

Thanks for pointing out it was 20. I changed the question. The check is made every 60 seconds but then I would like to should also like to modify the message so that is shows a countdown decrementing every one second.
1

So depending on how you are looking to output the details to the user, you'd probably be best making this some type of directive and controlling everything within that.

The key to doing what you want to do is to use the $interval service, which returns an id:

$scope.intervalId = $interval(retryMessage, 1000);

You can then cancel depending on whatever conditions your set.

I made a plnkr demonstrating what you're looking to accomplish:

http://plnkr.co/edit/RmADu1aiOUO5o4k4pnqE?p=preview

Comments

1

I wrote this timer recently from which you may be able to take some logic - Plunker. It counts up and it can end at a set time

JS

(function() {

    'use strict';

    var angularTimerApp = angular.module('angularTimerApp', []);

    angularTimerApp.directive("angularTimer", function() {
        return {
            restrict: "E",
            templateUrl: "angular-timer.html",
            scope: {endTime: "@"},
            controllerAs: "at",
            bindToController: true,
            controller: ["$scope", "$interval", function ($scope, $interval) {
                var at = this;
                at.secondsInAYear = 31536000;
                at.secondsInADay = 86400;
                at.secondsInAnHour = 3600;
                at.secondsInAMinute = 60;
                at.endTimeValue = null;

                $scope.$watch("at.endTime", function(newValue) {
                  if (angular.isDefined(newValue)) {
                    at.endTimeValue = parseInt(newValue); // No test for int
                  }
                });

                at.getTimeDisplay = function(seconds) {
                    var hoursTxt = Math.floor(((seconds % at.secondsInAYear) % at.secondsInADay) / at.secondsInAnHour);
                    var minutesTxt = Math.floor((((seconds % at.secondsInAYear) % at.secondsInADay) % at.secondsInAnHour) / at.secondsInAMinute);
                    var secondsTxt = (((seconds % at.secondsInAYear) % at.secondsInADay) % at.secondsInAnHour) % at.secondsInAMinute;
                    return ("Hours: " + hoursTxt + ", Minutes: " + minutesTxt + ", Seconds: " + secondsTxt);
                };

                at.reset = function () {
                    at.timeOffset = 0;
                    at.timeDisplay = at.getTimeDisplay(0);
                };
                at.reset();

                at.stop = function () {
                    $interval.cancel(at.timer);
                    at.timeOffset = at.time;
                };

                at.start = function() {
                    at.timeStart = (new Date()).getTime();

                    at.timer = $interval(function() {
                        at.time = Math.floor(((new Date()).getTime() - at.timeStart) / 1000) + at.timeOffset;
                        if ((at.endTimeSet) && (at.endTimeValue !== null)) {
                            if (at.time > at.endTimeValue) {
                                at.stop();
                            }
                            else {
                                at.timeDisplay = at.getTimeDisplay(at.time);
                            }
                        }
                        else {
                            at.timeDisplay = at.getTimeDisplay(at.time);
                        }
                    }, 1000);
                };
            }]
        };
    });
})();

Markup

<body>
    <angular-timer end-time="10"></angular-timer>
</body>

angular-timer.html

{{at.timeDisplay}}
<br><br>
<button ng-click="at.start()">Start</button>
<br><br>
<button ng-click="at.stop()">Stop</button>
<br><br>
<button ng-click="at.reset()">Reset</button>
<br><br>
<input type="checkbox" ng-model="at.endTimeSet"> End at 27 seconds

Comments

1

I think what you really want is a service that returns a promise, a promise that sends notifications back to your controller. Here's an example of such a service:

<!DOCTYPE html>
<html>
<head>
<script data-require="[email protected]" data-semver="1.4.0-rc.0" src="https://code.angularjs.org/1.4.0-rc.0/angular.js"></script>
</head>
<body ng-app="myapp" ng-controller="main">

<h1>Task Retry Example</h1>
<p>Task condition: {{value}}</p>
<button ng-click="value = true">Set task condition to true</button>
<button ng-click="reset()">Reset Task</button>
<p>{{msg}}</p>

<script>
  var app = angular.module('myapp', []);
  app.controller('main', function($scope, task){
    var myTask = function(){
      return $scope.value;
    }

    function success(result){
      $scope.msg = result;
    }
    function failure(reason){
      $scope.msg = reason;
    }
    function notify(value){
      $scope.msg = value.message;
    }

    $scope.reset = function(){
      $scope.value = false;
      $scope.msg = "Loading...";
      task.go(myTask, {maxAttempts: 3, waitTime: 3})
        .then(success, failure, notify);
    }

    $scope.reset();

  });

  app.service('task', function($q, $timeout){

    var DEFAULT_OPTIONS = {
      maxAttempts: 1,
      waitTime: 10
    };
    var thisOptions = {};

    function _countDownStep(secondsLeft, attemptsLeft, countDownProgress, taskAttemptProgress){
      if(secondsLeft <= 0){
        countDownProgress.resolve(true);
        return;
      }
      var attempt = thisOptions.maxAttempts - attemptsLeft,
            msg = "Attempt failed; retrying (" + attempt + " of " + thisOptions.maxAttempts + ") in " + secondsLeft + " seconds...";
      taskAttemptProgress.notify({
        "state": "WAITING",
        "message": msg
      })
      $timeout(function(){
        _countDownStep(secondsLeft-1, attemptsLeft, countDownProgress, taskAttemptProgress);
      }, 1000);
    }

    function _countDown(secondsLeft, attemptsLeft, progress){
      var deferred = $q.defer();
      _countDownStep(secondsLeft, attemptsLeft, deferred, progress);
      return deferred.promise;
    }

    function _attempt(task, attemptsLeft, progress){
      if(!angular.isFunction(task)) {
        progress.reject("Task is not a function.");
        return;
      }
      if(attemptsLeft <= 0){
        progress.reject("Max attempts reached.");
      }
      var result = task();
      if(result){
        progress.resolve("Successfully completed task.");
      }else {
        --attemptsLeft;
        if(attemptsLeft <= 0){
          progress.reject("Max attempts reached.");
        }
        _countDown(thisOptions.waitTime, attemptsLeft, progress).then(function(){
          var attempt = thisOptions.maxAttempts - attemptsLeft,
            msg = "Making another attempt (" + attempt + " of " + thisOptions.maxAttempts + ")...";
          progress.notify({
            "state": "TRYING",
            "message": msg
          })
          _attempt(task, attemptsLeft, progress);  
        });
      }
    }

    function _go(task, options){
      var deferred = $q.defer();
      thisOptions = options || DEFAULT_OPTIONS;
      _attempt(task, thisOptions.maxAttempts, deferred);
      return deferred.promise;
    }

    return {
      go: _go
    }

  });

</script>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.