Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bug(MatDialog): When opening multiple dialogs in a short amount of time, some of them do not open #24037

Closed
1 task done
guimabdo opened this issue Nov 30, 2021 · 37 comments · Fixed by #24121
Closed
1 task done
Assignees
Labels
Accessibility This issue is related to accessibility (a11y) area: material/dialog P2 The issue is important to a large percentage of users, with a workaround

Comments

@guimabdo
Copy link

guimabdo commented Nov 30, 2021

Is this a regression?

  • Yes, this behavior used to work in the previous version

The previous version in which this bug was not present was

12.2.0

Description

When opening multiple dialogs in a short amount of time, some of them do not open

Reproduction

The bug can be noticed with the example below, when clicking the "Open Fast" button.

import { Component, TemplateRef, ViewChild } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';

@Component({
  selector: 'app-root',
  template: `
  <button (click)="openFast()">Open Fast</button>
  <button (click)="openSlow()">Open Slow</button>
  <ng-template let-data #template>
    {{data.message}}
    <button mat-dialog-close>close</button>
  </ng-template>
  `
})
export class AppComponent {
  @ViewChild("template") template?: TemplateRef<any>;
  constructor(private matDialog: MatDialog){}
  openFast(){
    this.open('1');
    setTimeout(() => this.open('2'), 1);
    setTimeout(() => this.open('3'), 50);
    setTimeout(() => this.open('4'), 100);
    setTimeout(() => this.open('5'), 150);
    setTimeout(() => this.open('6'), 200);
  }
  openSlow(){
    this.open('1');
    setTimeout(() => this.open('2'), 300);
    setTimeout(() => this.open('3'), 600);
    setTimeout(() => this.open('4'), 900);
    setTimeout(() => this.open('5'), 1200);
    setTimeout(() => this.open('6'), 1500);
  }

  private open(message: string){
    this.matDialog.open(this.template!, { 
      disableClose: true,
      data: { message } 
    });
  }
}

Expected Behavior

"Open fast" button should open 6 dialogs, just like the "Open slow" button.

Actual Behavior

The "Open fast" button opens only 2 dialogs.
On most attempts it opens (1) and (5), but in some cases (1) and (6) are open.

Environment

  • Angular: 13.0.0
  • CDK/Material: 13.0.0
  • Browser(s): Chrome
  • Operating System (e.g. Windows, macOS, Ubuntu): Windows
@guimabdo guimabdo added the needs triage This issue needs to be triaged by the team label Nov 30, 2021
@hijamoya
Copy link

hijamoya commented Dec 1, 2021

We encounter the same issue, If we call open in ngOnInit like below:

  ngOnInit() {
      setTimeout(() => this.open('2'), 0);
  }

The dialog dose not open anymore, it is fine in previous version.

However, if we just:

  ngOnInit() {
      this.open('2');
  }

The dialog will be opened correctly.

@lephyrus
Copy link

lephyrus commented Dec 2, 2021

We've also hit this issue. I'm sure it was introduced with this commit. There's a comment on it that points to the underlying issue.

The commit message says the goal is to [a]void opening multiple of the same dialog before animations complete by returning the previous MatDialogRef, but the change does not check whether the two dialogs are the same. This leads to really weird behaviour, which took us a long time to figure out. Here's our use case (which was working fine before):

// injected in real code
declare const dialog: MatDialog;

// somewhere in the codebase
const dialogA = dialog.open(DialogA); // dialog that shows a spinner while async thing A is happening

// somewhere else in codebase, gets executed as part of A succeeding but can fail independently
const dialogB = dialog.open(DialogB); // dialog that shows an error
dialogB.afterClosed().pipe(/* do stuff according to user response */);

The way the timing works out in our case, dialogB will sometimes be a reference to DialogB (MatDialogRef<DialogB>, as guaranteed by the typing of open()), but mostly it will be a reference to DialogA and (more crucially) dialog B wasn't opened and we're processing a response that was given for the other dialog.

Any chance you could take another look at this, @Splaktar?

@Splaktar
Copy link
Member

Splaktar commented Dec 2, 2021

Thank you for the example code. It would be great if you could provide a runnable StackBlitz to help us investigate this more efficiently.

@Splaktar Splaktar added the needs: clarification The issue does not contain enough information for the team to determine if it is a real bug label Dec 2, 2021
Splaktar referenced this issue Dec 2, 2021
- notify screen reader users that they have entered a dialog
- previously only the focused element would be read
  i.e. "Close Button Press Search plus Space to activate"
- now the screen reader user gets the normal dialog behavior, which is to
  read the dialog title, role, content, and then tell the user about the
  focused element
  - this matches the guidance here:
    https://www.w3.org/TR/wai-aria-practices-1.1/examples/dialog-modal/dialog.html
