How to build Menu with Angular7 and Material Design7 → Part2

Angkarn Janjuang
odds.team
Published in
4 min readNov 9, 2018

สืบเนื่องจาก part ที่แล้ว เราได้ลองสร้าง menu ให้กับเว็บไซต์ของเราด้วย angular และ material ไปแล้ว ซึ่งจะเห็นว่าทำได้ง่ายมาก รัน command ไม่กี่คำสั่ง แก้ style sheet อีกนิดหน่อยก็ได้เว็บไซต์พร้อมเมนูแล้ว

เมนูที่เราสร้างไว้ใน part ที่แล้ว

ย้อนไปภาคแรก

https://medium.com/printcode/menu-side-nav-with-angular7-and-material-design7-82c156ebb00c

Requirement

ความต้องการเพิ่มเติมในครั้งนี้คือ

  • อยากได้เมนูย่อยเพิ่ม
  • เมื่อทำการคลิ้กที่ลูกศร ปิด-เปิด เมนูแล้วต้องเห็นเมนูย่อย
  • เมื่อคลิ้กที่เมนูย่อยให้เปิดหน้าจอของเมนูนั้นๆออกมา
  • เมื่อเข้าไปหน้าจอของเมนูใดๆแล้ว ให้เปลี่ยนสีที่เมนูให้บอกได้ว่ากำลังเข้าเมนูนั้นอยู่ (ในตัวอย่างนี้จะทำการเปลี่ยนสีพื้นหลังของเมนูที่กำลัง active อยู่)
ภาพสุดท้ายที่อยากได้

มาเริ่มกันเลย

Step1

สร้าง service สำหรับจัดการข้อมูลของเมนู

ng g s service/menu-service

Step2

สร้าง Interface โครงสร้างเมนู

export interface MenuItem {
group: MenuItem;
menu: Menu[];
}
export interface Menu {
code: string;
name: string;
}

Step3

สร้าง function สำหรับเตรียมเมนู

getMenuList() {
const menuList: MenuItem[] = [
{
group: { code: 'menu1', name: 'Menu 1' },
menus: [
{ code: 'subMenu1', name: 'Sub Menu 1' },
{ code: 'subMenu2', name: 'Sub Menu 2' },
{ code: 'subMenu3', name: 'Sub Menu 3' }
]
}
}, {
group: { code: 'menu2', name: 'Menu 2' },
menus: [
{ code: 'subMenu1', name: 'Sub Menu 1' },
{ code: 'subMenu2', name: 'Sub Menu 2' },
{ code: 'subMenu3', name: 'Sub Menu 3' }
]
}
}, {
group: { code: 'menu3', name: 'Menu 3' },
menus: [
{ code: 'subMenu1', name: 'Sub Menu 1' },
{ code: 'subMenu2', name: 'Sub Menu 2' },
{ code: 'subMenu3', name: 'Sub Menu 3' }
]
}
}, {
group: { code: 'menu4', name: 'Menu 4' },
menus: []
}
}, {
group: { code: 'menu5', name: 'Menu 5' },
menus: []
}
}
];
return menuList;
}

จากโค้ดด้านบนคือเราจะสร้างเมนู 4 กลุ่มใหญ่คือ Menu 1–5 และ Menu 1-3 จะมีเมนูย่อยอีก 3 เมนูคือ SubMenu1–3 ส่วน Menu 4–5 ไม่มีเมนูย่อย

Step4 (Optional)

สร้าง function สำหรับดึงชื่อ Menu ของ Sub Menu ขั้นตอนนี้หากให้ต้องการเอาหาชื่อเมนูย่อยเพื่อจะเอาไปแสดงที่หน้าจอก็สามารถทำได้ตามนี้

getSubMenuName(menuCode: string) {
const menuList = this.getMenuList();
for (const group of menuList) {
for (const menu of group.menus) {
return ` > ${menu.name}`;
}
}
return '';
}

Step5

ไปที่ไฟล์ side-nav.component.ts

  • ทำการ Inject MenuService เข้ามาใน SideNavComponent
  • ประกาศตัวแปร menuList Type MenuItem array
  • ทำการดึงค่าของ Menu มาเก็บไว้ที่ menuList
  • ประกาศตัวแปร menuGroupSelected Type string
  • เพิ่ม function selectMenu สำหรับ event ของการ ปิด-เปิด เมนูย่อย
export class SideNavComponent {
menuGroupSelected: string;
menuList: MenuItem[];
constructor(
...
private menuService: MenuServiceService
) {
this.menuList = this.menuService.getMenuList();
}
selectMenu(menuGroup: Menu) {
if (this.menuGroupSelected === menuGroup.code) {
this.menuGroupSelected = null;
return;
}
this.menuGroupSelected = menuGroup.code;
}
}

