Can ng-show directive be used with a delay
Asked Answered
J

3

9

I have a spinner this is shown with ng-show="loading>0"

Is there a way I can display this spinner with a delay (say 1 second)?

I can't use a timeout because with multiple requests de loading counter will get out of sync.

What I need is a delay on the ng-show via css transition or similar

Joshia answered 8/1, 2015 at 14:19 Comment(2)
Did you already had a look into the docs? link Scroll to bottom and you will see a sample with transition and ng-showWallraff
What are you really looking to accomplish here? You want to have a delay so that for fast operations it doesn't just blink on and off, but for longer operations it shows up? Just a general purpose loading indicator?Grigsby
U
5

Here's a simpler approach that worked for my needs. Depending on what your action is, you would tie function setDelay() to the element. For example, in my case I tied setDelay() to a select input.

Trigger HTML:

<select class="first-option"
    ng-change="setDelay()" 
    ng-options="o.label for o in download.options" 
    ng-model="optionModel" required>
</select>

In your controller, add a simple function setDelay that will change the flag $scope.delay:

$scope.setDelay = function(){
    $scope.delay = true;
    $timeout(function(){
        $scope.delay = false;
    }, 200);
};

Then, you can simply use $scope.delay as a flag in ng-show:

<div class="loading-div" ng-show="delay">
    <img src="loading_spinner.gif">
</div>

And show content after done loading:

<div ng-show="!delay">
    Content is loaded.
</div>

Now, every time the user selects a new value in the dropdown menu, it will trigger$scope.delay to be set totrue causing the spinner to show, and when it reaches 200, it will be set to false causing the spinner to hide.

Uke answered 13/7, 2015 at 21:57 Comment(0)
G
7

My suspicion is that you are looking for a general purpose spinner that includes a delay. The standard, show after 200ms or something like that.

This is a perfect candidate for a directive, and actually pretty easy to accomplish.

I know this is a long code example, but the primary piece is the directive. It's pretty simple.

Listen to a few scope variables and shows after some configurable delay. If the operation takes longer than the delay, it will just get canceled and never show up.

(function() {
  'use strict';

  function SpinnerDirective($timeout) {
    return {
      restrict: 'E',
      template: '<i class="fa fa-cog fa-spin"></i>',
      scope: {
        show: '=',
        delay: '@'
      },
      link: function(scope, elem, attrs) {
        var showTimer;

        //This is where all the magic happens!
        // Whenever the scope variable updates we simply
        // show if it evaluates to 'true' and hide if 'false'
        scope.$watch('show', function(newVal){
          newVal ? showSpinner() : hideSpinner();
        });
        
        function showSpinner() {
          //If showing is already in progress just wait
          if (showTimer) return;

          //Set up a timeout based on our configured delay to show
          // the element (our spinner)
          showTimer = $timeout(showElement.bind(this, true), getDelay());
        }

        function hideSpinner() {
          //This is important. If the timer is in progress
          // we need to cancel it to ensure everything stays
          // in sync.
          if (showTimer) {
            $timeout.cancel(showTimer);
          }

          showTimer = null;

          showElement(false);
        }

        function showElement(show) {
          show ? elem.css({display:''}) : elem.css({display:'none'});
        }

        function getDelay() {
          var delay = parseInt(scope.delay);

          return angular.isNumber(delay) ? delay : 200;
        }
      }
    };
  }

  function FakeService($timeout) {
    var svc = this,
      numCalls = 0;

    svc.fakeCall = function(delay) {
      numCalls += 1;

      return $timeout(function() {

        return {
          callNumber: numCalls
        };

      }, delay || 50);
    };
  }

  function MainCtrl(fakeService) {
    var vm = this;

    vm.makeCall = function(delay) {
      vm.isBusy = true;
      fakeService.fakeCall(delay)
        .then(function(result) {
          vm.result = result;
        }).finally(function() {
          vm.isBusy = false;
        });
    }
  }

  angular.module('spinner', [])
    .service('fakeService', FakeService)
    .controller('mainCtrl', MainCtrl)
    .directive('spinner', SpinnerDirective);

}());
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" rel="stylesheet" />
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular.min.js"></script>

<div class="container" ng-app="spinner">
  <div class="row" ng-controller="mainCtrl as ctrl">
    <div class="col-sm-12">
      <h2>{{ctrl.result | json}}
        <spinner show="ctrl.isBusy" delay="200"></spinner>
      </h2>
      <button type="button" 
              class="btn btn-primary" 
              ng-click="ctrl.makeCall(2000)" 
              ng-disabled="ctrl.isBusy">Slow Call
      </button>
      <button type="button" 
              class="btn btn-default" 
              ng-click="ctrl.makeCall()" 
              ng-disabled="ctrl.isBusy">Fast Call
      </button>
    </div>
  </div>
</div>
Grigsby answered 8/1, 2015 at 15:33 Comment(1)
Excellent solution!Ressler
U
5

Here's a simpler approach that worked for my needs. Depending on what your action is, you would tie function setDelay() to the element. For example, in my case I tied setDelay() to a select input.

Trigger HTML:

<select class="first-option"
    ng-change="setDelay()" 
    ng-options="o.label for o in download.options" 
    ng-model="optionModel" required>
</select>

In your controller, add a simple function setDelay that will change the flag $scope.delay:

$scope.setDelay = function(){
    $scope.delay = true;
    $timeout(function(){
        $scope.delay = false;
    }, 200);
};

Then, you can simply use $scope.delay as a flag in ng-show:

<div class="loading-div" ng-show="delay">
    <img src="loading_spinner.gif">
</div>

And show content after done loading:

<div ng-show="!delay">
    Content is loaded.
</div>

Now, every time the user selects a new value in the dropdown menu, it will trigger$scope.delay to be set totrue causing the spinner to show, and when it reaches 200, it will be set to false causing the spinner to hide.

Uke answered 13/7, 2015 at 21:57 Comment(0)
U
4

I think a pure CSS solution is the best way to do it.

Here is a plunker showing how to do it. Using ng-animate classes for transition and applying a transition delay with a transition of 10ms (0s transition is not working with css).

Relevant part of the code :

.your-element-class.ng-hide {
  opacity: 0;
}

.your-element-class.ng-hide-add,
.your-element-class.ng-hide-remove {
  transition: all linear 0.01s 1s;
}

The only reason to use a custom directive for it would be using this tons of times in your code with different delays value. A custom directive allow more flexibility with the delay timing.

Utgardloki answered 13/6, 2017 at 3:57 Comment(1)
This helped me and is a really clean way to keep any workaround from bleeding into the js code. I like to keep as much of the styling/rendering logic in CSS as possible.Bayberry

© 2022 - 2024 — McMap. All rights reserved.