- Avoid opening multiple of the same dialog before animations complete by returning
  the previous `MatDialogRef`
- update material/dialog API golden file

Fixes #21840
@Splaktar
Copy link
Member

Splaktar commented Dec 2, 2021

Does this still happen when you import NoopAnimations instead of BrowserAnimations?

@Inject(ANIMATION_MODULE_TYPE)
animationMode?: 'NoopAnimations' | 'BrowserAnimations',

@Splaktar
Copy link
Member

Splaktar commented Dec 2, 2021

@lephyrus there is a lot of discussion about the tradeoffs and issues that we faced here in the following PRs

@Splaktar
Copy link
Member

Splaktar commented Dec 2, 2021

If I had a StackBlitz for this, I could try out a few quick things right now and see if I can provide a workaround, but since I don't, I probably won't have time to look into this again this week.

@Splaktar Splaktar added Accessibility This issue is related to accessibility (a11y) needs investigation A member of the team needs to do further investigation to determine the root cause and removed needs triage This issue needs to be triaged by the team labels Dec 2, 2021
@guimabdo guimabdo closed this as completed Dec 2, 2021
@guimabdo guimabdo reopened this Dec 2, 2021
@guimabdo
Copy link
Author

guimabdo commented Dec 2, 2021

Thank you for the example code. It would be great if you could provide a runnable StackBlitz to help us investigate this more efficiently.

@Splaktar
I tried but couldn't. I think StackBlitz has some issue with Angular 13:
stackblitz/core#1657

@Splaktar
Copy link
Member

Splaktar commented Dec 2, 2021

Doh. Thank you for linking that StackBlitz issue. Let me see if I can get you a working Angular v13 StackBlitz template.

@Splaktar
Copy link
Member

Splaktar commented Dec 2, 2021

PR #13466 could potentially help, so you could disable the animation or at least know how long they would run in these cases. However, it's not clear if or when that will land.

@hijamoya
Copy link

hijamoya commented Dec 3, 2021

@Splaktar Can you provide an option to disable this behavior?, this impacts our product so much, many dialogs does not open correctly after upgrade the version.

Thanks!

@Totati
Copy link
Contributor

Totati commented Dec 3, 2021

I've could make a stackblitz example, it can be forked.

@guimabdo
Copy link
Author

guimabdo commented Dec 3, 2021

My case is very similar to @lephyrus 's:
a busy indicator that may be followed by an error message. To work around I ended up changing the busy indicator to not use MatDialog.
I still have to review our applications for specific cases. I think there might be some form dialog that opens another prompt/alert dialog when opening. If I find this case, I'll add some delay.

@lephyrus
Copy link

lephyrus commented Dec 6, 2021

@lephyrus there is a lot of discussion about the tradeoffs and issues that we faced here in the following PRs