Step6

ไปที่ไฟล์ side-nav.component.html แก้ไข code ในส่วนของ mat-nav-list เป็นแบบด้านล่างนี้แทน

<mat-nav-list>
<div *ngFor="let menuGroup of menuList">
<div *ngIf="menuGroup.menus.length == 0">
<a mat-list-item class="line" routerLinkActive="active" [routerLink]="['/app/'+menuGroup.group.code]">
<span>{{ menuGroup.group.name }}</span>
</a>
</div>
<div *ngIf="menuGroup.menus.length > 0" [ngClass]="{'group-menu-selected': menuGroupSelected==menuGroup.group.code}">
<a mat-list-item class="line" (click)="selectMenu(menuGroup.group)">
<mat-icon>{{menuGroupSelected==menuGroup.group.code? 'keyboard_arrow_down': 'keyboard_arrow_right'}}</mat-icon>
<span>{{ menuGroup.group.name }}</span>
</a>
<div *ngIf="menuGroupSelected==menuGroup.group.code">
<a *ngFor="let menu of menuGroup.menus" mat-list-item class="line" routerLinkActive="active" [routerLink]="['/app/'+menuGroup.group.code+'/'+menu.code]">
<span>{{ menu.name }}</span>
</a>
</div>
</div>
</div>
</mat-nav-list>

อธิบาย Code แบบสั้นๆ

<!-- วนลูปเมนูใน mat-nav-list -->
<div *ngFor="let menuGroup of menuList">
<!-- Code หลังจากบรรทัดนี้คือการ render menu กรณีที่ไม่มี submenu -->
<div *ngIf="menuGroup.menus.length == 0">
<!-- Code หลังจากบรรทัดนี้มี logic ใน render group menu และ submenu
<div *ngIf="menuGroup.menus.length > 0">

Step7

ไปที่ไฟล์ side-nav.component.scss แก้ไข style sheet ของเดิมนิดหน่อยตามตัวอย่างโค้ดด้านล่างนี้

.sidenav-container {
...
.mat-nav-list {
padding-top: 0px;
a.active {
background: #3f51b5;
font-weight: bold;
color:#fff;
}
div > div > div > a {
& span {
padding-left: 12px;
}
}
}
.group-menu-selected {
background: #e6e6e6;
}
}
.line {
border-bottom: 0.1px solid #e0e0e0;
}
.mat-list-item {
span {
white-space: nowrap;
}
}

มาดูผลลัพธ์กันหน่อย

ได้เมนูตามที่อยากได้แล้ว แต่ยังครบตาม requirement ต้องไปสร้าง component ของแต่ละเมนูต่อ

Step8

ขั้นตอนต่อไปให้เราไปสร้าง component และ mapping routing ตามจำนวนเมนูที่เราสร้างไว้

ng g c component/menu1
...
ng g c component/menu5

Step9

สร้างไฟล์ routing module

ng g module component --routing

จะได้ไฟล์ที่ชื่อว่า component-routing.module.ts ให้ mapping routing ตามตัวอย่างด้านล่าง

ที่ไฟล์ component.module.ts ให้ประกาศ component ตามนี้

กลับไปที่ไฟล์ app-routing.module.ts ให้เพิ่ม routing ดังนี้

จากตัวอย่างนี้เราใช้วิธีการโหลด component แบบ Lazy load โดยการโหลดของทั้ง module เข้ามาใน child routing เลยซึ่งเมื่อเราคลิ้กเมนูใน module นั้น angular จำทำการโหลด component ที่ถูก mapping path routing ไว้ มาแสดงใน component SideNav โดยผ่าน router outlet

Final Step

มาถึงขั้นตอนสุดท้ายแล้ว

ไปที่ไฟล์ app.component.html ให้ทำการเพิ่ม router outlet

<router-outlet></router-outlet>

ไปที่ไฟล์ side-nav.component.html ให้เพิ่ม router outlet

<!-- Add Content Here -->
<router-outlet></router-outlet>
เสร็จแล้ว เมนูสามารถใช้งานได้ถูกต้องแล้ว

จบแล้วสำหรับการสร้างเมนูด้วย angular7 และ material design7 ภาค 2 ที่เพิ่มความ Advance ขึ้นมานิดนึง

ตัวอย่างนี้คงเป็นประโยชน์ต่อผู้ที่ผ่านเข้ามาอ่านนะครับ

Source Code: https://gitlab.com/angkarn9/ng7-menu-sidenav

Checkout ไปที่ branch part2 นะครับ

--

--