Reputation: 613
I use angular (latest version) and angular material.
There are 3 components:
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
Upvotes: 61
Views: 99083
Reputation: 131
Angular 17 version ...but the idea is for basically any previous one
<mat-sidenav-container>
<mat-sidenav #sidenav mode="side">I'm a sidenav</mat-sidenav>
<mat-sidenav-content>Some content</mat-sidenav-content>
</mat-sidenav-container>
@Component({
selector: 'elo-admin',
standalone: true,
imports: [CommonModule, MatSidenavModule, MatFormFieldModule, MatSelectModule, MatButtonModule],
templateUrl: './admin.component.html',
styleUrl: './admin.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AdminComponent {
@ViewChild('sidenav') sidenav!: MatSidenav;
private cdr = inject(ChangeDetectorRef);
toggle() {
this.sidenav.toggle();
this.cdr.detectChanges();
}
}
<mat-toolbar color="primary">
<h1>{{title}}</h1>
<button (click)="admin.toggle()">toggle</button>
</mat-toolbar>
<div class="content-wrapper">
<elo-admin #admin />
<mat-card>
<mat-card-content>
<router-outlet />
</mat-card-content>
</mat-card>
</div>
NOTE: I've been stuck for a while due to change detection, you need to trigger it manually after toggle().
Upvotes: 0
Reputation: 133
For a versatile and adaptable solution applicable to various scenarios, including but not limited to side-nav event management, is using NgEventBus.
Steps:
Component That Has Access to the SideNav:
This component is responsible for toggling the MatSidenav based on a specific event.
import { Component, ViewChild } from '@angular/core';
import { MatSidenav } from '@angular/material/sidenav';
import { NgEventBus } from 'ng-event-bus';
@Component({
...
})
export class ComponentThatHasAccessToTheSideNav {
@ViewChild(MatSidenav) matSidenav: MatSidenav;
constructor(private eventBus: NgEventBus) {}
ngOnInit() {
this.eventBus.on('appBarMenuButtonClickedEvent').subscribe(() => this.matSidenav.toggle());
}
}
Component That Has the Toggle Button:
This component triggers the event that the other component listens for, leading to the toggling of the MatSidenav.
import { Component } from '@angular/core';
import { NgEventBus } from 'ng-event-bus';
@Component({
...
})
export class ComponentThatHasTheToggleButton {
constructor(private eventBus: NgEventBus) {}
menuClick() {
this.eventBus.cast('appBarMenuButtonClickedEvent');
}
}
By adopting this approach, there's no need to create a new service for each similar use case. Moreover, multiple subscribers can listen to specific events.
Upvotes: 0
Reputation: 134
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();
}
}
Upvotes: 6
Reputation: 2199
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 :)
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() {
}
}
Upvotes: 37
Reputation: 69
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;
Upvotes: 0
Reputation: 743
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.
Upvotes: 1
Reputation: 99
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
Upvotes: 9
Reputation: 4709
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">
Upvotes: 0
Reputation: 8301
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';
Upvotes: 107
Reputation: 2605
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.
Upvotes: 0