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

Sidenav: content area size not calculated properly with animation #9837

Open
KlausHans opened this issue Feb 8, 2018 · 9 comments
Open

Sidenav: content area size not calculated properly with animation #9837

KlausHans opened this issue Feb 8, 2018 · 9 comments
Labels
area: material/sidenav P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent

Comments

@KlausHans
Copy link

Bug, feature request, or proposal:

Bug

What is the expected behavior?

The size of the content area is calculated correctly according to the width of the sidenav menu.

What is the current behavior?

The calculation is wrong. The margin-left value of the content area is inverted to the width defined in the animation.

What are the steps to reproduce?

https://stackblitz.com/edit/angular-etpys5
(to see the issue you have to click a few times on the expand button)

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

@angular/material@5.2.0
@angular/cdk@5.2.0
@angular/core@5.2.4
@angular/animations@5.2.4

@mmalerba mmalerba added the P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent label Feb 16, 2018
@mmalerba
Copy link
Contributor

It looks like a change detection issue. It's not exactly ideal, but you can use the start and done handlers on the animation to start and stop a requestAnimationFrame ticker that will trigger the appropriate change detection: https://stackblitz.com/edit/angular-o5fjz1?file=app/app.component.ts

@crisbeto Any way you can think of that we can do this for people?

@crisbeto
Copy link
Member

I'm not sure whether there's an efficient way to do it, apart from using a ResizeObserver which has pretty bad browser support. For these kinds of cases it might be better to make _updateContentMargins a part of the public API so people can trigger a recalculation manually.

@KlausHans
Copy link
Author

KlausHans commented Mar 19, 2019

With the latest Angular (Material) version the behavior changed slightly. But its still broken.
https://stackblitz.com/edit/angular-kzia2e
@angular/animations7.2.9
@angular/cdk7.3.5
@angular/common7.2.9
@angular/compiler7.2.9
@angular/core7.2.9
@angular/material7.3.5

@robert-king
Copy link

I'm facing a similar issue - I adjust the width of the side-nav from 200 to 79 pixels:

sidenav component sets its width internally with hostbinding:

@HostBinding('style.width') width = '200px';

  toggleCollapse() {
    this.collapse = !this.collapse;
    this.width = this.collapse ? '79px' : '200px'
  }

but mat-sidenav-content margin-left stays at 200px when the side-nav changes to 79px. It makes collapsing the side-nav redundant.

<mat-sidenav-container>
  <mat-sidenav
    #sidenav
    [autoSize]="true" <--- INVALID, NOT AN OPTION FOR MAT-SIDENAV
    [mode]="(isBarkMobile$ | async) ? 'over': 'side'"
    [opened]="sideNavOpen"
    [disableClose]="true"
    [fixedInViewport]="(isBarkMobile$ | async)"
    [fixedTopGap]="0"
    [fixedBottomGap]="0">
    <shared-sidenav (closeSideNav)="closeSideNavIfMobile()"> <-- WIDTH ADJUSTS 200 -> 79!!
    </shared-sidenav>
  </mat-sidenav>

  <mat-sidenav-content> <-- MARGIN-RIGHT STAYS AT 200PX and doesnt go to 79px
    <router-outlet></router-outlet>
    <div style="display: none">
      <!-- preload these icons -->
      <mat-icon svgIcon="close-2"></mat-icon>
      <mat-icon svgIcon="offline"></mat-icon>
    </div>
  </mat-sidenav-content>
</mat-sidenav-container>
@robert-king
Copy link

Here's my temporary fix:

toggleCollapse() {
    this.collapse = !this.collapse;
    this.width = this.collapse ? '79px' : '200px'
    document.querySelector('.mat-drawer-content.mat-sidenav-content').style.marginLeft = this.width;
  }
@KlausHans
Copy link
Author

Still broken.
https://stackblitz.com/edit/angular-rf4mmn

@angular/animations9.1.0
@angular/cdk9.2.0
@angular/common9.1.0
@angular/compiler9.1.0
@angular/core9.1.0
@angular/forms9.1.0
@angular/material9.2.0
@angular/platform-browser9.1.0
@angular/platform-browser-dynamic9.1.0
@angular/router9.1.0
@meblum
Copy link

meblum commented May 20, 2020

Any updates on this?

@empinator
Copy link

this workaround with setTimeout worked for me

export class AppComponent implements  AfterViewInit {

  @ViewChild('sidenav') 
  private sidenav: MdSidenav;

  ngAfterViewInit(): void {
    setTimeout(() => {
      this.sidenav.open();
    }, 250);
  }
}

Source: #6743 (comment)

@andrewalderson
Copy link

andrewalderson commented Dec 21, 2021

I have been having these same issues and I have not been able to find a way to animate the width change of the drawer with the current functionality of these components. This is just one thing that I don't think was taken into consideration when the autosize functionality was added. It works great as long as the width of the drawer is not animated.

The main problem I have encountered is the 'mat-drawer-transition' css class. It doesn't get added until the drawer is toggled for the first time so the content container won't animate unless the drawer has been toggled and once added it causes issues with the animations if you animate the width of the drawer. The original issue reported here is caused by this.

My temporary solution to this is to add an appropriately named directive to the MatSidenav or MatDrawer component.

@Directive({
  selector: "[appDrawerAutosizeHack]",
})
export class DrawerAutosizeHackDirective implements OnInit, OnDestroy {
  constructor(
    @Optional() @Self() @Host() drawer: MatDrawer,
    @Optional() @Self() @Host() sidenav: MatSidenav,
    @Optional() drawerContainer: MatDrawerContainer,
    @Optional() sidenavContainer: MatSidenavContainer,
    @Optional() @Inject(DOCUMENT) private _doc: Document,
    private _el: ElementRef<HTMLElement>
  ) {
    this._drawer = drawer ?? sidenav;
    this._container = drawerContainer ?? sidenavContainer;
  }

  private _drawer?: MatDrawer | MatSidenav;
  private _container?: MatDrawerContainer | MatSidenavContainer;

  private _destroyed = new Subject<void>();

  private resizeObserver?: ResizeObserver;

  ngOnInit(): void {
    this._drawer?.openedChange
      .pipe(takeUntil(this._destroyed))
      .subscribe(() => {
        this._doc
          .querySelector(".mat-drawer-transition")
          ?.classList.remove("mat-drawer-transition");
      });

    this.resizeObserver = new ResizeObserver((entries) => {
      this._container?.updateContentMargins();
    });
    this.resizeObserver.observe(this._el.nativeElement);
  }

  ngOnDestroy(): void {
    this._destroyed.next();
    this._destroyed.complete();

    this.resizeObserver?.unobserve(this._el.nativeElement);
  }
}

This just simply removes the 'mat-drawer-transition' class and manually updates the content margins of the container. This is not ideal because it relies on implementation details (the name of the css class and the fact that it is added each time the animation starts) but it does keep this hack isolated. Doing this also means that the autosize property on the container is superfluous.

The easiest fix for this is to change the MatDrawerContainer class to add the 'mat-drawer-transition' class when the drawer animation starts and remove it when it ends. This would allow developers to handle their own functionality if they want to transition the drawer size.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: material/sidenav P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent
8 participants