中安拓也のブログ

中安拓也がプログラミングについて書くブログ

Angular Materialでレスポンシブなサイドメニューを実装する

はじめに

PCなどの大きいディスプレイの時はサイドメニューを常に表示し、モバイル端末などの小さいディスプレイの時はハンバーガーメニューに切り替えてスペースを節約したい。

f:id:l08084:20200320172927p:plain
画面が大きいときはサイドメニュー

f:id:l08084:20200320174008p:plain
画面の横幅が狭くなるとハンバーガーメニューになる

f:id:l08084:20200320174658p:plain
タップするとメニューが横から出てくる

このように端末のディスプレイサイズに合わせて表示形式が変わるサイドメニューを作成していく。

環境

本記事における実行環境と使用ライブラリについて

$ ng version

Angular CLI: 8.3.20
Node: 12.13.1
OS: darwin x64
Angular: 8.2.14
... animations, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.803.20
@angular-devkit/build-angular     0.803.20
@angular-devkit/build-optimizer   0.803.20
@angular-devkit/build-webpack     0.803.20
@angular-devkit/core              8.3.20
@angular-devkit/schematics        8.3.20
@angular/cdk                      7.3.7
@angular/cli                      8.3.20
@angular/fire                     5.2.3
@angular/material                 7.3.7
@ngtools/webpack                  8.3.20
@schematics/angular               8.3.20
@schematics/update                0.803.20
rxjs                              6.5.3
typescript                        3.5.3
webpack                           4.39.2

今回やること

レスポンシブなメニューを実装するために、今回やることだが下記となる。

  • ディスプレイサイズの検知
  • サイドメニューの表示・非表示の切り替え
  • ハンバーガーメニューの表示・非表示の切り替え

ダッシュボード画面

最初に画像で貼ったダッシュボード画面(home.component.html)の実装となる。

ポイントはヘッダー(<app-header>)とサイドメニュー(<mat-sidenav>)の実装、そしてディスプレイサイズを検知するisHandset$の3点である。

<!-- ヘッダー -->
<app-header [isHandset$]="isHandset$" (drawerToggled)="drawer.toggle()"></app-header>

<mat-sidenav-container class="container">
  <!-- サイドメニュー -->
  <mat-sidenav #drawer [attr.role]="(isHandset$ | async) ? 'dialog' : 'navigation'"
    [mode]="(isHandset$ | async) ? 'over' : 'side'" class="side-menu" [opened]="!(isHandset$ | async)">
    <app-main-side-menu [isHandset]="(isHandset$ | async)" (drawerClosed)="drawer.close()"></app-main-side-menu>
  </mat-sidenav>
  <!-- メイン コンテンツ -->
  <mat-sidenav-content class="main-contents">
    <!-- サブサイドメニュー -->
    <router-outlet></router-outlet>
  </mat-sidenav-content>
</mat-sidenav-container>

isHandset$

Angular CDKのBreakpointObserverを使用して画面が小さくなったらtrueを返す、isHandset$を定義する。

このisHandset$を使用してハンバーガーメニューを表示したりサイドメニューを消したりして、レスポンシブデザインを実現する。

  • home.component.ts
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
import { map } from 'rxjs/operators';

// ...省略

export class HomeComponent {
  public isHandset$: Observable<boolean> = this.breakpointObserver
    .observe(Breakpoints.Handset)
    .pipe(map(result => result.matches));

  constructor(private breakpointObserver: BreakpointObserver) {}

}

ヘッダー(<app-header>)

ngIfisHandset$を使って画面が小さいときのみ、ハンバーガーメニューを表示している。isHandset$はObservableを返すので、asyncパイプも使用する必要がある。

  • header.component.html
<mat-toolbar color="primary">
  <div class="main">
    <!-- ハンバーガーメニュー -->
    <button type="button" mat-icon-button *ngIf="(isHandset$ | async)" (click)="toggle()">
      <mat-icon>menu</mat-icon>
    </button>

    <span class="logo" (click)="signOut()">
      <mat-icon class=" header-icon">
        note
      </mat-icon>
      <span class="title">3Memo</span>
    </span>
    <!-- ログアウトボタン(ログインしている時のみ表示される) -->
    <mat-icon matTooltip="ログアウトする" (click)="signOut()" *ngIf="authenticationService.getUser() | async"
      class="right-icon">exit_to_app
    </mat-icon>
  </div>
</mat-toolbar>

サイドメニュー(<mat-sidenav>)

サイドメニューの実装にはAngular MaterialのSidenavを使用している。

  <!-- サイドメニュー -->
  <mat-sidenav #drawer [attr.role]="(isHandset$ | async) ? 'dialog' : 'navigation'"
    [mode]="(isHandset$ | async) ? 'over' : 'side'" class="side-menu" [opened]="!(isHandset$ | async)">
    <!-- 省略... -->
  </mat-sidenav>

isHandset$を使用することでディスプレイが小さくなった時には、attr.roledialogにし、modeoverに、openedfalseにしている

参考サイト

Angular Material Responsive Navigation - Ahmed Abouzied - Medium

https://material.angular.io/cdk/layout/overview

Angular Component Dev Kit 入門 - Qiita

Angular CDK Layoutを触ってみる - Qiita