Thanks, @Splaktar. Here's what I'm not clear on: The current behaviour is that no dialog will be opened while another one is opening. Is that the intention of the change? In the PR discussion, there is talk of componentOrTemplateRef === this._lastDialogOpenType (which isn't part of the code now), the commit message says "avoid opening multiple of the same dialog", and the test for the behaviour uses the same component:

let dialogRef1: MatDialogRef<PizzaMsg>, dialogRef2: MatDialogRef<PizzaMsg>;
dialogRef1 = dialog.open(PizzaMsg);
dialogRef2 = dialog.open(PizzaMsg);

Currently, this would pass but seems very strange (i.e. the reference I get from open() might be totally unrelated to the component I passed, which also violates the return type):

fooRef = dialog.open(Foo);
barRef = dialog.open(Bar);
expect(barRef).toEqual(fooRef);
expect(barRef.componentInstance instanceof Foo).toBe(true);

@albert-asin
Copy link

adding a delay as @guimabdo says works as workarround

@markwhitfeld
Copy link

markwhitfeld commented Dec 9, 2021

FYI, StackBlitz now supports Angular 13. You can go ahead and make your repro if still needed.

Great news! Support for Angular 13 has finally rolled out.
...
For the next day or two (until 12 December 2021), there is a possibility of an initial slow down in starting up the dev server or running NGCC (or potentially even timeouts) as the packages repopulate into the cache as they are used, but it should come right after a refresh or two if you do have any issues. Anything else should be reported as a new issue.

Related StackBlitz issue comment: stackblitz/core#1657 (comment)

@Splaktar
Copy link
Member

Splaktar commented Dec 9, 2021

The current behaviour is that no dialog will be opened while another one is opening. Is that the intention of the change?

Yes.

In the PR discussion, there is talk of componentOrTemplateRef === this._lastDialogOpenType (which isn't part of the code now)

Yes, per code review feedback and due to breaking existing tests we moved away from that.

the commit message says "avoid opening multiple of the same dialog",

I think that part of the commit message just didn't get updated before the final merge of the PR. Sorry about that. This does cover the most common case we were focused on though, which was multiple clicks/key presses on a button (or other trigger) causing the dialog to be opened twice.

Currently, this would pass but seems very strange (i.e. the reference I get from open() might be totally unrelated to the component I passed, which also violates the return type)

Wouldn't that trigger a TypeScript compiler error?

@Splaktar
Copy link
Member

Splaktar commented Dec 9, 2021

Also on the topic of opening multiple dialogs at the same time, the Material Design guidelines for Dialogs include this description:

A dialog is a type of modal window that appears in front of app content to provide critical information or ask for a decision. Dialogs disable all app functionality when they appear, and remain on screen until confirmed, dismissed, or a required action has been taken.

There are a number of other recommendations there that clearly call out something like "dialog that shows a spinner while async thing A is happening" as an improper use of a dialog.

In those cases, you may want to look at building a custom component for your application using CDK Overlay which provides a number of position and scroll strategies.

In #6761 (comment), @crisbeto mentioned that we officially support opening multiple dialogs on the page. It's not clear if he was intending to say that we support opening multiple dialogs in a very short period of time (before they can animate open). That said, I believe that the fix there is what caused the a11y issue in #21840 that I just fixed.

@Splaktar Splaktar added needs: discussion Further discussion with the team is needed before proceeding and removed needs: clarification The issue does not contain enough information for the team to determine if it is a real bug labels Dec 9, 2021
@marsc
Copy link

marsc commented Dec 10, 2021

We're facing the same issue in our application since Angular 13. Rolled back to version 12.x. This is a breaking change! If this is the intended behaviour we've to do a very large refactoring just to update to version 13 again. Very bad.

@Splaktar
Copy link
Member

@marsc can you please share your use case and a reproduction?

@marsc
Copy link

marsc commented Dec 20, 2021

Also on the topic of opening multiple dialogs at the same time, the Material Design guidelines for Dialogs include this description:

A dialog is a type of modal window that appears in front of app content to provide critical information or ask for a decision. Dialogs disable all app functionality when they appear, and remain on screen until confirmed, dismissed, or a required action has been taken.

We have the same problem here. I do not know how to solve the following example:

  1. User decides to delete a file
  2. A MatDialog is opened "Are you sure you want to delete file XY?"
  3. After clicking "Yes, delete" another MatDialog needs to popup instantly with "Unable to delete File. No permission!"
    (In both cases the described behavior is needed and the two different dialogs will popup instantly, not needing arbitrary animation time)

How should I implement this usecase without using MatDialog two times directly after another?

@Kademlia in this case you can do something like this:

this.matDialog.open( ... ).afterClosed().subscribe(() => { this.matDialog.open( ... ); });

@dushkostanoeski
Copy link

@marsc there lies the problem. If the task from the first dialog finishes before the first dialog is fully opened, the second dialog won't show up.

@Splaktar Splaktar added P2 The issue is important to a large percentage of users, with a workaround and removed needs: discussion Further discussion with the team is needed before proceeding needs investigation A member of the team needs to do further investigation to determine the root cause labels Jan 13, 2022
@Splaktar
Copy link
Member

PR #24121 has been tested and reviewed. It should land shortly and fix this.

@aikrez
Copy link

aikrez commented Feb 3, 2022

Any ETA? This is the only thing that is blocking the upgrade of my project.

@Splaktar
Copy link
Member

Splaktar commented Feb 3, 2022

@aikrez there is nothing blocking the PR from being merged. It's been fully reviewed and approved. I can't give an ETA though.

@Splaktar
Copy link
Member

Splaktar commented Feb 3, 2022

@aikrez okay, I do have an update. There have been some undesirable side effects of this change in the presubmit tests. They are being investigated and potential solutions and workarounds are being evaluated.

@rasoulMrz

This comment was marked as abuse.

@rasoulMrz
Copy link

@aikrez okay, I do have an update. There have been some undesirable side effects of this change in the presubmit tests. They are being investigated and potential solutions and workarounds are being evaluated.

Any updates?

@P-de-Jong
Copy link

Would like to know too when this PR is getting merged. We're having a lot of problems with this issue at the moment 😢

@proggler23
Copy link

proggler23 commented Mar 3, 2022

It seems like opening a dialog from within another dialog also fails if there happened to be an error shortly before (within the animation duration). I cannot listen on "dialogOpened" observable, as this only triggers once, but the error will trigger the dialogContainer.start animation again.

crisbeto added a commit to crisbeto/material2 that referenced this issue Mar 4, 2022
For a long time we used to move focus into the dialog after the opening animation was finished, because it allowed for the content to settle in case something like an autocomplete needed to open. This approach has caused us accessibility issues in the past, because we've had to find ways to prevent the user from interacting with content behind the dialog while the opening animation is running.

Initially we used to move focus to the dialog container before transferring it into the dialog, but that ended up interrupting the screen reader. In a later change we started returning the previous dialog ref if the same kind of dialog is opened while the animation is still running. The problem with this approach is that it doesn't allow for multiple dialog to be opened quickly (see angular#24037).

These changes aim to address the root cause by moving focus immediately after the dialog content is attached. If this causes regressions with autocompletes inside dialogs, we now have APIs that allow users to customize the focus behavior.

Fixes angular#24037.
@crisbeto
Copy link
Member

crisbeto commented Mar 5, 2022

I reworked the fix for this on Friday to make it easier to merge and got it passing all the internal checks. It'll be merged in on Monday and should be out in the next patch release.

crisbeto added a commit that referenced this issue Mar 7, 2022
…24121)

* fix(material/dialog): don't wait for animation before moving focus

For a long time we used to move focus into the dialog after the opening animation was finished, because it allowed for the content to settle in case something like an autocomplete needed to open. This approach has caused us accessibility issues in the past, because we've had to find ways to prevent the user from interacting with content behind the dialog while the opening animation is running.

Initially we used to move focus to the dialog container before transferring it into the dialog, but that ended up interrupting the screen reader. In a later change we started returning the previous dialog ref if the same kind of dialog is opened while the animation is still running. The problem with this approach is that it doesn't allow for multiple dialog to be opened quickly (see #24037).

These changes aim to address the root cause by moving focus immediately after the dialog content is attached. If this causes regressions with autocompletes inside dialogs, we now have APIs that allow users to customize the focus behavior.

Fixes #24037.

* fixup! fix(material/dialog): don't wait for animation before moving focus

(cherry picked from commit 92863cc)
crisbeto added a commit that referenced this issue Mar 7, 2022
…24121)

