diff --git a/src/lib/sidenav/drawer.spec.ts b/src/lib/sidenav/drawer.spec.ts
index c56504787d32..493490d8b6bf 100644
--- a/src/lib/sidenav/drawer.spec.ts
+++ b/src/lib/sidenav/drawer.spec.ts
@@ -1,4 +1,11 @@
-import {fakeAsync, async, tick, ComponentFixture, TestBed} from '@angular/core/testing';
+import {
+ fakeAsync,
+ async,
+ tick,
+ ComponentFixture,
+ TestBed,
+ discardPeriodicTasks,
+} from '@angular/core/testing';
import {Component, ElementRef, ViewChild} from '@angular/core';
import {By} from '@angular/platform-browser';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
@@ -422,6 +429,7 @@ describe('MatDrawerContainer', () => {
DrawerDelayed,
DrawerSetToOpenedTrue,
DrawerContainerStateChangesTestApp,
+ AutosizeDrawer,
],
});
@@ -523,6 +531,30 @@ describe('MatDrawerContainer', () => {
expect(container.classList).not.toContain('mat-drawer-transition');
}));
+ it('should recalculate the margin if a drawer changes size while open in autosize mode',
+ fakeAsync(() => {
+ const fixture = TestBed.createComponent(AutosizeDrawer);
+
+ fixture.detectChanges();
+ fixture.componentInstance.drawer.open();
+ fixture.detectChanges();
+ tick();
+ fixture.detectChanges();
+
+ const contentEl = fixture.debugElement.nativeElement.querySelector('.mat-drawer-content');
+ const initialMargin = parseInt(contentEl.style.marginLeft);
+
+ expect(initialMargin).toBeGreaterThan(0);
+
+ fixture.componentInstance.fillerWidth = 200;
+ fixture.detectChanges();
+ tick(10);
+ fixture.detectChanges();
+
+ expect(parseInt(contentEl.style.marginLeft)).toBeGreaterThan(initialMargin);
+ discardPeriodicTasks();
+ }));
+
});
@@ -676,3 +708,17 @@ class DrawerContainerStateChangesTestApp {
renderDrawer = true;
}
+
+@Component({
+ template: `
+
+
+ Text
+
+
+ `,
+})
+class AutosizeDrawer {
+ @ViewChild(MatDrawer) drawer: MatDrawer;
+ fillerWidth = 0;
+}
diff --git a/src/lib/sidenav/drawer.ts b/src/lib/sidenav/drawer.ts
index e41b45fb317c..dbf9c75cf80e 100644
--- a/src/lib/sidenav/drawer.ts
+++ b/src/lib/sidenav/drawer.ts
@@ -28,6 +28,7 @@ import {
Output,
QueryList,
ViewEncapsulation,
+ InjectionToken,
} from '@angular/core';
import {DOCUMENT} from '@angular/common';
import {merge} from 'rxjs/observable/merge';
@@ -35,6 +36,7 @@ import {filter} from 'rxjs/operators/filter';
import {take} from 'rxjs/operators/take';
import {startWith} from 'rxjs/operators/startWith';
import {takeUntil} from 'rxjs/operators/takeUntil';
+import {debounceTime} from 'rxjs/operators/debounceTime';
import {map} from 'rxjs/operators/map';
import {Subject} from 'rxjs/Subject';
import {Observable} from 'rxjs/Observable';
@@ -54,6 +56,9 @@ export class MatDrawerToggleResult {
constructor(public type: 'open' | 'close', public animationFinished: boolean) {}
}
+/** Configures whether drawers should use auto sizing by default. */
+export const MAT_DRAWER_DEFAULT_AUTOSIZE =
+ new InjectionToken('MAT_DRAWER_DEFAULT_AUTOSIZE');
@Component({
moduleId: module.id,
@@ -74,7 +79,7 @@ export class MatDrawerContent implements AfterContentInit {
* drawer is open. We use margin rather than transform even for push mode because transform breaks
* fixed position elements inside of the transformed element.
*/
- _margins: {left: number, right: number} = {left: 0, right: 0};
+ _margins: {left: number|null, right: number|null} = {left: null, right: null};
constructor(
private _changeDetectorRef: ChangeDetectorRef,
@@ -403,7 +408,6 @@ export class MatDrawer implements AfterContentInit, OnDestroy {
})
export class MatDrawerContainer implements AfterContentInit, OnDestroy {
@ContentChildren(MatDrawer) _drawers: QueryList;
-
@ContentChild(MatDrawerContent) _content: MatDrawerContent;
/** The drawer child with the `start` position. */
@@ -412,6 +416,19 @@ export class MatDrawerContainer implements AfterContentInit, OnDestroy {
/** The drawer child with the `end` position. */
get end(): MatDrawer | null { return this._end; }
+ /**
+ * Whether to automatically resize the container whenever
+ * the size of any of its drawers changes.
+ *
+ * **Use at your own risk!** Enabling this option can cause layout thrashing by measuring
+ * the drawers on every change detection cycle. Can be configured globally via the
+ * `MAT_DRAWER_DEFAULT_AUTOSIZE` token.
+ */
+ @Input()
+ get autosize(): boolean { return this._autosize; }
+ set autosize(value: boolean) { this._autosize = coerceBooleanProperty(value); }
+ private _autosize: boolean;
+
/** Event emitted when the drawer backdrop is clicked. */
@Output() backdropClick = new EventEmitter();
@@ -431,15 +448,23 @@ export class MatDrawerContainer implements AfterContentInit, OnDestroy {
/** Emits when the component is destroyed. */
private _destroyed = new Subject();
- _contentMargins = new Subject<{left: number, right: number}>();
+ /** Emits on every ngDoCheck. Used for debouncing reflows. */
+ private _doCheckSubject = new Subject();
+
+ _contentMargins = new Subject<{left: number|null, right: number|null}>();
- constructor(@Optional() private _dir: Directionality, private _element: ElementRef,
- private _ngZone: NgZone, private _changeDetectorRef: ChangeDetectorRef) {
+ constructor(@Optional() private _dir: Directionality,
+ private _element: ElementRef,
+ private _ngZone: NgZone,
+ private _changeDetectorRef: ChangeDetectorRef,
+ @Inject(MAT_DRAWER_DEFAULT_AUTOSIZE) defaultAutosize = false) {
// If a `Dir` directive exists up the tree, listen direction changes and update the left/right
// properties to point to the proper start/end.
if (_dir != null) {
_dir.change.pipe(takeUntil(this._destroyed)).subscribe(() => this._validateDrawers());
}
+
+ this._autosize = defaultAutosize;
}
ngAfterContentInit() {
@@ -460,9 +485,15 @@ export class MatDrawerContainer implements AfterContentInit, OnDestroy {
this._changeDetectorRef.markForCheck();
});
+
+ this._doCheckSubject.pipe(
+ debounceTime(10), // Arbitrary debounce time, less than a frame at 60fps
+ takeUntil(this._destroyed)
+ ).subscribe(() => this._updateContentMargins());
}
ngOnDestroy() {
+ this._doCheckSubject.complete();
this._destroyed.next();
this._destroyed.complete();
}
@@ -477,6 +508,14 @@ export class MatDrawerContainer implements AfterContentInit, OnDestroy {
this._drawers.forEach(drawer => drawer.close());
}
+ ngDoCheck() {
+ // If users opted into autosizing, do a check every change detection cycle.
+ if (this._autosize && this._isPushed()) {
+ // Run outside the NgZone, otherwise the debouncer will throw us into an infinite loop.
+ this._ngZone.runOutsideAngular(() => this._doCheckSubject.next());
+ }
+ }
+
/**
* Subscribes to drawer events in order to set a class on the main container element when the
* drawer is open and the backdrop is visible. This ensures any overflow on the container element
@@ -572,6 +611,12 @@ export class MatDrawerContainer implements AfterContentInit, OnDestroy {
}
}
+ /** Whether the container is being pushed to the side by one of the drawers. */
+ private _isPushed() {
+ return (this._isDrawerOpen(this._start) && this._start!.mode != 'over') ||
+ (this._isDrawerOpen(this._end) && this._end!.mode != 'over');
+ }
+
_onBackdropClicked() {
this.backdropClick.emit();
this._closeModalDrawer();
@@ -628,6 +673,7 @@ export class MatDrawerContainer implements AfterContentInit, OnDestroy {
}
}
- this._contentMargins.next({left, right});
+ // Pull back into the NgZone since in some cases we could be outside.
+ this._ngZone.run(() => this._contentMargins.next({left, right}));
}
}
diff --git a/src/lib/sidenav/sidenav-module.ts b/src/lib/sidenav/sidenav-module.ts
index 49e81a3db878..a77029c3a47d 100644
--- a/src/lib/sidenav/sidenav-module.ts
+++ b/src/lib/sidenav/sidenav-module.ts
@@ -12,8 +12,13 @@ import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
import {MatCommonModule} from '@angular/material/core';
import {ScrollDispatchModule} from '@angular/cdk/scrolling';
-import {MatDrawer, MatDrawerContainer, MatDrawerContent} from './drawer';
import {MatSidenav, MatSidenavContainer, MatSidenavContent} from './sidenav';
+import {
+ MatDrawer,
+ MatDrawerContainer,
+ MatDrawerContent,
+ MAT_DRAWER_DEFAULT_AUTOSIZE,
+} from './drawer';
@NgModule({
@@ -41,5 +46,8 @@ import {MatSidenav, MatSidenavContainer, MatSidenavContent} from './sidenav';
MatSidenavContainer,
MatSidenavContent,
],
+ providers: [
+ {provide: MAT_DRAWER_DEFAULT_AUTOSIZE, useValue: false}
+ ]
})
export class MatSidenavModule {}
diff --git a/src/lib/sidenav/sidenav.md b/src/lib/sidenav/sidenav.md
index 139c40616ece..5c736ea018f9 100644
--- a/src/lib/sidenav/sidenav.md
+++ b/src/lib/sidenav/sidenav.md
@@ -131,6 +131,16 @@ Custom handling for backdrop clicks can be done via the `(backdropClick)` output
+### Resizing an open sidenav
+By default, Material will only measure and resize the drawer container in a few key moments
+(on open, on window resize, on mode change) in order to avoid layout thrashing, however there
+are cases where this can be problematic. If your app requires for a drawer to change its width
+while it is open, you can use the `autosize` option to tell Material to continue measuring it.
+Note that you should use this option **at your own risk**, because it could cause performance
+issues.
+
+
+
### Setting the sidenav's size
The `` and `` will, by default, fit the size of its content. The width can
@@ -158,7 +168,7 @@ the top or bottom.
A sidenav often needs to behave differently on a mobile vs a desktop display. On a desktop, it may
make sense to have just the content section scroll. However, on mobile you often want the body to be
the element that scrolls; this allows the address bar to auto-hide. The sidenav can be styled with
-CSS to adjust to either type of device.
+CSS to adjust to either type of device.
@@ -174,7 +184,7 @@ describes your sidenav, `role="region"` is recommended.
Similarly, the `` should be given a role based on what it contains. If it
represents the primary content of the page, it may make sense to mark it `role="main"`. If no more
-specific role makes sense, `role="region"` is again a good fallback.
+specific role makes sense, `role="region"` is again a good fallback.
### Troubleshooting
diff --git a/src/material-examples/example-module.ts b/src/material-examples/example-module.ts
index 1bb2111e59e8..56b38b7eae3d 100644
--- a/src/material-examples/example-module.ts
+++ b/src/material-examples/example-module.ts
@@ -104,6 +104,7 @@ import {SidenavOpenCloseExample} from './sidenav-open-close/sidenav-open-close-e
import {SidenavOverviewExample} from './sidenav-overview/sidenav-overview-example';
import {SidenavPositionExample} from './sidenav-position/sidenav-position-example';
import {SidenavResponsiveExample} from './sidenav-responsive/sidenav-responsive-example';
+import {SidenavAutosizeExample} from './sidenav-autosize/sidenav-autosize-example';
import {SlideToggleConfigurableExample} from './slide-toggle-configurable/slide-toggle-configurable-example';
import {SlideToggleFormsExample} from './slide-toggle-forms/slide-toggle-forms-example';
import {SlideToggleOverviewExample} from './slide-toggle-overview/slide-toggle-overview-example';
@@ -674,6 +675,12 @@ export const EXAMPLE_COMPONENTS = {
additionalFiles: null,
selectorName: null
},
+ 'sidenav-autosize': {
+ title: 'Autosize sidenav',
+ component: SidenavAutosizeExample,
+ additionalFiles: null,
+ selectorName: null
+ },
'slide-toggle-configurable': {
title: 'Configurable slide-toggle',
component: SlideToggleConfigurableExample,
@@ -900,6 +907,7 @@ export const EXAMPLE_LIST = [
SidenavOverviewExample,
SidenavPositionExample,
SidenavResponsiveExample,
+ SidenavAutosizeExample,
SlideToggleConfigurableExample,
SlideToggleFormsExample,
SlideToggleOverviewExample,
diff --git a/src/material-examples/sidenav-autosize/sidenav-autosize-example.css b/src/material-examples/sidenav-autosize/sidenav-autosize-example.css
new file mode 100644
index 000000000000..8ab00588bb34
--- /dev/null
+++ b/src/material-examples/sidenav-autosize/sidenav-autosize-example.css
@@ -0,0 +1,16 @@
+.example-container {
+ width: 500px;
+ height: 300px;
+ border: 1px solid rgba(0, 0, 0, 0.5);
+}
+
+.example-sidenav-content {
+ display: flex;
+ height: 100%;
+ align-items: center;
+ justify-content: center;
+}
+
+.example-sidenav {
+ padding: 20px;
+}
diff --git a/src/material-examples/sidenav-autosize/sidenav-autosize-example.html b/src/material-examples/sidenav-autosize/sidenav-autosize-example.html
new file mode 100644
index 000000000000..f9326a3846f3
--- /dev/null
+++ b/src/material-examples/sidenav-autosize/sidenav-autosize-example.html
@@ -0,0 +1,16 @@
+
+
+