Angular7 で Web アプリを作ろう - sidenav
今日すること
こんにちは、ふるてつです。
残念ながらGW10連休はあっというまに終わりました。
今回のテーマもAngular Material
で、sidenav
の使い方について少し書きたいと思います。
navigation関連のリファレンスについて
リファレンスではnavigation
関連で 3 つのコンポーネントが記載されています。
sidenav
toolbar
menu
です。
その中で一番大きなくくりはsidenav
になると思います。
今回sidenav
の記述はapp.component
に追加します。
そしてsidenav
用のコンポーネントsidenav.component
を新たに追加しようと思います。
sidenav関連のモジュールを追加
今回sidenav
を使用するにあたり下記のモジュールを使用しました。
MatSidenavModule
MatToolbarModule
MatMenuModule
MatListModule
です。
これらはapp.module.ts
に追加する必要があります。
わたしはmaterial.module.ts
を別途作っていますので、今回そちらにに追加しました。
material.module.ts
の内容
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatGridListModule } from '@angular/material/grid-list';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatCardModule } from '@angular/material/card';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatDividerModule } from '@angular/material/divider';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
import { MatTableModule } from '@angular/material/table';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatMenuModule } from '@angular/material/menu';
import { MatListModule } from '@angular/material/list';
@NgModule({
declarations: [],
imports: [
CommonModule,
MatGridListModule,
MatInputModule,
MatFormFieldModule,
MatCardModule,
MatButtonToggleModule,
MatDividerModule,
MatIconModule,
MatButtonModule,
MatTableModule,
MatProgressSpinnerModule,
MatCheckboxModule,
MatSidenavModule,
MatToolbarModule,
MatMenuModule,
MatListModule
],
exports: [
MatGridListModule,
MatInputModule,
MatFormFieldModule,
MatCardModule,
MatButtonToggleModule,
MatDividerModule,
MatIconModule,
MatButtonModule,
MatTableModule,
MatProgressSpinnerModule,
MatCheckboxModule,
MatSidenavModule,
MatToolbarModule,
MatMenuModule,
MatListModule
]
})
export class MaterialModule { }
app.component.htmlを修正
ではまずapp.component.html
を修正します。
Bootstrap
で作っていた時はコンポーネントをヘッダー、フッター、ボディに分けているのみでした。
修正前のapp.component.html
<div class="container-fluid">
<app-header></app-header>
<router-outlet></router-outlet>
<app-footer></app-footer>
</div>
修正後のapp.component.html
<mat-sidenav-container>
<!-- sidenav -->
<mat-sidenav #sidenav mode="side">
<app-sidenav (sidenavClose)="sidenav.close()"></app-sidenav>
</mat-sidenav>
<!-- main content -->
<mat-sidenav-content>
<app-header (sidenavToggle)="sidenav.toggle()"></app-header>
<router-outlet></router-outlet>
<app-footer></app-footer>
</mat-sidenav-content>
</mat-sidenav-container>
全体を<mat-sidenav-container>
でくくり、中に<mat-sidenav>
と<mat-sidenav-content>
タグを配置します。
<mat-sidenav>
中には、今回新しく作るsidenav
用のコンポーネントを配置します。
<mat-sidenav-content>
中には、これまでのヘッダー、フッター、ボディを丸ごと配置します。
補足になりますが、<mat-sidenav>
タグ中の(sidenavClose)="sidenav.close()"
はsidenav
用のコンポーネントとの連係の為に書いています。
sidenavコンポーネントの追加
sidenav
用のコンポーネントをこれから作成します。
同様にサービスも追加します。
ng generate component ./component/common/sidenav
ng generate service ./service/common/sidenavService/sidenav
sidenav.component.html
の内容
<mat-nav-list>
<mat-list-item routerLink="/evaluation-result" (click)="onSidenavClose()">
<mat-icon>home</mat-icon>{{ 'menu.home' | translate }}
</mat-list-item>
<ng-container *ngFor="let item of availableMenuListDtoLists">
<mat-list-item [matMenuTriggerFor]="appMenu">
<mat-icon>arrow_drop_down</mat-icon>
<a matline>{{ item.propertyId | translate }}</a>
</mat-list-item>
<mat-menu #appMenu="matMenu">
<ng-container *ngFor="let subitem of item.availableMenuDto">
<button mat-menu-item (click)="onSidenavClose()"
routerLink="/{{subitem.apiName}}">{{ subitem.propertyId | translate }}
</button>
</ng-container>
</mat-menu>
</ng-container>
<mat-list-item routerLink="/account-setting" (click)="onSidenavClose()">
<mat-icon>account_circle</mat-icon> {{ 'menu.accountSetting' | translate }}
</mat-list-item>
<mat-list-item routerLink="/sign-out" (click)="onSidenavClose()">
<mat-icon>exit_to_app</mat-icon> {{ 'menu.signOut' | translate }}
</mat-list-item>
</mat-nav-list>
ソース全体は<mat-nav-list>
でくくります。
次の3行<mat-list-item>...</mat-list-item>
はホームメニューです。
次の12行は動的にメニューを作る部分で、<ng-container>...</ng-container>
で繰り返し処理を行います。
メニューとサブメニューがありますので繰り返しはネストします。
さらに下 2 つの<mat-list-item>...</mat-list-item>
はアカウント設定とサインアウトになります。
どのメニューもクリックしたときにonSidenavClose()
メソッドを呼び出すようになっています。
sidenav.component.ts
の内容
import { Component, OnInit, Output, EventEmitter } from '@angular/core';
import { SidenavService } from 'src/app/service/common/sidenav/sidenav.service';
import { AvailableMenuListDto } from 'src/app/entity/dto/available-menu-list-dto';
@Component({
selector: 'app-sidenav',
templateUrl: './sidenav.component.html',
styleUrls: ['./sidenav.component.css']
})
export class SidenavComponent implements OnInit {
@Output() sidenavClose = new EventEmitter();
// メニュー
public availableMenuListDtoLists: AvailableMenuListDto[];
constructor(
private sidenavService: SidenavService,
) { }
ngOnInit() {
// メニューを取得する。
this.getAvailableMenu();
}
/**
* メニューを取得する。
*/
private getAvailableMenu(): void {
this.sidenavService.getAvailableMenu()
.subscribe(availableMenuListDtoLists => this.availableMenuListDtoLists = availableMenuListDtoLists);
}
/**
* メニュー選択後に親コンポーネントに対してクローズイベントを発生する。
*/
public onSidenavClose() {
this.sidenavClose.emit();
}
}
このクラスは初期表示時にSidenavService
からメニューの内容を取得します。
他にはonSidenavClose()
メソッドにてsidenavClose
イベントを発生させ、親画面にイベントを通知します(@Output() sidenavClose = new EventEmitter();
)
sidenav.service.ts
の内容
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { TranslateService } from '@ngx-translate/core';
import { Observable, of } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { AppConst } from '../../../app-const';
import { environment } from '../../../../environments/environment';
import { ErrorMessageService } from '../message/error-message.service';
import { AvailableMenuListDto } from 'src/app/entity/dto/available-menu-list-dto';
@Injectable({
providedIn: 'root'
})
export class SidenavService {
private server = environment.production ? AppConst.URL_PROD_SERVER : AppConst.URL_DEV_SERVER;
private webApiUrl = 'availableMenu';
constructor(
private http: HttpClient,
private errorMessageService: ErrorMessageService,
private readonly translateService: TranslateService
) { }
getAvailableMenu(): Observable {
console.log(this.server + this.webApiUrl);
return this.http.get(this.server + this.webApiUrl, AppConst.httpOptions)
.pipe(
catchError(err => {
this.errorMessageService.add(this.translateService.instant('errMessage.http'));
console.log(err);
return of([]);
})
);
}
}
このサービスはサーバからメニュー内容(AvailableMenuListDto[]
)を取得します。
その他 dto などの内容
available-menu-list-dto.ts
import { AvailableMenuDto } from './available-menu-dto';
export class AvailableMenuListDto {
public propertyId: string;
public availableMenuDto: AvailableMenuDto[];
}
available-menu-dto.ts
export class AvailableMenuDto {
public itemNo: number;
public propertyId: string;
public apiName: string;
}
app-const.ts
import { HttpHeaders } from '@angular/common/http';
export class AppConst {
// サーバーURL
static readonly URL_PROD_SERVER = 'http://localhost/api/';
static readonly URL_DEV_SERVER = 'http://localhost:8080/api/';
// ユーザ権限
static readonly ROLE_USER = 'ROLE_USER';
static readonly ROLE_ADMIN = 'ROLE_ADMIN';
// httpオプション(プレフライトリクエスト)
static readonly httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
}),
withCredentials: false
};
}
sidenav 用 api モック作成
こちらも補足になりますが、サーバからメニューの内容を取得する api モックも作りました。
Spring Bootで若干雑に作りましたが、こちらも掲載しておきます。
AccountRestController.java
の内容
package com.weekenditlaboratory.controller;
import java.util.List;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.weekenditlaboratory.entity.dto.account.AvailableMenuListDto;
import com.weekenditlaboratory.mockService.MockAccountService;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@RestController
@RequestMapping("api/availableMenu")
@CrossOrigin
public class AccountRestController {
private final MockAccountService mockAccountService;
@GetMapping
public List getAvailableMenuList() {
return mockAccountService.getAvailableMenuList(Long.valueOf(1));
}
}
MockAccountService.java
の内容
package com.weekenditlaboratory.mockService;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
import com.weekenditlaboratory.entity.dto.account.AvailableMenuDto;
import com.weekenditlaboratory.entity.dto.account.AvailableMenuListDto;
@Service
public class MockAccountService {
public List getAvailableMenuList(Long accountId) {
// 評価メニュー
AvailableMenuDto availableMenuDto01 = new AvailableMenuDto(1, "subMenu.employeeLists","employee-lists");
// 評価結果メニュー
AvailableMenuDto availableMenuDto11 = new AvailableMenuDto(11, "subMenu.employeeLists", "employee-lists");
AvailableMenuDto availableMenuDto12 = new AvailableMenuDto(12, "subMenu.evaluatedResultLists", "evaluated-result-lists");
// 管理者メニュー
AvailableMenuDto availableMenuDto21 = new AvailableMenuDto(21, "subMenu.companyRegistration", "company-registration");
AvailableMenuDto availableMenuDto22 = new AvailableMenuDto(22, "subMenu.employeeRegistration", "employee-registration");
AvailableMenuDto availableMenuDto23 = new AvailableMenuDto(23, "subMenu.employeeBatchRegistration", "employee-batch-registration");
AvailableMenuDto availableMenuDto24 = new AvailableMenuDto(24, "subMenu.employeeLists", "employee-lists");
AvailableMenuDto availableMenuDto25 = new AvailableMenuDto(25, "subMenu.questionRegistration", "question-registration");
AvailableMenuDto availableMenuDto26 = new AvailableMenuDto(26, "subMenu.evaluatorRegistration", "evaluator-registration");
AvailableMenuDto availableMenuDto27 = new AvailableMenuDto(27, "subMenu.evaluatorBatchRegistration", "evaluator-batch-registration");
AvailableMenuDto availableMenuDto28 = new AvailableMenuDto(28, "subMenu.evaluationRuleRegistration", "evaluation-rule-registration");
AvailableMenuDto availableMenuDto29 = new AvailableMenuDto(29, "subMenu.evaluationStatusCheck", "evaluation-status-check");
// 全件管理者メニュー
AvailableMenuDto availableMenuDto31 = new AvailableMenuDto(31, "subMenu.companyLists", "company-lists");
// リストをセット
List evaluateList = new ArrayList();
evaluateList.add(availableMenuDto01);
AvailableMenuListDto evaluateMenuListDto = new AvailableMenuListDto("menu.evaluate", evaluateList);
List evaluatedResultList = new ArrayList();
evaluatedResultList.add(availableMenuDto11);
evaluatedResultList.add(availableMenuDto12);
AvailableMenuListDto evaluatedResultMenuListDto = new AvailableMenuListDto("menu.evaluatedResult", evaluatedResultList);
List administratorList = new ArrayList();
administratorList.add(availableMenuDto21);
administratorList.add(availableMenuDto22);
administratorList.add(availableMenuDto23);
administratorList.add(availableMenuDto24);
administratorList.add(availableMenuDto25);
administratorList.add(availableMenuDto26);
administratorList.add(availableMenuDto27);
administratorList.add(availableMenuDto28);
administratorList.add(availableMenuDto29);
AvailableMenuListDto administratorMenuListDto = new AvailableMenuListDto("menu.administrator", administratorList);
List allAdministratorList = new ArrayList();
allAdministratorList.add(availableMenuDto31);
AvailableMenuListDto allAdministratorMenuListDto = new AvailableMenuListDto("menu.allAdministrator", allAdministratorList);
List res = new ArrayList();
res.add(evaluateMenuListDto);
res.add(evaluatedResultMenuListDto);
res.add(administratorMenuListDto);
res.add(allAdministratorMenuListDto);
return res;
}
}
その他 dto などの内容
AvailableMenuListDto.java
package com.weekenditlaboratory.entity.dto.account;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class AvailableMenuListDto {
private String propertyId;
private List availableMenuDto;
}
こちらはメインメニューの dto です。
他言語化して表示したいのでpropertyId
にはAngularで持っている他言語用のjsonのプロパティを設定するようにしました。
availableMenuDto
は実際の詳細なメニューにあたる内容でList<AvailableMenuDto>
にしています。
AvailableMenuListDto.java
package com.weekenditlaboratory.entity.dto.account;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class AvailableMenuDto {
private Integer itemNo;
private String propertyId;
private String apiName;
}
こちらが詳細メニューの dto です。
動作確認
では動作を確認してみます。
画面を開いたときはこのような感じです。
メニューボタンを押してsidenav
を開くと下記のようにサイドにメニューが表示されます。
メニューをクリックするとサブメニューが表示されます。
最後に、いずれかのメニューをクリックすると、画面が切替わりsidenav
が閉じた元の状態になります。
今日の感想
今日はAngular Material
のsidenav
についてでした。
navigation関連のリファレンスがわたしには分かりづらく、そのためGW中盤に始めたのですが、結局GW明けになりました。
ツールバーにもメニューを置きたいですが、そこはまた別途書きたいと思います。
では、今日もお疲れ様でした。