* fix(material/dialog): don't wait for animation before moving focus

For a long time we used to move focus into the dialog after the opening animation was finished, because it allowed for the content to settle in case something like an autocomplete needed to open. This approach has caused us accessibility issues in the past, because we've had to find ways to prevent the user from interacting with content behind the dialog while the opening animation is running.

Initially we used to move focus to the dialog container before transferring it into the dialog, but that ended up interrupting the screen reader. In a later change we started returning the previous dialog ref if the same kind of dialog is opened while the animation is still running. The problem with this approach is that it doesn't allow for multiple dialog to be opened quickly (see #24037).

These changes aim to address the root cause by moving focus immediately after the dialog content is attached. If this causes regressions with autocompletes inside dialogs, we now have APIs that allow users to customize the focus behavior.

Fixes #24037.

* fixup! fix(material/dialog): don't wait for animation before moving focus
@DoodahProductions
Copy link

Hello, thanks for this fix !
I'm not sure what npm package I should update to fetch this fix, may you help me ? Thanks :)

@crisbeto
Copy link
Member

crisbeto commented Mar 9, 2022

The fix will be in @angular/material, but we haven't made a release with the new code yet. It should be out later today/tomorrow.

@Splaktar
Copy link
Member

This fix is included in 13.2.6 and 14.0.0-next.6.

@P-de-Jong
Copy link

We have updated our applications to the new patch and everything seems to work as intended. Thanks!

forsti0506 pushed a commit to forsti0506/components that referenced this issue Apr 3, 2022
…ngular#24121)

* fix(material/dialog): don't wait for animation before moving focus

For a long time we used to move focus into the dialog after the opening animation was finished, because it allowed for the content to settle in case something like an autocomplete needed to open. This approach has caused us accessibility issues in the past, because we've had to find ways to prevent the user from interacting with content behind the dialog while the opening animation is running.

Initially we used to move focus to the dialog container before transferring it into the dialog, but that ended up interrupting the screen reader. In a later change we started returning the previous dialog ref if the same kind of dialog is opened while the animation is still running. The problem with this approach is that it doesn't allow for multiple dialog to be opened quickly (see angular#24037).

These changes aim to address the root cause by moving focus immediately after the dialog content is attached. If this causes regressions with autocompletes inside dialogs, we now have APIs that allow users to customize the focus behavior.

Fixes angular#24037.

* fixup! fix(material/dialog): don't wait for animation before moving focus
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Apr 11, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Accessibility This issue is related to accessibility (a11y) area: material/dialog P2 The issue is important to a large percentage of users, with a workaround
Projects
None yet
Development

Successfully merging a pull request may close this issue.