今回はよくスマホなどで使用されるハンバーガーメニューを実装していきます。ハンバーガーメニューとはヘッダーなどに置かれているメニューボタンを押すと横から出てくるサイドメニューのことです。
完成しているコードは下記にあります。
環境
- node: 18.13.0
- Angular CLI:15.0.0
- Angular Material:15.2.9
プロジェクトの作成
$ ng new hamburger-menu
Angular Materialのインストール
$ ng add @angular/material
コンポーネントの生成
$ ng generate component header
$ ng generate component side-menu
$ ng generate component content
$ ng generate component page-a
$ ng generate component page-b
- header ・・・ ヘッダー
- side-menu ・・・ サイドメニュー
- content, page-a, page-b ・・・ ページ遷移確認用のページ
モジュールのインポート
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ContentComponent } from './content/content.component';
import { HeaderComponent } from './header/header.component';
import { SideMenuComponent } from './side-menu/side-menu.component';
import { PageAComponent } from './page-a/page-a.component';
import { PageBComponent } from './page-b/page-b.component';
import { MatToolbarModule } from '@angular/material/toolbar'; // 追加
import { MatIconModule } from '@angular/material/icon'; // 追加
import { MatSidenavModule } from '@angular/material/sidenav'; // 追加
import { MatListModule } from '@angular/material/list'; // 追加
@NgModule({
declarations: [
AppComponent,
ContentComponent,
HeaderComponent,
SideMenuComponent,
PageAComponent,
PageBComponent
],
imports: [
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,
MatToolbarModule, // 追加
MatIconModule, // 追加
MatSidenavModule, // 追加
MatListModule // 追加
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
- MatToolbarModule ・・・ ヘッダーに使用
- MatIconModule ・・・ サイドメニューの表示・非表示のアイコンに使用
- MatSidenavModule ・・・ 折りたたみ可能なサイドメニュー用のモジュール
- MatListModule ・・・ サイドメニューにメニューを表示するために使用
ルーティング
import { Component, NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ContentComponent } from './content/content.component'; // 追加
import { PageAComponent } from './page-a/page-a.component'; // 追加
import { PageBComponent } from './page-b/page-b.component'; // 追加
const routes: Routes = [ // 更新
{ path: '', component: ContentComponent },
{ path: 'a', component: PageAComponent },
{ path: 'b', component: PageBComponent },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
テンプレートコンポーネント
<div class="example-container">
<!-- ヘッダー-->
<app-header (sidenavToggled)="sidenav.toggle()" [sidenav]="sidenav"></app-header>
<mat-sidenav-container class="example-sidenav-container">
<!-- メインコンテンツ -->
<mat-sidenav-content>
<router-outlet></router-outlet>
</mat-sidenav-content>
<!-- サイトメニュー -->
<mat-sidenav mode="over" #sidenav>
<app-side-menu></app-side-menu>
</mat-sidenav>
</mat-sidenav-container>
</div>
.example-container {
display: flex;
flex-direction: column;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.example-sidenav-container {
flex: 1;
}
import { Component, ViewChild } from '@angular/core';
import { Router } from '@angular/router';
import { MatSidenav } from '@angular/material/sidenav';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'humbuger-menu';
@ViewChild('sidenav')
private sidenav!: MatSidenav
constructor(
private readonly _router: Router,
) {
this._router.events.subscribe(() => {
this.sidenav.close();
});
}
}
app.component.html
<mat-sidenav-container>
で <mat-sidenav-content>
と <mat-sidenav>
を囲みます。 <mat-sidenav-content>
はメインコンテンツ、 <mat-sidenav>
はサイドメニューを表しています。
4行目
ヘッダー部分です。ヘッダーでサイドメニューの開閉を検知してアイコンを変更したいので、14行目で宣言している参照変数 #sidenav
で <mat-sidenav>
をヘッダーに渡しています。
また、 (sidenavToggled)="sidenav.toggle()"
でヘッダー内の sidenavToggled
と、sidenav.toggle()
( sidenav
は14行目の #sidenav
)とを紐づけます。 toggle()
は <mat-sidenav>
で定義されているメソッドで、サイドメニューの開閉を操作するメソッドになります。
14~16行目
mode="over"
でサイドメニューがオーバーレイで表示されるようになります。 <mat-sidenav>
を TypeScript 上で参照できるように #sidenav
と参照変数を宣言しています。
app.component.ts
13~14行目
html で宣言した参照変数 #sidenav を読み込みます。
16~22行目
ページ遷移をしたときにサイドメニューを閉じるようにしてます。
サイドメニューの実装
<mat-nav-list>
<a mat-list-item *ngFor="let link of naviLinks" routerLink="{{link.location}}">
{{link.label}}</a>
</mat-nav-list>
import { Component } from '@angular/core';
@Component({
selector: 'app-side-menu',
templateUrl: './side-menu.component.html',
styleUrls: ['./side-menu.component.css']
})
export class SideMenuComponent {
naviLinks = [
{ location: '', label: 'top' },
{ location: 'a', label: 'page a' },
{ location: 'b', label: 'page b' }
];
}
side-menu.component.html
<mat-list>
を使ってメニューを表示しています。
side-menu.component.ts
9~13行目
サイドメニューで表示するメニューの配列を宣言しています。リンクにアイコンがほしいのであれば icon のような項目を追加してやるといいです。
ヘッダーの実装
<mat-toolbar color="primary">
<mat-icon mat-icon-button class="header-icon" id="header-icon" (click)="toggle()">menu</mat-icon>
<span>Angular Sample</span>
</mat-toolbar>
.header-icon {
margin-right: 20px;
}
import { Component, Output, Input, EventEmitter } from '@angular/core';
import { MatSidenav } from '@angular/material/sidenav';
@Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.css']
})
export class HeaderComponent {
@Output() sidenavToggled = new EventEmitter<{}>();
@Input() sidenav!: MatSidenav;
private icons = {
opened: 'close',
closed: 'menu'
}
toggle(): void {
this.sidenavToggled.emit({});
}
private subscribeToSidenav() {
const icon = document.querySelector('#header-icon') as HTMLElement;
this.sidenav.openedStart.subscribe(() => {
icon.innerHTML = this.icons.opened;
});
this.sidenav.closedStart.subscribe(() => {
icon.innerHTML = this.icons.closed;
});
}
ngOnInit() {
this.subscribeToSidenav();
}
}
header.component.ts
11行目
@Output
を使うことで子コンポーネントから親コンポーネントのイベントを呼び出せるようになります。 sidenavToggled
は app.component.html
で sidenav.toggle()
と紐づけていますので、子コンポーネントであるヘッダーから sidenav.toggle()
が呼び出せるようになります。
12行目
@Input
は親コンポーネントから値を受け取ります。こちらも app.component.html
で宣言したように、<mat-sidenav>
を受け取っています。
19~21行目
アイコンのクリックイベントです。 this.sidenavToggled.emit({})
で sidenavToggled
に紐づいている sidenav.toggle()
を実行します。
26~28行目
openedStart
メソッドを使用して <mat-sidenav>
のopenイベントの発生を検知してアイコンを切り替えています。
30~32行目
closedStart
メソッドを使用して <mat-sidenav>
のcloseイベントの発生を検知してアイコンを切り替えています。
確認
$ ng serve
ブラウザから「http://localhost:4200/」にアクセスすると確認できます。
サイドメニューが閉じている状態

サイドメニューが開いている状態

最後に・・・
今回は簡単に説明しましたが、そのうち @ViewChild()
とか、 @Input()
, @Output()
についての解説記事を書きたいですね。解説できるぐらいに勉強せねば・・・
コメント