wmluke/angular-flash

Add support for `ngAminate`

wmluke opened this issue · 1 comments

AngularJS's new animating support is outstanding...angular-flash should leverage it.

http://docs.angularjs.org/api/ngAnimate

For anyone looking to incorporate ng-animate, here's what I did to accomplish it.

A few things to be aware of:

  1. Requires ngAnimate.
  2. ngAnimate will destroy your element after the ng-leave animation has been completed, so your element will never be used. Instead, a clone of your original will be dynamically created and used in its place, allowing for a single alert to be used as many times as you want. It might be better to render alert(s) by way of an ng-repeat in the instead.
  3. I set this up with the element being hidden by default by using BS3's hide class. The hide class is removed within the $animate.enter(), and added again via the $animate.leave() (prior to the element being destroyed). Because of this, you shouldn't need to use the active-class="..." anymore.
  4. I make no claim that this is the "ideal" approach to implementing ngAnimate, just offer it up as a relatively simple solution that works. If you find a better way, please update accordingly.

First, in the flash-alert-directive.js, inject $animate and add clone and parent var declarations:

    function flashAlertDirective(flash, $animate, $timeout) {
        return {
            scope: true,
            link: function ($scope, element, attr) {
                var timeoutHandle, subscribeHandle, clone, parent;

And:

angular.module('angular-flash.flash-alert-directive', ['angular-flash.service'])
    .directive('flashAlert', ['flash', '$animate', '$timeout', flashAlertDirective]);

Next, update the show() method:

function show(message, type) {
    if (timeoutHandle) {
        $timeout.cancel(timeoutHandle);
    }

    $scope.flash.type = type;
    $scope.flash.message = message;
    removeAlertClasses();

    angular.forEach(flash.classnames[type], function (clazz) {

        // because ng-animate destroys elements, we clone & use the original element
        // ignoring the original 
        clone  = element.clone();
        parent = element.parent();

        // add the clone to the parent & add the alert class
        parent.append(clone);
        clone.addClass(clazz);

        // animate the clone
        $animate.enter(clone, parent, null, function() {
            clone.removeClass('hide');
        });
    });

    // this shouldn't be needed anymore
    if (!isBlank(attr.activeClass)) {
        element.addClass(attr.activeClass);
    }

    if (!message) {
        $scope.hide();
        return;
    }

    var delay = Number(attr.duration || 5000);
    if (delay > 0) {
        timeoutHandle = $timeout($scope.hide, delay);
    }
}

Finally, update the hide() method:

$scope.hide = function () {
    $animate.leave(clone, function() {
        removeAlertClasses();
        clone.removeClass(attr.activeClass);
        clone.addClass('hide');
    });
};

Example Markup:

    <div class="row">
        <div class="col-md-12 col-lg-12">
            <div flash-alert class="alert am-fade-and-scale hide">
                <strong>You Did It!!!</strong>
                <span class="alert-message">{{flash.message}}</span>
            </div>
        </div>
    </div>

Note: I am using the angular-motion from https://github.com/mgcrea/angular-motion. You can use whatever animations you want, just replace the am-fade-and-scale with the class name your custom animation, or the class name of the pre-defined animation you wish to use.