S.C.
S.C.

Reputation: 920

Ionic - when should ionicModal.remove() be called? [Ionic Modal Hide Callback]

In my controller, modal views are initialized with a factory by listening to the $ionicView.afterEnter event. As the documentation suggests, modal views should be removed whenever the current active ionic view is about to be destroyed. A function is called in the $ionicView.beforeLeave callback in order to remove the modal views.

.controller('UserProfileCtrl', function($scope, $state, user, ModalFactory) {
    $scope.user = user;

    $scope.checkOrders = function() {
        $state.go('app.orders');
    };

    $scope.$on('$ionicView.afterEnter', function() {
        $scope.modals = ModalFactory.getUserProfileModals($scope, user.photos);
    });

    $scope.$on('$ionicView.beforeLeave', function() {
        $scope.modals.remove();
    });
});

.factory('ModalFactory', function($ionicModal, $ionicSlideBoxDelegate) {
    var modalFactory = {};

    modalFactory.getUserProfileModals = function(scope, images) {
        var modals = {
            'views': [],
            'data': []
        };

        $ionicModal.fromTemplateUrl('templates/modals/common/image-view.html', { 'scope': scope }).then(function(modal) { modals.views.imageView = modal; });

        if (images) {
            modals.data.images = images;
        }

        modals.open = function(view, slide) {
            Object.keys(this.views).forEach(function(key, index) {
                console.log(key);
            });

            if (view && this.views.hasOwnProperty(view)) {
                this.views[view].show();

                if (view == 'imageView' && slide) {
                    $ionicSlideBoxDelegate.$getByHandle('image-view').slide(slide);
                }
            }
        };

        modals.close = function(view) {
            Object.keys(this.views).forEach(function(key, index) {
                console.log(key);
            });

            if (view && this.views.hasOwnProperty(view)) {
                this.views[view].hide();
            } else {
                Object.keys(this.views).forEach(function(key, index) {
                    this.views[key].hide();
                });
            }
        };

        modals.remove = function() {
            console.log('remove');
            Object.keys(this.views).forEach(function(key, index) {
                console.log(key);
            });

            this.data.splice(0, this.data.length);

            Object.keys(this.views).forEach(function(key, index) {
                this.views[key].remove();
            });

            this.views.splice(0, this.views.length);
        };

        return modals;
    };

    return modalFactory;
});

However, I get the following output in the console when I execute these actions in sequence:

  1. call $scope.modals.open('imageView', 1),
  2. call $scope.modals.close(),
  3. navigate to another page with $state.go('app.orders').

Screenshot showing console output

I tried listening to $destroy instead of $ionicView.beforeLeave, but then $scope.modals.remove() is not called at all. It seems $destroy is not fired when I am testing my application with Chrome.

Can anyone tell me when should I remove the modal views, and why is there such an error message in my scenario?


Update

After I modified the code in ModalFactory as follows, the error is gone.

function open(view, slide) {
    if (view && modals.views.hasOwnProperty(view)) {
        modals.views[view].show();

        if (view == 'imageView' && slide) {
            $ionicSlideBoxDelegate.$getByHandle('image-view').slide(slide);
        }
    }
};

function close(view) {
    if (view && modals.views.hasOwnProperty(view)) {
        modals.views[view].hide();
    } else {
        Object.keys(modals.views).forEach(function(key, index) {
            modals.views[key].hide();
        });
    }
};

function remove() {
    console.log('remove');

    modals.data.splice(0, modals.data.length);

    Object.keys(modals.views).forEach(function(key, index) {
        modals.views[key].remove();
    });

    modals.views.splice(0, modals.views.length);
};

modals.open = open;
modals.close = close;
modals.remove = remove;

return modals;

Upvotes: 3

Views: 4758

Answers (2)

ProllyGeek
ProllyGeek

Reputation: 15836

As I was looking for a solution in 2016, it is mentioned that

$scope.$on('destroy') isn't called in new ionic builds where cache is used

so I came up with a solution to add event listener for animation/transition end on modal.hidden.

First of all this solution solves the animation end event problem.

The awesome guy made two libraries, one with jQuery Depency, and the other is written in plain javascript.

jQuery Plugin:

/*
    By Osvaldas Valutis, www.osvaldas.info
    Available for use under the MIT License
*/

