Open/Close sidenav from another component
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() { }
}
Non-working sample with code stackblitz
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';
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() {
}
}
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
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();
}
}
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.