ionic-team / ionic-framework

A powerful cross-platform UI toolkit for building native-quality iOS, Android, and Progressive Web Apps with HTML, CSS, and JavaScript.
https://ionicframework.com
MIT License
51.1k stars 13.51k forks source link

bug: Menu freezes if closed and disabled while animation is in progress #20092

Closed damirarh closed 1 year ago

damirarh commented 4 years ago

Bug Report

Ionic version:

[x] 4.x

Current behavior:

If you attempt to close and disable the menu while the opening animation is still in progress, the menu will freeze in place. The application will need to be reloaded to resume normal operation. The following error message is emitted in the console:

vendor.js:104532 Error: ASSERT: can not be animating
    at assert (vendor.js:106591)
    at Menu.updateState (38.js:410)
    at Menu.disabledChanged (38.js:82)
    at vendor.js:105830
    at Array.forEach (<anonymous>)
    at setValue (vendor.js:105827)
    at HTMLElement.set [as disabled] (vendor.js:105868)
    at Object.enable (vendor.js:106778)

Expected behavior:

The menu should close and become disabled even if these calls are issued while the opening animation is in progress. The opening animation should either be interrupted or continue till the end before the menu is closed.

Steps to reproduce:

I encountered the issue with a menu item that triggered navigation to another page. In the ionViewWillLeave method of the current page I had the following code:

await this.menuCtrl.close();
await this.menuCtrl.enable(false);

The key to reproducing the issue is to click on the menu item quickly after clicking the menu toggle button to open the menu. The menu animation must still be in progress at that point.

The issue can also be reproduced if the two lines above are directly placed in the menu item handler. That's what I did in the sample application.

Related code:

https://github.com/damirarh/ionic-menu-freezing

Other information:

Ionic info:

Ionic:

   Ionic CLI                     : 5.2.8 (C:\Users\damir\AppData\Roaming\npm\node_modules\ionic)
   Ionic Framework               : @ionic/angular 4.11.7
   @angular-devkit/build-angular : 0.801.3
   @angular-devkit/schematics    : 8.1.3
   @angular/cli                  : 8.1.3
   @ionic/angular-toolkit        : 2.1.1

Utility:

   cordova-res : not installed
   native-run  : 0.2.7

System:

   NodeJS : v10.16.0 (C:\Program Files\nodejs\node.exe)
   npm    : 6.9.0
   OS     : Windows 10
djabif commented 4 years ago

I can confirm this bug is still happening in Ionic 5 rc2. How should we do to enable the side menu and not getting this error? I think this bug is similar as https://github.com/ionic-team/ionic/issues/19676 Thanks

damirarh commented 4 years ago

@djabif I added a guard to all menu item handlers which waits for the menu open animation to complete:

if (await this.menuCtrl.isOpen()) {
  // perform the requested page navigation
}

I wrote a blog post about it with a more detailed explanation and a link to a sample project in the comments.

RobinGiel commented 4 years ago

You can do this:

<ion-menu [swipeGesture]="menuSwipe.swipe" (ionDidClose)="didClose($event)">

in ts:

didClose(e){ this.menu.swipeGesture(false); }

Radecom commented 4 years ago

The same happens to me with: "@ionic/angular": "^5.0.6"

burdokat commented 4 years ago

This happened to me. First I had the menu enabled and disabled inside a guard. But I had problems with the menu being activated inside pages where it should not. Upon code refactoring, the problem started according to #19676, entering debug mode. Upon further inspection, it just boils down to order of execution. If you open the menu, it must be enabled first. And, any animation and transitions of the menu must be executed successfully, without being halted by any other means.

So, for more verbosity, just keep in mind that the menu should only be opened after the menu is successfully enabled. And whenever you are disabling the menu, make sure that it is closed first and that the close animation succeeds. Failure to do so, will throw you errors. With that in mind, the solution really depends on the approach you are using in your code. For example: when deactivating any menu, you can actually wait for the close animation to succeed with this code snippet:

async yourAsyncMenuDeactivationFunction(menuId: string): Promise<boolean>{
  return await this.menuCtrl.close(menuId).then(() => this.menuCtrl.isOpen(menuId));
}

The close() method closes the menu, and then the isOpen() method will emit after the close animation succeeds, hence, isOpen() will always emit false. Then you can return that promise and use it to execute anything else afterwards (deactivate the menu, navigate back, guards, logout, etc...)

Just a personal comment: I really don't think this should be reported as a bug. I think the solution has to be documented on how the menu should be properly used. Just saying.

dkerkez commented 4 years ago

This is working for me.

Try disabling the menu after the menu is closed. Add (ionDidClose)="menuDidClose()" to menu HTML and inside menuDidClose disable menu.

public menuDidClose(){
    if(this.disableSideMenu)
    {
      this.menu.enable(false);
      this.menu.swipeGesture(false);
      this.disableSideMenu = false;
    }
  }
anayarojo commented 4 years ago

The following works for me:

Replace this code:

<ion-menu>
    ...
    <ion-menu-toggle>
        <ion-item [routerLink]="['./']" (click)="signOut()" class="item-gray" color="sofiaxt-dark" lines="full" detail>
        <ion-label>Cerrar sesión</ion-label>
        <ion-icon slot="start" name="lock-closed"></ion-icon>
        </ion-item>
    </ion-menu-toggle>
    ...