;( function( $, window, document, undefined )
{
    var s = document.body || document.documentElement, s = s.style, prefixAnimation = '', prefixTransition = '';

    if( s.WebkitAnimation == '' )   prefixAnimation  = '-webkit-';
    if( s.MozAnimation == '' )      prefixAnimation  = '-moz-';
    if( s.OAnimation == '' )        prefixAnimation  = '-o-';

    if( s.WebkitTransition == '' )  prefixTransition = '-webkit-';
    if( s.MozTransition == '' )     prefixTransition = '-moz-';
    if( s.OTransition == '' )       prefixTransition = '-o-';

    $.fn.extend(
    {
        onCSSAnimationEnd: function( callback )
        {
            var $this = $( this ).eq( 0 );
            $this.one( 'webkitAnimationEnd mozAnimationEnd oAnimationEnd oanimationend animationend', callback );
            if( ( prefixAnimation == '' && !( 'animation' in s ) ) || $this.css( prefixAnimation + 'animation-duration' ) == '0s' ) callback();
            return this;
        },
        onCSSTransitionEnd: function( callback )
        {
            var $this = $( this ).eq( 0 );
            $this.one( 'webkitTransitionEnd mozTransitionEnd oTransitionEnd otransitionend transitionend', callback );
            if( ( prefixTransition == '' && !( 'transition' in s ) ) || $this.css( prefixTransition + 'transition-duration' ) == '0s' ) callback();
            return this;
        }
    });
})( jQuery, window, document );

or use Plain Javascript Library:

/*
    By Osvaldas Valutis, www.osvaldas.info
    Available for use under the MIT License
*/

;( function ( document, window, index )
{
    var s = document.body || document.documentElement, s = s.style, prefixAnimation = '', prefixTransition = '';

    if( s.WebkitAnimation == '' )   prefixAnimation  = '-webkit-';
    if( s.MozAnimation == '' )      prefixAnimation  = '-moz-';
    if( s.OAnimation == '' )        prefixAnimation  = '-o-';

    if( s.WebkitTransition == '' )  prefixTransition = '-webkit-';
    if( s.MozTransition == '' )     prefixTransition = '-moz-';
    if( s.OTransition == '' )       prefixTransition = '-o-';

    Object.prototype.onCSSAnimationEnd = function( callback )
    {
        var runOnce = function( e ){ callback(); e.target.removeEventListener( e.type, runOnce ); };
        this.addEventListener( 'webkitAnimationEnd', runOnce );
        this.addEventListener( 'mozAnimationEnd', runOnce );
        this.addEventListener( 'oAnimationEnd', runOnce );
        this.addEventListener( 'oanimationend', runOnce );
        this.addEventListener( 'animationend', runOnce );
        if( ( prefixAnimation == '' && !( 'animation' in s ) ) || getComputedStyle( this )[ prefixAnimation + 'animation-duration' ] == '0s' ) callback();
        return this;
    };

    Object.prototype.onCSSTransitionEnd = function( callback )
    {
        var runOnce = function( e ){ callback(); e.target.removeEventListener( e.type, runOnce ); };
        this.addEventListener( 'webkitTransitionEnd', runOnce );
        this.addEventListener( 'mozTransitionEnd', runOnce );
        this.addEventListener( 'oTransitionEnd', runOnce );
        this.addEventListener( 'transitionend', runOnce );
        this.addEventListener( 'transitionend', runOnce );
        if( ( prefixTransition == '' && !( 'transition' in s ) ) || getComputedStyle( this )[ prefixTransition + 'transition-duration' ] == '0s' ) callback();
        return this;
    };
}( document, window, 0 ));

Next in your Ionic Code, listen to Modal Hide Event:

$scope.$on('modal.hidden', function() {
       $( '.modal' ).onCSSAnimationEnd( function()//this example uses jQuery Plugin
       {
           $scope.modal.remove();
       });
      });

Or Plain Javascripy Example:

var item = document.querySelector( '.modal' );   
item.onCSSAnimationEnd( function()
{
    $scope.modal.remove();
});

Hope it helps someone someday.

Upvotes: 0

Rjun
Rjun

Reputation: 406

By using $scope.$on('$destroy', ..) method Angular will broadcast a $destroy event just before tearing down a scope and removing the scope from its parent.

Here is the example of how i resolved modal.remove() issue,

.factory('ModalFactory', function($ionicModal, $rootScope) {
    var init = function(tpl, $scope) {
        var promise;
        $scope = $scope || $rootScope.$new();

        promise = $ionicModal.fromTemplateUrl(tpl, {
            scope: $scope,
            animation: 'slide-in-right'
        }).then(function(modal) {
            $scope.modal = modal;
            return modal;
        });

        $scope.openModal = function() {
            $scope.modal.show();
        };
        $scope.closeModal = function() {
            $scope.modal.hide();
        };
        $scope.$on('$destroy', function() {
            $scope.modal.remove();
        });

        return promise;
    };

    return {
        init: init
    }

})

And from the controller, pass the current scope of controller and modal template

.controller('UserProfileCtrl', function($scope, $state, user, ModalFactory) {    
    ModalFactory.init('image-view.html', $scope)
            .then(function(modal) {
                confirmationModal = modal;
                confirmationModal.show();
            });
})

Hope this is helpful for you.

Upvotes: 2

Related Questions