Open/Close sidenav from another component
AngularTypescriptAngular MaterialAngular Problem Overview
I use angular (latest version) and angular material.
There are 3 components:
- header.component, in which there is a control button for right-sidenav
- rigth-sidenav.component, in which is the right-sidenav
- sidenav.component (this is the left main menu), this component calls header.component, right-sidenav.component and content
How to open / close sidenav from another component ? (in my case, the button is in header.component).
Tried the following option (but got the error: TypeError: this.RightSidenavComponent.rightSidenav is undefined):
header.component.html
<button mat-button (click)="toggleRightSidenav()">Toggle side nav</button>
header.component.ts
import { Component } from '@angular/core';
import { RightSidenavComponent } from '../right-sidenav/right-sidenav.component';
@Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss']
})
export class HeaderComponent {
constructor(public RightSidenavComponent:RightSidenavComponent) { }
toggleRightSidenav() {
this.RightSidenavComponent.rightSidenav.toggle();
}
}
sidenav.component.html
<mat-sidenav-container class="sidenav-container">
<mat-sidenav #sidenav mode="side" opened="true" class="sidenav"
[fixedInViewport]="true"> Sidenav </mat-sidenav>
<mat-sidenav-content>
<app-header></app-header>
<router-outlet></router-outlet>
<app-right-sidenav #rSidenav></app-right-sidenav>
</mat-sidenav-content>
</mat-sidenav-container>
sidenav.component.ts
import { Component, Directive, ViewChild } from '@angular/core';
import { RightSidenavComponent } from '../right-sidenav/right-sidenav.component';
@Component({
selector: 'app-sidenav',
templateUrl: './sidenav.component.html',
styleUrls: ['./sidenav.component.scss']
})
export class SidenavComponent {
@ViewChild('rSidenav') public rSidenav;
constructor(public RightSidenavComponent: RightSidenavComponent) {
this.RightSidenavComponent.rightSidenav = this.rSidenav;
}
}
right-sidenav.component.html
<mat-sidenav #rightSidenav mode="side" opened="true" class="rightSidenav"
[fixedInViewport]="true" [fixedTopGap]="250">
Sidenav
</mat-sidenav>
right-sidenav.component.ts
import { Component, Injectable } from '@angular/core';
@Component({
selector: 'app-right-sidenav',
templateUrl: './right-sidenav.component.html',
styleUrls: ['./right-sidenav.component.scss']
})
@Injectable()
export class RightSidenavComponent {
public rightSidenav: any;
constructor() { }
}
Angular Solutions
Solution 1 - Angular
I had the same problem using. I resolved it like this.
SidenavService
import { Injectable } from '@angular/core';
import { MatSidenav } from '@angular/material';
@Injectable()
export class SidenavService {
private sidenav: MatSidenav;
public setSidenav(sidenav: MatSidenav) {
this.sidenav = sidenav;
}
public open() {
return this.sidenav.open();
}
public close() {
return this.sidenav.close();
}
public toggle(): void {
this.sidenav.toggle();
}
Your Component
constructor(
private sidenav: SidenavService) { }
toggleRightSidenav() {
this.sidenav.toggle();
}
> Bind your html toggle() based on your requirement.
App component.
@ViewChild('sidenav') public sidenav: MatSidenav;
constructor(private sidenavService: SidenavService) {
}
ngAfterViewInit(): void {
this.sidenavService.setSidenav(this.sidenav);
}
App Module
providers: [YourServices , SidenavService],
Working sample with code stackblitz
Angular 9+ Update
Per this answer on SO, "Components can no longer be imported through @angular/material
. Use the individual secondary entry-points, such as @angular/material/button.
" As such, make sure to import MatSidenav
like so:
import { MatSidenav } from '@angular/material/sidenav';
Solution 2 - Angular
I used @Input() inputSideNav: MatSideNav
in parent\ another component to pass the sideNav object as target property from child component. It works as expected. By the way, I liked the service implementation by @Eldho :)
- It is simple to plugin in your current implementation
- It is scalable to as many sideNav or other UI controls passed as reference into the component definition
- No cluttering in code and plain as it looks.
- Vote up if you get what we mean :)
Child.component.html
<app-layout-header [inputSideNav]="sideNav"></app-layout-header>
<mat-sidenav-container>
<mat-sidenav #sideNav (click)="sideNav.toggle()" mode="side">
<a routerLink="/">List Products</a>
</mat-sidenav>
<mat-sidenav-content>
<router-outlet></router-outlet>
</mat-sidenav-content>
</mat-sidenav-container>
layout-header.component.html
<section>
<mat-toolbar>
<span (click)="inputSideNav.toggle()">Menu</span>
</mat-toolbar>
</section>
layout-header.component.ts
import { Component, OnInit, Input } from '@angular/core';
import { MatSidenav } from '@angular/material';
@Component({
selector: 'app-layout-header',
templateUrl: './layout-header.component.html',
styleUrls: ['./layout-header.component.css']
})
export class LayoutHeaderComponent implements OnInit {
@Input() inputSideNav: MatSidenav;
constructor() { }
ngOnInit() {
}
}
Solution 3 - Angular
If you are working in Angular 9+ Remember add param of static: true
in @ViewChild
like:
@ViewChild('rightSidenav', {static: true}) sidenav: MatSidenav;
You can see my code working on Angular 9 here:
https://stackblitz.com/edit/angular-kwfinn-matsidenav-angular9
Solution 4 - Angular
For an easier solution just use an @ouput() event emitter.
In your parent component
<mat-sidenav
#sidenav
class="sidenav"
fixedInViewport
[opened]="opened"
[mode]="mode"
>
// catch the emitted output from child component
<childcomponent (sidenav)="sidenav.toggle()"></childcomponent>
....
</mat-sidenav>
child component
<mat-toolbar>
<div>
<button mat-button (click)="toggle()">
<mat-icon>menu</mat-icon>
</button>
</div>
</mat-toolbar>
child component.ts
import {Component, EventEmitter, Output} from '@angular/core';
@Component({
selector: 'childcomponent',
templateUrl: './childcomponent.component.html',
styleUrls: ['./childcomponent.component.scss'],
})
export class ChildComponent {
@Output() sidenav: EventEmitter<any> = new EventEmitter();
toggle() {
this.sidenav.emit();
}
}
Solution 5 - Angular
Though people above has already answered, but I believe my solution is more simple when I solved for myself.
You need to have a service in middle to support both components.
Let's say, such service is NavService
, below code goes into NavService
:
public toggleNav: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public toggle(): void {
this.toggleNav.next(!this.toggleNav.value);
}
Then you have a component from where you would like to open or close your SideNav
. In my case, I created the button in the Navbar
as:
button *ngIf="this.loginService.tokenSubject.value" mat-mini-fab color="warn" (click)="onToggle()"><mat-icon>menu</mat-icon></button>
Then in the SideNavComponent
that you would have created for SideNav
, put this in the mat-sidenav
tag:
[opened]="this.sideNavService.toggleNav.value"
Of course, I injected the NavService as sideNavService in the constructor of this component.
Solution 6 - Angular
First of all, you don't need to toggle the right sidenav in the sidenav component. Your toggle button is in the header component, so you can do this in your header component.
header.component.ts
import { Component } from '@angular/core';
import { RightSidenavComponent } from '../right-sidenav/right-sidenav.component';
@Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss']
})
export class HeaderComponent {
rightSidenav: boolean;
constructor(public RightSidenavComponent:RightSidenavComponent) { }
toggleRightSidenav() {
this.rightSidenav = !this.rightSidenav;
}
}
And in your header.component.html, use this code:
<app-right-sidenav *ngIf="rightSideNav"></app-right-sidenav>
This will make your work easy.
Solution 7 - Angular
Just to add to Eldho's answer, you can disable animations for a true hiding effect.
<mat-sidenav #leftbar opened mode="side" class="ng-sidenav" [@.disabled]="true">
Solution 8 - Angular
to expanded on eldho service response I had an error when clicking this.sidenav is undefined' I fixed it by making sure my Side nav was tagged with #sidenav to match the selector on the app.component.ts file.
Also mat-drawer-container and mat-drawer seems to be the new name for sidenav
<mat-drawer-container autosize>
<mat-drawer #sidenav class="example-sidenav" mode="side" position="end">
</mat-drawer>
<mat-drawer-content autosize="true">
</mat-drawer-content>
</mat-drawer-container>
the app.component.ts
@ViewChild('sidenav') public sidenav: MatSidenav;