Showtime! A timed Angular ‘ng-show’.

For my current project with Angular I want some messages to be shown for a few seconds (e.g. success of password change) but I didn’t want to use ‘Toasts‘ because on non-mobile screens the action and response are somewhat ‘detached’ if the ‘Toast’ appears far away from where the user triggered the action. (Usually ‘Toasts’ appearing at the top, bottom or in a corner of the screen and pop-ups ain’t an option here for me either.)

So how we can have a ng-show / ng-if with a defined live-time? Well, there is a trivial solution:

In your HTML do a normal ng-show:

<p ng-show="trigger">Hello World!</p> 

And in your controller inject and use the Angular $timeout function:

$scope.trigger = false;</code>

//Something happend
$timeout(function(){</code><code>$scope.trigger = true;}, 1000);

This will show you ‘Hello world!’ for 1 sec. Done!

But wait… this doesn’t feel right:

  1. It’s not reusable
  2. It’s not worth to blog about 😉

So, what about a nice little directive? That’s what we want, right?

My first attempt looked like this:

Directive

directive('timed', function () {
    return {
        restrict: 'AE',
        scope: {
            trigger: '='
        },
        link: function (scope, element, attrs) {
            setTimeout(function () {
                element.css('display', 'none');
                scope.$apply(function () {
                    scope.trigger = false;
                });
            },
                    attrs.duration);
        }
    };
});

Controller

$scope.trigger = false;

$scope.showTime = function() {
    $scope.trigger = true;
};

HTML

<timed ng-if="trigger" trigger="$parent.trigger" duration="3000">
    Hello World!
</timed>

It took me a while to get this running because I’ve missed once again (*aaaargh!*) that ng-if creates a new scope. That’s why we need to reference the trigger with parent.trigger to update the right one. The other important thing is to scope.$apply in the directive to make sure that the change becomes effective.

This works quite nicely but I didn’t like that ‘ng-if’. Fortunately I’ve found this snipplet for setting the focus of an element which gave me the right idea for my ‘final’ solution:

Directive

directive('ngShowTimed', function ($timeout, $parse) {
    return {
        restrict: 'AE',
        link: function (scope, element, attrs) {
            var model = $parse(attrs.trigger);
            scope.$watch(model, function (value) {
                if (value === true) {
                    element.css('display', '');
                    $timeout(function () {
                        scope.$apply(model.assign(scope, false));
                    }, (attrs.duration * 1000));
                } else {
                    element.css('display', 'none');
                }
            });
        }
    };
});

Controller

$scope.trigger = false;

$scope.showTime = function() {
    $scope.trigger = true;
};

HTML

<!-- As attribute -->
<span ng-show-timed trigger="trigger" duration="1">
    Hello World!
</span>

<!-- As element -->
<ng-show-timed trigger="trigger" duration="1">
    Hello World!
</ng-show-timed>

Demo: Plunker

Any questions, corrections, additions? Just leave a comment, I’ll get back to you soon. Share if you found this useful.