</ion-menu>
public async signOut() {
    const loading = await this.uiService.showLoading('Closing session...');
    await this.authenticationService.signOut();
    this.navController.navigateRoot('/login');
    loading.dismiss();
}

With this one:

<ion-menu>
    ...
    <ion-item [routerLink]="['./']" (click)="signOut()" class="item-gray" color="sofiaxt-dark" lines="full" detail>
        <ion-label>Cerrar sesión</ion-label>
        <ion-icon slot="start" name="lock-closed"></ion-icon>
    </ion-item>
    ...
</ion-menu>
public async signOut() {
    const loading = await this.uiService.showLoading('Cerrando...');
    if (await this.menuController.isOpen('userMenu')) {
        await this.menuController.close('userMenu');
        await this.menuController.enable(false, 'userMenu');
        await this.authenticationService.signOut();
        this.navController.navigateRoot('/login');
    }
    loading.dismiss();
}

Summary:

lincolnthree commented 1 year ago

I just noticed this again today (Ionic 7), when clicking on ion-menu-button - not sure why it happened but the menu started opening, then slammed closed (disappeared) and then things went back to normal from there on.

helpers-c8b0fe32.js:335 ASSERT: _before() should be called while animating
assert @ helpers-c8b0fe32.js:335
afterAnimation @ ion-menu_3.entry.js:475
(anonymous) @ ion-menu_3.entry.js:291
asyncGeneratorStep @ asyncToGenerator.js:3
_next @ asyncToGenerator.js:22
invoke @ zone.js:368
run @ zone.js:127
(anonymous) @ zone.js:1257
invokeTask @ zone.js:402
runTask @ zone.js:171
drainMicroTaskQueue @ zone.js:581
Promise.then (async)
nativeScheduleMicroTask @ zone.js:557
scheduleMicroTask @ zone.js:568
scheduleTask @ zone.js:392
scheduleTask @ zone.js:214
scheduleMicroTask @ zone.js:234
scheduleResolveOrReject @ zone.js:1247
resolvePromise @ zone.js:1184
(anonymous) @ zone.js:1100
(anonymous) @ zone.js:1116
onFinish.oneTimeCallback @ animation-80d2c7ea.js:887
(anonymous) @ animation-80d2c7ea.js:556
afterAnimation @ animation-80d2c7ea.js:555
animationFinish @ animation-80d2c7ea.js:571
animationFinish @ animation-80d2c7ea.js:573
webAnimations.<computed>.onfinish @ animation-80d2c7ea.js:619
Show 26 more frames
12:00:25.074 zone.js:1043 Unhandled Promise rejection: ASSERT: _before() should be called while animating ; Zone: <root> ; Task: Promise.then ; Value: Error: ASSERT: _before() should be called while animating
    at assert (helpers-c8b0fe32.js:337:11) [<root>]
    at Menu.afterAnimation (ion-menu_3.entry.js:475:11) [<root>]
    at :8100/node_modules_ionic_core_dist_esm_ion-menu_3_entry_js.js:336:14 [<root>]
    at Generator.next (<anonymous>) [<root>]
    at asyncGeneratorStep (asyncToGenerator.js:3:1) [<root>]
    at _next (asyncToGenerator.js:22:1) [<root>]
    at :8100/polyfills.js:10455:28 [<root>]
    at drainMicroTaskQueue (zone.js:581:35) [<root>] Error: ASSERT: _before() should be called while animating
    at assert (http://localhost:8100/vendor.js:24602:11) [<root>]
    at Menu.afterAnimation (http://localhost:8100/node_modules_ionic_core_dist_esm_ion-menu_3_entry_js.js:515:60) [<root>]
    at http://localhost:8100/node_modules_ionic_core_dist_esm_ion-menu_3_entry_js.js:336:14 [<root>]
    at Generator.next (<anonymous>) [<root>]
    at asyncGeneratorStep (http://localhost:8100/vendor.js:190701:24) [<root>]
    at _next (http://localhost:8100/vendor.js:190720:9) [<root>]
    at http://localhost:8100/polyfills.js:10455:28 [<root>]
    at drainMicroTaskQueue (http://localhost:8100/polyfills.js:9792:23) [<root>]
a
liamdebeasi commented 1 year ago

Hi everyone,

I appreciate everyone's patience while we work to resolve this issue. Here is a dev build with a proposed fix if anyone is interested in testing: 7.4.3-dev.11696264821.1755dd6a

Angular

npm install @ionic/angular@7.4.3-dev.11696264821.1755dd6a

React

npm install @ionic/react@7.4.3-dev.11696264821.1755dd6a @ionic/react-router@7.4.3-dev.11696264821.1755dd6a

Vue

npm install @ionic/vue@7.4.3-dev.11696264821.1755dd6a @ionic/vue-router@7.4.3-dev.11696264821.1755dd6a

Vanilla JavaScript

npm install @ionic/core@7.4.3-dev.11696264821.1755dd6a
liamdebeasi commented 1 year ago

Thanks for the issue. This has been resolved via https://github.com/ionic-team/ionic-framework/pull/28268, and a fix will be available in an upcoming release of Ionic Framework.

ionitron-bot[bot] commented 1 year ago

Thanks for the issue! This issue is being locked to prevent comments that are not relevant to the original issue. If this is still an issue with the latest version of Ionic, please create a new issue and ensure the template is fully filled out.