Tutorial: Angular Material Sidenav
REFERENCES |
---|
[1]: https://material.angular.io/components/snack-bar/overview "Snackbar" |
Refactoring with Angular Material Sidenav
Before we begin the next part of the Angular Tour of Heroes tutorial, we will need to have some navigation around our application. In most simple cases, you could use the mat-navbar
component to have a list of navigation links. However, to learn more about the basics of Angular Material, we will begin adding a sidenav with our application. In most web and mobile applications, the sidenav can be opened with the hamburger menu item we defined earlier in our mat-navbar
.
Introducing the Angular Material Sidenav
Angular Material provides two sets of components designed to add collapsible side content (often navigation, though it can be any content) alongside some primary content. These are the sidenav and drawer components.
The sidenav components are designed to add side content to a fullscreen app. To set up a sidenav we use three components: <mat-sidenav-container>
which acts as a structural container for our content and sidenav, <mat-sidenav-content>
which represents the main content, and <mat-sidenav>
which represents the added side content.
The drawer component is designed to add side content to a small section of your app. This is accomplished using the <mat-drawer-container>
, <mat-drawer-content>
, and <mat-drawer>
components, which are analogous to their sidenav equivalents. Rather than adding side content to the app as a whole, these are designed to add side content to a small section of your app. They support almost all of the same features, but do not support fixed positioning.
Specifying the main and side content
Both the main and side content should be placed inside of the <mat-sidenav-container>
, content that you don't want to be affected by the sidenav, such as a header or footer, can be placed outside of the container.
The side content should be wrapped in a <mat-sidenav>
element. The position
property can be used to specify which end of the main content to place the side content on. position
can be either start
or end
which places the side content on the left or right respectively in left-to-right languages. If the position
is not set, the default value of start
will be assumed. A <mat-sidenav-container>
can have up to two <mat-sidenav>
elements total, but only one for any given side.
The main content should be wrapped in a <mat-sidenav-content>
. If no <mat-sidenav-content>
is specified for a <mat-sidenav-container>
, one will be created implicitly and all of the content inside the <mat-sidenav-container>
other than the <mat-sidenav>
elements will be placed inside of it.
The following are examples of valid sidenav layouts:
<!-- Creates a layout with a left-positioned sidenav and explicit content. -->
<mat-sidenav-container>
<mat-sidenav>Start</mat-sidenav>
<mat-sidenav-content>Main</mat-sidenav-content>
</mat-sidenav-container>
<!-- Creates a layout with a left and right sidenav and implicit content. -->
<mat-sidenav-container>
<mat-sidenav>Start</mat-sidenav>
<mat-sidenav position="end">End</mat-sidenav>
<section>Main</section>
</mat-sidenav-container>
<!-- Creates an empty sidenav container with no sidenavs and implicit empty content. -->
<mat-sidenav-container></mat-sidenav-container>
And these are examples of invalid sidenav layouts:
<!-- Invalid because there are two `start` position sidenavs. -->
<mat-sidenav-container>
<mat-sidenav>Start</mat-sidenav>
<mat-sidenav position="start">Start 2</mat-sidenav>
</mat-sidenav-container>
<!-- Invalid because there are multiple `<mat-sidenav-content>` elements. -->
<mat-sidenav-container>
<mat-sidenav-content>Main</mat-sidenav-content>
<mat-sidenav-content>Main 2</mat-sidenav-content>
</mat-sidenav-container>
<!-- Invalid because the `<mat-sidenav>` is outside of the `<mat-sidenav-container>`. -->
<mat-sidenav-container></mat-sidenav-container>
<mat-sidenav></mat-sidenav>
These same rules all apply to the drawer components as well.
Opening and closing a sidenav
A <mat-sidenav>
can be opened or closed using the open()
, close()
and toggle()
methods. Each of these methods returns a Promise<boolean>
that will be resolved with true
when the sidenav finishes opening or false
when it finishes closing.
The opened state can also be set via a property binding in the template using the opened
property. The property supports 2-way binding.
<mat-sidenav>
also supports output properties for just open and just close events, The (opened)
and (closed)
properties respectively.
All of these properties and methods work on <mat-drawer>
as well.
Changing the sidenav's behavior
The <mat-sidenav>
can render in one of three different ways based on the mode
property.
Mode | Description |
---|---|
over |
Sidenav floats over the primary content, which is covered by a backdrop |
push |
Sidenav pushes the primary content out of its way, also covering it with a backdrop |
side |
Sidenav appears side-by-side with the main content, shrinking the main content's width to make space for the sidenav. |
If no mode
is specified, over
is used by default.
Disabling automatic close
Clicking on the backdrop or pressing the Esc key will normally close an open sidenav. However, this automatic closing behavior can be disabled by setting the disableClose
property on the <mat-sidenav>
or <mat-drawer>
that you want to disable the behavior for.
Custom handling for Esc can be done by adding a keydown listener to the <mat-sidenav>
. Custom handling for backdrop clicks can be done via the (backdropClick)
output property on <mat-sidenav-container>
.
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 <mat-sidenav>
and <mat-drawer>
will, by default, fit the size of its content. The width can be explicitly set via CSS:
mat-sidenav {
width: 200px;
}
Try to avoid percent based width as resize
events are not (yet) supported.
Fixed position sidenavs
For <mat-sidenav>
only (not <mat-drawer>
) fixed positioning is supported. It can be enabled by setting the fixedInViewport
property. Additionally, top and bottom space can be set via the fixedTopGap
and fixedBottomGap
. These properties accept a pixel value amount of space to add at the top or bottom.
Creating a responsive layout for mobile & desktop
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.
Create the sidenav inside the root component
To begin, we must follow the fixed position sidenav example and create a fixed position mat-sidenav-container
. Change the following code files to match:
src/index.html
<!doctype html>
<html lang="en">
<head>
...
</head>
<body>
<!--ROOT COMPONENT-->
<div class="container-fluid">
<app-root></app-root>
</div>
</body>
</html>
src/styles.css
@import './assets/css/bootstrap-reboot.min.css';
@import './app-theme.css';
@import './material-colors.min.css';
@import './assets/css/bootstrap-grid.min.css';
@import './assets/css/bootstrap-helpers.min.css';
/* Application-wide Styles */
.container-fluid {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: 0;
padding: 0;
}
a {
color: transparent;
text-underline: transparent;
text-decoration: transparent;
}
a:hover, a:visited, a:link, a:active {
color: inherit;
text-decoration: none;
}
src/app/app.component.html
<app-mat-navbar></app-mat-navbar>
<mat-sidenav-container class="sidenav-container">
<mat-sidenav #sidenav class="sidenav red lighten-1 white-text pt-4"
mode="over" [opened]="false" [fixedInViewport]="fixed" [fixedTopGap]="0" [fixedBottomGap]="0">
Sidenav content
</mat-sidenav>
<mat-sidenav-content>
<div class="col align-middle align-content-center red py-4">
<h1 class="mat-display-1 text-center white-text mb-0">{{ title }}</h1>
</div>
<div class="col-sm-12">
<app-heroes></app-heroes>
</div>
</mat-sidenav-content>
</mat-sidenav-container>
src/app/app.component.css
.sidenav-container {
position: absolute;
top: 60px;
bottom: 0;
left: 0;
right: 0;
overflow: hidden;
}
.sidenav-content {
overflow: hidden;
}
.sidenav {
display: flex;
align-items: start;
justify-content: center;
width: 250px;
z-index: 11;
}
@Output
a method to be used in the <app-mat-navbar>
selector
First, configure the mat-navbar
component as so:
src/app/mat-navbar/mat-navbar.component.ts
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
@Component({
selector: 'app-mat-navbar',
templateUrl: './mat-navbar.component.html',
styleUrls: ['./mat-navbar.component.css']
})
export class MatNavbarComponent implements OnInit {
@Output() navToggle = new EventEmitter<boolean>();
constructor() { }
ngOnInit() { }
toggleSidenav() {
this.navToggle.emit(true);
}
}
Then change the mat-navbar
template to allow the hamburger button to use the newly defined method.
src/app/mat-navbar/mat-navbar.component.html
<button mat-button class="hamburger mat-button" title="Side Navigation" (click)="toggleSidenav()">
...
</button>
Finally, change the app-root
template to use the output method defined.
src/app/app.component.html
<app-mat-navbar (navToggle)="sidenav.toggle()"></app-mat-navbar>
<mat-sidenav-container class="sidenav-container">
<mat-sidenav #sidenav class="sidenav red lighten-1 white-text pt-4"
mode="over" [opened]="false" [fixedInViewport]="fixed"
[fixedTopGap]="0" [fixedBottomGap]="0">
Sidenav content
</mat-sidenav>
...
</mat-sidenav-container>
Now you should have a sidenav with the content inside a <mat-sidenav-content>
and a skeleteon ready to include navigation links in the <mat-sidenav>
with the mat-navbar
component still separated.