Add the "Mini variant" behavior of the Navigation drawer.
As defined in the Google Material guidelines:
https://material.google.com/patterns/navigation-drawer.html#navigation-drawer-behavior
When toggled, the navigation drawer change its width.
It's not fully hidden.
Only two states are available: displayed, hidden
Create a sidenav and toggle it on and off.
The sidenav can only be opened or closed.
No intermediate state.
Desktop users need to quickly change the current view.
The sliding effect of the sidenav could be annoying when used a lot.
It's common for desktop apps to keep the navigation opened by default.
But in some cases the icons are self explanatory so no need to display the associated labels.
Angular2
A similar feature request was submitted for Angular 1:
https://github.com/angular/material/issues/3158
Is there any reason you couldn't control the width of the sidenav with css and toggle classes based on which width you want to display?
Hello @jelbourn
Thanks for your answer.
Yes I tried to do it manually.
I can show/hide the texts with a CSS class or use a ngIf.
It works but then I have a problem of alignment for my icons.
They are not correctly centered.
So yes we can do it ourselves but I think it would be really useful to have it natively.
And then just use an attribute like textVisible or iconOnly for instance ...
Regards
Is that feature has being planned yet? Is there any deadlines on that?
@daugoh have you come up with any proper soulution for this one?
Here is https://github.com/angular/material2/issues/2021 one of the ways http://plnkr.co/edit/LcRDIHNFjHKLFTaMdNEM?p=preview of doing that
Probably, You can manage it buy css for intermediate state as below. You might need some css overrides as per need.
<md-sidenav-layout fullscreen>
<md-sidenav #sidenav>
<md-nav-list>
<a md-list-item (click)="sidenav.toggle()">
<md-icon md-list-icon>menu</md-icon>
</a>
<a md-list-item *ngFor="let view of views">
<md-icon md-list-icon>{{view.icon}}</md-icon>
<span md-line>{{view.name}}</span>
</a>
</md-nav-list>
</md-sidenav>
<app-contacts></app-contacts>
</md-sidenav-layout>
/*left sidenav style override*/
md-sidenav {
background-color: #f8921e;
overflow: hidden;
}
md-nav-list a[md-list-item] {
color: #fff;
}
md-nav-list a[md-list-item] .md-list-item{
padding: 0 10px !important;
}
.md-sidenav-over.md-sidenav-closed md-nav-list a[md-list-item] .md-list-text {
display: none !important;
}
md-sidenav.md-sidenav-closed {
transform: translate3d(0, 0, 0) !important;
visibility: visible !important;
min-width: 40px !important;
}
@kuncevic
So far I'm just using a quick workaround with a ngIf to show/hide the text
Any news?
@rajamarketing can you help me and provide css with @media query so i don't need to watch in angular componnet for window resize events and css applied automatic?
Hi people, news?
from @rajamarketing comment here is what I've come up with, be it sass complains but it compiles...
md-sidenav {
width: 200px;
md-icon {
padding-right: 20px !important;
}
&.mat-sidenav-closed {
transform: translate3d(0, 0, 0) !important;
visibility: visible !important;
min-width: 64px !important;
width: 64px !important;
&~ /deep/.mat-sidenav-content {
margin-left: 64px !important;
}
}
}
}
Any updates?
Seems like @daxsom has a solution.
Any chance to get this with the next stable release?
firebase and google analytics has this functionality.
It would be awesome to have this build in and working by default
yes pls!
this may help ..
https://github.com/Ravi-Rajpurohit/mini-md-sidenav
Would be extremely helpful to have this as a native feature. As for now it requires some hacky css to make it work both for desktop (mini-sidenav) and mobile (classic one), which are breaking with almost every release :)
Any news?
Yes, any news, please?
could be on the steps. I mentioned it to Thomas Burleson from the Angular Material team at Angular Connect. He said it shouldn't take long to make. Meanwhile, we could use the CSS hack method.
I'll just follow up on it.
I hope and expect...
any stable release with these changes?
Thanks to https://github.com/angular/material2/pull/8488 you can now use autosize
in mat-sidenav-container
, so you can resize the sidenav without css hacks.
Here's an example adapted from the "autosize sidenav" example:
https://stackblitz.com/edit/angular-b4gmby
@Melanie-M so just a question... If a remove "autosize"... I still see the same result?
@mackelito I think without autosize
the main content doesn't resize properly, the sidenav overlaps it instead. But so depending on what behavior you want, I guess it can work without autosize
Answering @jelbourn's comment from 2 years ago. It cannot be achieved easily with css: mat-sidenav-content
gets it's margin-left
from mat-sidenav
and you have to add the autosize
to the mat-sidenav-container
(wich adds perf issues) and even if you hide manually the mat-list-item
's labels, there are no fancy animations on the icons/labels out-of-the-box. I think this should be the library's responsibility, not user's (because you have to override a lot of css if you want to avoid the autosize
attr, enable the animations, etc. on your own, being this decisions not future-proof)
must have feat
If autosize
causes performance problems -- which isn't surprising if it makes the size get rechecked on every change cycle -- can we just get a method that will force resize manually?
The code is already in the component to do all the work, and we (usually) know when we want to make it resize. Seems fair to just expose a way to force it to happen when needed, and not bother with having it check all the time.
FWIW, we're getting around this by (at time to resize) turning on autosize via a ViewChild reference, then turning it off again a second later via setTimeout. It's hacky though.
must have :)
I am just going to leave this here in the hope that similar features are introduced in Angular.
https://vuetifyjs.com/en/components/navigation-drawers#example-mini
You can also check this link: https://medium.com/@harkiratsingh.026/angular-6-mini-variant-drawer-d5326be55dd1
@jelbourn are you guys open for a PR about this?
Not easy to pinpoint the specs because in current Material Design guidelines the "mini" variant is nowhere to be found, but if you tell me old specs are ok, I can try a PR which just defines a 'mini' status with user-defined width value (with sensible default values).
This issue actually proposed some kind of API for it already #9640
At this point we would probably hold off on the mini variant to check with the Material Design team to see if they plan for this to still be part of the canon.
@jelbourn any news from Material Design Team?
in css:
/deep/.mat-drawer.mat-drawer-end[ng-reflect-opened=false]{
left: 35px !important;
visibility: visible !important;
}
it working!!!
but in prod - no....
try this instead
::ng-deep .mat-drawer.mat-drawer-end[ng-reflect-opened=false]{
ng-reflect-opened property not appearing in prod - after build
(im wrote the last answer)
@zipporaSay I would suggest you to have your own selector. When you close it add class "closed" an use that selector instead
/deep/
and ::ng-deep
are deprecated because they break incapsulation, not really a good solution IMHO...
Also, just setting the sidenav width should suffice to get what you're trying to do with that CSS
Are there other ways to force styles to components outside the material components?
Not from a component to another AFAIK
You should use global CSS rules (aka non-scoped CSS) for that, even if it's a lot less maintenable because it moves some rules relative to a component outside the component folder/files
Okay, so it's been a little over 2 years. Any core team member who would like to comment on this?
...this would be nice!
+1 Must have here. Thanks
Same here, we need this.
This is a good request, Any updates?
edit: Ravi-Rajpurohit's & Harkiratsingh026's links work great
Any updates? Need this
the angular materials team is working together with the angular core team to launch the ivy compiler, do not expect any new feature until the ivy be released
(I'm going through the highest voted issues today an commenting on their status)
We still consider this something that we want to add, but it's still one of the lower-priority features in the backlog (compared to things like virtual scrolling for table, density theming, improving CDK documentation, etc.). As I mentioned in my recent ng-conf talk, we're doing some work now to integrate MDC Web into our components, so we would likely hold off on working on this until we have a better idea of what the MDC-based sidenav would look like.
@jelbourn just so that we can get a grasp of what is to come and what is priority..
Is https://github.com/angular/components/projects/25 "up to date"?
Also how many devs are currently focusing on angular components?
It's up-to-date. It's hard to say an exact number since people tend to work on a number of different things around Angular.
Sweet!.. all the "Build MDC-based.." are those rewrites of existing components?
(sorry for hogging the issue like this but there is no clear way where to ask these questions.. and the README is on a really high level (Jan-Jun) so I kind of expect that there we not be any bigger changes or news in the near future (judging from the state in project)
Is that a fare assumption?
There should be more to see by the end of the year
I am really looking forward to this feature.
It's tin 馃槨馃が
You will still wait 鈽狅笍100 years, it is better to change the UI
Alternatively take a look at ng-zorro
+1
The example cited above actually works, the only problem is that, if you try using animations and autosize, it screws up the layout (you must click anywhere in the interface to "fix" it, or wait a few seconds till the digest cycle).
Will we see this feature only in 2020? 2021...?
;)
There are so many different implementations outside... May it become necessary there is a standard somewhere. And why not inside of this framework itself. Would be a nice xmas present ;-)
But seriously if so many people already here and some repos' outside for feature that all expect its all-in material-angular... may it needs to become true...
Would be great to see!
Do I take it the issue here is that there no point in developing new features when you working towards MDC version?
If anyone struggling to get this open without wanting to create a component. You should use a directive as its the most likely what angular team may use.
import { Directive, ElementRef, OnInit, OnDestroy, Host, Self, Optional, Renderer2, Input } from '@angular/core';
import { group, query, animateChild, animate, style, AnimationBuilder } from '@angular/animations'
import type { AnimationMetadata, AnimationStyleMetadata, AnimationGroupMetadata } from '@angular/animations';
import { MatSidenav, MatDrawer } from '@angular/material/sidenav';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { sidebarAnimationCloseGroup } from '../animations/animations';
const defaultDuration = '100ms';
const defaultMinWidth = '100px';
const defaultMaxWidth = '200px';
export function sidebarClose(minWidth: string = defaultMinWidth): AnimationStyleMetadata {
return style({
width: minWidth,
visibility: 'visible',
})
}
export function sidebarOpen(maxWidth: string = defaultMaxWidth): AnimationStyleMetadata {
return style({
width: maxWidth,
visibility: 'visible',
})
}
export function sidebarAnimationOpenGroup(
animationDuration: string = defaultDuration,
maxWidth: string = defaultMaxWidth
): AnimationGroupMetadata {
return group([
query('@iconAnimation', animateChild(), { optional: true }),
query('@labelAnimation', animateChild(), { optional: true }),
animate(`${animationDuration} ease-in-out`, sidebarOpen(maxWidth))
])
}
@Directive({ selector: '[mat-nav-mini]' })
export class MatNavMiniDirective implements OnInit, OnDestroy {
public onDestory: Subject<void> = new Subject();
private drawer: MatSidenav | MatDrawer;
@Input()
public openAnimation: AnimationMetadata | AnimationMetadata[];
@Input()
public closeAnimation: AnimationMetadata | AnimationMetadata[];
@Input()
public closeWidth: string = defaultMinWidth;
@Input()
public expandedWidth: string = defaultMaxWidth;
constructor(
private builder: AnimationBuilder,
private el: ElementRef<HTMLElement>,
private renderer2: Renderer2,
@Host() @Self() @Optional() sidenav: MatSidenav,
@Host() @Self() @Optional() drawer: MatDrawer,
) {
this.drawer = sidenav || drawer;
}
public ngOnInit(): void {
this.closeAnimation = this.closeAnimation || sidebarAnimationCloseGroup(defaultDuration, this.closeWidth);
this.openAnimation = this.openAnimation || sidebarAnimationOpenGroup(defaultDuration, this.expandedWidth);
this.renderer2.setStyle(this.el.nativeElement, 'overflow', 'hidden');
this.drawer.closedStart.pipe(takeUntil(this.onDestory)).subscribe(() => {
const factory = this.builder.build(this.closeAnimation);
const player = factory.create(this.el.nativeElement);
player.play();
});
this.drawer.openedStart.pipe(takeUntil(this.onDestory)).subscribe(() => {
const factory = this.builder.build(this.openAnimation);
const player = factory.create(this.el.nativeElement);
player.play();
});
}
public ngOnDestroy(): void {
this.onDestory.next();
this.onDestory.complete();
}
}
@Jordan-Hall when importing there's errors for the import type
statement at the top and the ../animations/animations
import.
@Helveg change import type to import. I'm running on bleeding edge typescript and Angular. It will still work on older version of angular and typescript. Just cant use new typescript syntax :)
@Jordan-Hall just out of curiosity. if i got your snippet right, the width property will be animated, right?. I'm not exactly sure how the web animation api handles this - but animating width in CSS is a pain point as the browser has to recalculate the whole document(-part) in every frame to relayout sibling elements. (could lead to jank on slower devices like older phones). Any chance this is possible with the efficient 4? (translate, transform, scale, opacity).
@nlappe You are correct in the sense it might not be efficient. I only wrote it in just under 2 hours after studying the component internals. I tried to override or "prevent" closing animation by angular but couldn't work it out.
I'll take a look this weekend with the efficient 4 and create a test project for it.
Thanks to #8488 you can now use
autosize
inmat-sidenav-container
, so you can resize the sidenav without css hacks.Here's an example adapted from the "autosize sidenav" example:
https://stackblitz.com/edit/angular-b4gmby
Just what I was looking for, thanks for sharing -__-
I am wondering about how old is this issue.
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.
Since autosize
flag description doesn't look very friendly, I am trying it via css.
https://stackblitz.com/edit/angular-mini-sidenav-by-css
It is not perfect but it fits my needs, I hope it helps anybody. 馃槂
I am wondering about how old is this issue.
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.
Since
autosize
flag description doesn't look very friendly, I am trying it via css.https://stackblitz.com/edit/angular-mini-sidenav-by-css
It is not perfect but it fits my needs, I hope it helps anybody. 馃槂
Thanks for your contribution. I'm sure there is a communication problem between the development team.
@lramondev It's not a lack of communication. Material design team haven't come up with a standard for the Mini variant.
Exact. I consider the lack of communication between the team to be a very requested resource
Guys just do it with some css
.mat-drawer-side {
border-right: none !important;
}
.mat-drawer-content {
background-color: #fafafa;
margin-left: 64px !important;
border-left: solid 1px rgba(0, 0, 0, 0.12);
z-index: 10 !important;
will-change: margin-left;
transition: margin-left 0.3s ease-in-out;
}
.is-expanded {
margin-left: 230px !important;
}
then toggle is-expanded class with ngClass
in ts file
toggleMenu() {
this.isExpanded = !this.isExpanded;
}
in html
<mat-sidenav-content [ngClass]="{ 'is-expanded': isExpanded }">
if you want hover support too
`
[mode]="mobileQuery.matches ? 'over' : 'side'"
[opened]="true"
(mouseenter)="toggleMenu()"
(mouseleave)="toggleMenu()"
`
@aram-m the issue with animation apparently. However this is basically how i manged it with a directive https://github.com/angular/components/issues/1728#issuecomment-576750289
@Jordan-Hall yeap but this is much simpler solution with few lines of css, you're solution es very good too :)
@aram-m the issue with animation apparently. However this is basically how i manged it with a directive #1728 (comment)
I 've noticed there is a reference to import { sidebarAnimationCloseGroup } from '../animations/animations';
,
@Jordan-Hall could you add a working example in Stackblitz or similar? 馃殌
@angelfraga I've improved the code 馃槉 so it now works on mode="mini" and thats all you need to do.
Blog post: https://medium.com/@LostDeveloper/angular-material-navigation-drawer-adding-support-mode-rail-mini-variant-behaviour-8f7b107700b3
Demo: https://angular-material-mini-variant.stackblitz.io/
Code:
default.config.ts:
export const miniConfig = {
defaultDuration: '100ms',
defaultMinWidth: '60px',
defaultMaxWidth: '300px',
}
animations.settings.ts:
import { group, query, animateChild, animate, style, AnimationBuilder } from '@angular/animations'
import { AnimationStyleMetadata, AnimationGroupMetadata } from '@angular/animations';
import { miniConfig } from './default.config';
export function sidebarClose(minWidth: string = miniConfig.defaultMinWidth): AnimationStyleMetadata {
return style({
width: minWidth,
visibility: 'visible',
transform: 'none',
overflow: 'hidden'
})
}
export function sidebarOpen(maxWidth: string = miniConfig.defaultMaxWidth): AnimationStyleMetadata {
return style({
width: maxWidth,
visibility: 'visible',
})
}
export function sidebarAnimationOpenGroup(
animationDuration: string = miniConfig.defaultDuration,
maxWidth: string = miniConfig.defaultMaxWidth
): AnimationGroupMetadata {
return group([
query('@iconAnimation', animateChild(), { optional: true }),
query('@labelAnimation', animateChild(), { optional: true }),
animate(`${animationDuration} ease-in-out`, sidebarOpen(maxWidth))
])
}
export function sidebarAnimationCloseGroup(
animationDuration: string = miniConfig.defaultDuration,
minWidth: string = miniConfig.defaultMinWidth
): AnimationGroupMetadata {
return group([
query('@iconAnimation', animateChild(), { optional: true }),
query('@labelAnimation', animateChild(), { optional: true }),
animate(`${animationDuration} ease-in-out`, sidebarClose(minWidth))
])
}
mini.directive.ts:
import { forwardRef, Inject, Directive, ElementRef, OnInit, OnDestroy, Host, Self, Optional, Renderer2, Input, AfterContentInit } from '@angular/core';
import { AnimationMetadata, AnimationStyleMetadata, AnimationGroupMetadata } from '@angular/animations';
import { AnimationBuilder } from '@angular/animations'
import { MatSidenav, MatDrawer, MatSidenavContainer, MatDrawerContainer } from '@angular/material';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { miniConfig } from './default.config';
import { sidebarAnimationCloseGroup, sidebarAnimationOpenGroup } from './animations.settings';
import {Directionality} from '@angular/cdk/bidi';
@Directive({
selector: 'mat-sidenav[mode="mini"], mat-drawer[mode="mini"]',
host: {
'[class.mat-drawer-side]': 'true',
}
})
export class MatNavMiniDirective implements OnInit, OnDestroy, AfterContentInit {
public onDestory: Subject<void> = new Subject();
private drawer: MatSidenav | MatDrawer;
private container: MatSidenavContainer | MatDrawerContainer;
@Input()
public openAnimation: AnimationMetadata | AnimationMetadata[];
@Input()
public closeAnimation: AnimationMetadata | AnimationMetadata[];
@Input()
public closeWidth: string = miniConfig.defaultMinWidth;
@Input()
public expandedWidth: string = miniConfig.defaultMaxWidth;
constructor(
private builder: AnimationBuilder,
private el: ElementRef<HTMLElement>,
private renderer2: Renderer2,
@Host() @Self() @Optional() sidenav: MatSidenav,
@Host() @Self() @Optional() drawer: MatDrawer,
@Inject(forwardRef(() => MatSidenavContainer)) @Optional() matSideNavContainer: MatSidenavContainer,
@Inject(forwardRef(() => MatDrawerContainer)) @Optional() matDrawerContainer: MatDrawerContainer,
@Optional() private _dir: Directionality,
) {
this.container = matSideNavContainer || matDrawerContainer;
this.drawer = sidenav || drawer;
this.container.hasBackdrop = false;
this.container.autosize = true;
}
public ngOnInit(): void {
this.closeAnimation = this.closeAnimation || sidebarAnimationCloseGroup(miniConfig.defaultDuration, this.closeWidth);
this.openAnimation = this.openAnimation || sidebarAnimationOpenGroup(miniConfig.defaultDuration, this.expandedWidth);
this.renderer2.setStyle(this.el.nativeElement.querySelector('.mat-drawer-inner-container'), 'overflow', 'hidden');
this.drawer.closedStart.pipe(takeUntil(this.onDestory)).subscribe(() => {
const containerContent = this.el.nativeElement.parentElement.querySelector('.mat-drawer-content');
if (this.drawer.position != 'end' || this._dir && this._dir.value != 'rtl') {
this.renderer2.setStyle(containerContent, 'marginLeft', this.closeWidth);
} else {
this.renderer2.setStyle(containerContent, 'marginRight', this.closeWidth);
}
const factory = this.builder.build(this.closeAnimation);
const player = factory.create(this.el.nativeElement);
player.play();
});
this.drawer.openedStart.pipe(takeUntil(this.onDestory)).subscribe(() => {
const containerContent = this.el.nativeElement.parentElement.querySelector('.mat-drawer-content');
if (this.drawer.position != 'end' || this._dir && this._dir.value != 'rtl') {
this.renderer2.setStyle(containerContent, 'marginLeft', this.expandedWidth);
} else {
this.renderer2.setStyle(containerContent, 'marginRight', this.expandedWidth);
}
const factory = this.builder.build(this.openAnimation);
const player = factory.create(this.el.nativeElement);
player.play();
});
}
ngAfterContentInit() {
const containerContent = this.el.nativeElement.parentElement.querySelector('.mat-drawer-content');
if (this.drawer.position != 'end' || this._dir && this._dir.value != 'rtl') {
this.renderer2.setStyle(containerContent, 'marginLeft', this.drawer._width + 'px');
} else {
this.renderer2.setStyle(containerContent, 'marginRight', this.drawer._width + 'px');
}
}
public ngOnDestroy(): void {
this.onDestory.next();
this.onDestory.complete();
}
}
_Disclaimer: This code isn't supported. You may not get response to any issues relating to this code. This code has also not been tested in production system. It's been developed adhoc and is a snippet of the code that's currently in development_
Looks like specs have finally been given by the material team https://material.io/components/navigation-rail#behavior
In it you can merge with the navigation drawer
@Jordan-Hall Thank you for your solution. I was trying to find a solution yesterday as well. Do you want to create a package for this?
Btw, I think we can improve the performance by setting
this.container.autosize = true;
after openedStart/ closedStart
and set it as false like
player.onDone(() => {
this.container.autosize = false;
});
Sure, I'll create a package shortly. I'm also going to try and create the Rail component and maybe try and make a PR for this.
@maxisam created the NPM package https://www.npmjs.com/package/angular-material-rail-drawer
Repo here https://github.com/Jordan-Hall/angular-material-rail-drawer-plugin
Any issues please let me know. I've just picked up next blog I need to write and a small freelance role then I'll hopefully have a PR ready for here
Most helpful comment
Seems like @daxsom has a solution.
Any chance to get this with the next stable release?