Flex-layout: Closing print preview in Chrome & Edge triggers the xs breakpoint

Created on 9 Apr 2020  路  9Comments  路  Source: angular/flex-layout

Bug Report

I have a layout using fxShow and fxHide with lt-md to create a responsive navigation, using the default flex-layout breakpoints. When using print in Chrome & IE the preview dialogue opens with the content I have selected to be printed. When the dialogue closes it activates the xs breakpoint.

What is the expected behavior?

Once save or cancel have been clicked the dialogue closes and the layout remains unchanged.

What is the current behavior?

Everything appears correctly until the dialogue closes. At that point it appears to trigger the xs breakpoint. It does correct itself while the dialogue is open when further using print.

What are the steps to reproduce?

https://stackblitz.com/edit/angular-flex-layout-print-preview-stuck-in-xs

I noticed in the quick stackblitz I created that if I resize the window to be less than the xs breakpoint or resize he window twice after closing the print dialogue, it seems to fix the issue and work as expected.

What is the use-case or motivation for changing an existing behavior?

The existing behaviour seems to be odd and may not be working correctly.

Which versions of Angular, Material, OS, TypeScript, browsers are affected?

angular: 9.0.1
flex-layout: 9.0.0-beta.29

Most helpful comment

Seems this bug is a duplicate of #1201 (or the opposite).

All 9 comments

I have a very similar issue, and i'm using gt-xs and lt-sm breakpoints as well as with fxHide directives. Upon closing the print dialog, the UI gets really messed up. Unfortunately I don't have a way to disclose much code for it as it's a work project and I'm not entirely sure where and what gets triggered exactly.

One example would actually be this:

<div class="wide-layout-container">
    <div class="wide-layout-wrapper" fxLayout="row" fxLayoutAlign="center">
        <div class="wide-layout-content" fxLayout="column" fxLayoutAlign="center" fxFlex="0 1 calc(100% - 64px)" fxFlex.lt-sm="0 1 100%" fxFill>
            <router-outlet #outlet="outlet"></router-outlet>
        </div>
    </div>
</div>

Here when print is activated, the fxFlex attribute does not seem to reset to 0 1 calc(100% - 64px), as the width becomes 100% instead of calc(100% - 64px).

Also found an issue raised by someone else in StackOverflow a while ago:
https://stackoverflow.com/questions/60948542/angular-flex-layout-stopped-working-after-print-dialog

I'm using:
chrome 80.0.3987.132
angular 9.1.2
flex-layout 9.0.0-beta.29

I have the exact same issue, if i use FlexLayoutModule.withConfig({
useColumnBasisZero: false,
disableDefaultBps: true
}), it will disable all the breakpoints, and it won't be mobile responsive but CTRL + P will work.

How can we have both functionalities working? CTRL + P without breaking the layout on close and keeping the breakpoints on mobile

Same issue :(

Fortunately for us, the content we need to print doesn't really have any client state apart from the route, so we can hack around this temporarily with:

window.addEventListener('afterprint', () => {
  location.reload();
});

Not really ideal though!

Edit: Probably disregard this, superseded by next comment.

Ok, so I've done some poking... I'm almost certain the issue is related to these changes and the collectActivations calls that are made when isPrintingBeforeAfterEvent is true.

At this stage, collectActivations is called with all of the following breakpoints: 'gt-xs', 'gt-sm', 'gt-md', 'gt-lg', 'xl', 'lt-xl', 'lt-lg', 'lt-md', 'lt-sm', 'xs', which all get added to deactivations. So if I'm understanding correctly, it's not just that the 'xs' breakpoint is being activated on exiting print, but it's effectively both the 'xs' and 'xl' breakpoints 馃槃

This seems to be confirmed as the issue goes away when I add the following incredibly horrible hack to the ctor of my parent component:

  constructor(
    private readonly printHook: PrintHook,
  ) {
    const printHookAny = this.printHook as any;
    const orig = printHookAny.collectActivations;
    printHookAny.collectActivations = (event) => {
      if (!printHookAny.isPrintingBeforeAfterEvent) {
        orig.bind(printHookAny, event)();
      }
    };
  }

(effectively just patching collectActivations so it doesn't get called when isPrintingBeforeAfterEvent is true).

NOTE: I do not remotely recommend anyone use this hack anywhere near production. I don't claim to fully understand the code it's messing with or what else doing that might break. Edit: yes, as I expected, this breaks restoring you to the right state after printing at breakpoints other than your default ones (I think)

Further digging: PrintHook.collectActivations gets called as a result of the following code in MediaMarshaller:

  private observeActivations() {
    const target = this as unknown as HookTarget;
    const queries = this.breakpoints.items.map(bp => bp.mediaQuery);

    this.matchMedia
        .observe(this.hook.withPrintQuery(queries))
        .pipe(
            tap(this.hook.interceptEvents(target)),
            filter(this.hook.blockPropagation())
        )
        .subscribe(this.onMediaChange.bind(this));
  }

(by the tap operator, through PrintHook.interceptEvents).

The actual issue seems to be that this.matchMedia.observe(this.hook.withPrintQuery(queries)) is emitting:

  • The correct deactivated queries with matched: false
  • AND every query from your current breakpoint down to 'xs' (or whatever is smallest presumably), also with matched: false.

So where my last comment said we were effectively activating 'xs' and 'xl' on exiting print, we're actually activating the correct current breakpoint (which for me at the time was 'xl') and also 'xs', incorrectly.

So I think the question is why is this.matchMedia.observe(this.hook.withPrintQuery(queries)) emitting incorrect extra items (or should they be emitted but filtered)?

Here's a log of the events that hit the callback inside of PrintHook.interceptEvents when I pressed print, with my browser window currently at the 'md' breakpoint width (phase numbers added by me):

Phase 1:
MediaChange聽{matches: false, mediaQuery: "screen and (min-width: 600px)", mqAlias: "", suffix: "", priority: 0,聽鈥
MediaChange聽{matches: false, mediaQuery: "screen and (min-width: 960px)", mqAlias: "", suffix: "", priority: 0,聽鈥
MediaChange聽{matches: false, mediaQuery: "screen and (max-width: 1919.9px)", mqAlias: "", suffix: "", priority: 0,聽鈥
MediaChange聽{matches: false, mediaQuery: "screen and (max-width: 1279.9px)", mqAlias: "", suffix: "", priority: 0,聽鈥
MediaChange聽{matches: false, mediaQuery: "screen and (min-width: 960px) and (max-width: 1279.9px)", mqAlias: "", suffix: "", priority: 0,聽鈥
Phase 2:
MediaChange聽{matches: true, mediaQuery: "print", mqAlias: "", suffix: "", priority: 0,聽鈥
Phase 3:
MediaChange聽{matches: false, mediaQuery: "screen and (max-width: 959.9px)", mqAlias: "", suffix: "", priority: 0,聽鈥
MediaChange聽{matches: false, mediaQuery: "screen and (max-width: 599.9px)", mqAlias: "", suffix: "", priority: 0,聽鈥
MediaChange聽{matches: false, mediaQuery: "screen and (min-width: 0px) and (max-width: 599.9px)", mqAlias: "", suffix: "", priority: 0,聽鈥
Phase 4:
MediaChange聽{matches: true, mediaQuery: "screen and (min-width: 600px)", mqAlias: "", suffix: "", priority: 0,聽鈥
MediaChange聽{matches: true, mediaQuery: "screen and (min-width: 960px)", mqAlias: "", suffix: "", priority: 0,聽鈥
MediaChange聽{matches: true, mediaQuery: "screen and (max-width: 1919.9px)", mqAlias: "", suffix: "", priority: 0,聽鈥
MediaChange聽{matches: true, mediaQuery: "screen and (max-width: 1279.9px)", mqAlias: "", suffix: "", priority: 0,聽鈥
MediaChange聽{matches: true, mediaQuery: "screen and (min-width: 960px) and (max-width: 1279.9px)", mqAlias: "", suffix: "", priority: 0,聽鈥
Phase 5:
MediaChange聽{matches: false, mediaQuery: "print", mqAlias: "", suffix: "", priority: 0,聽鈥

So:

  1. We get the correct set of matches: false events that set up deactivations correctly.
  2. We get a print matches: true event
  3. We get the "bad" events that incorrectly add 'xs' and lt-stuff from your correct breakpoint downwards to deactivations
  4. We get a set of matches: true events mirroring 1, but these are fine because collectActivations ignores them
  5. We get a print maches: false event mirroring 2

Seems like there are two solutions, either:

  • MatchMedia should not be emitting the events seen in phase 3 OR
  • When we get the print event in phase 2, something could be set on PrintHook to make collectActivations ignore further matches: false events until after phase 5 so that the phase 3 events don't do any harm.

Without knowing the original intention of all the code and what else MatchMedia gets used for internally, I'm not sure which is the "correct" fix, but hopefully this information is useful in moving towards a solution?

I should also say that all the testing I've done is in Chrome, so no idea currently if any of this is browser-dependent.

Hello guys,

Just to let you know I am facing exactly the same issue with flex-layout 9.0.0-beta.31

@jgbpercy your code helped me to "fix" it temporarly but it does not work all time.

As example I re-used the inital example with your suggested fix here:
https://stackblitz.com/edit/angular-flex-layout-print-preview-stuck-in-xs-9ujsx9

Do the following:
1) after loading increase the width of the right panel to see the div in red & yellow
2) click print button
3) click cancel
4) observe the div is back to green & red (instead of staying into red & yellow)
5) if you click again the print button and click again cancel, then this time div is red & yellow

Seems this bug is a duplicate of #1201 (or the opposite).

Ah yeah from having a glance at that duplicate issue and from what I later learned of the underlying problem, the workaround code in there is a much better bet than what I posted in my first comment above 馃憤

Was this page helpful?
0 / 5 - 0 ratings