เรายังอยู่ในเนื้อหาเกี่ยวกับ Routing อย่างที่ได้เคยกล่าวไว้แล้วในตอนแรกๆ ของบทความเกี่ยวกับ
Agular Router ว่าเนื้อหาเกี่ยวกับ routing จะมีค่อนข้างมาก และมีความสำคัญสำหรับการพัฒนา App
ด้วย Angular เรายังใช้รูปแบบตัวอย่างจากบทความที่ผ่านๆ มา แต่เนื้อหาตอนต่อไปนี้จะแยกออกมาพิเศษ
อาจไม่ได้เกี่ยวข้องกับเนื้อหาโดยตรง แต่ใช้ตัวอย่างร่วมกัน
จากตัวอย่างที่ผ่านๆ มาเรามีเมนู แสดงอยู่ด้วยกัน 4 เมนู คือ Home User Product และ Contact Us
เนื้อหานี้เราจะสนใจในส่วนของ Contact Us เมนู
เนื่องจากส่วนของ route path ของ contact us เรากำหนดลิ้งค์ในไฟล์ app.component.html
เป็น /contact ทบทวนได้ที่
การใช้งาน Routing และ Navigation ใน Angular เบื้องต้น ตอนที่ 1 http://niik.in/841
https://www.ninenik.com/content.php?arti_id=841 via @ninenik
ไฟล์ app.component.html (ตัดมาบางส่วน)
<a class="navbar-brand" routerLink="/contact" routerLinkActive="active">Contact Us</a>
แต่เนื่องจากเราไม่ได้กำหนด route path ให้กับ contact เนื่องจากไม่มี component ที่ใช้งานอยู่ ทำให้เวลาที่เรา
คลิกไปที่ /contact ก็จะดึง PagenofoundComponent มาแสดงแทน
ตอนนี้เราต้องการจะสร้างหน้า contact มาใช้งาน รวมถึงกำหนด route path /contact ด้วย แต่จะกำหนดในอีกรูปแบบ
ก่อนอื่นให้เราสร้าง contact component ขึ้นมาก่อน โดยใช้คำสั่ง
C:\projects\simplerouter>ng g c contact
โดยหน้า view ของ contact เราจะสร้างฟอร์มสำหรับสมมติส่งข้อมูลอย่างง่าย ตามรูปแบบดังนี้
ไฟล์ contact.component.html
<form class="myform"> <p>contact works!</p> <div class="text-center">{{ details }}</div> <div> <div> <label>Title: </label> </div> <div> <input type="text" class="form-control" /> </div> <div> <label>Message: </label> </div> <div> <textarea class="form-control" rows="4"></textarea> </div> </div> <br> <p> <button type="button" class="btn btn-sm btn-default" (click)="send()">Send</button> <button type="button" class="btn btn-sm btn-danger" (click)="cancel()">Cancel</button> </p> </form>
เป็นรูปแบบอย่างง่าย มีฟอร์ม มีช่องกรอกหัวข้อ และช่องรายละเอียดข้อความ ตามด้วยปุ่ม ส่งข้อความ
และปุ่ม ยกเลิก
ไฟล์จัดการ contact component เบื้องต้น ตอนนี้เรายังไม่ใส่โค้ดใดๆ เพิ่มเติม
ไฟล์ contact.component.ts
import { Component, OnInit } from '@angular/core'; @Component({ // selector: 'app-contact', templateUrl: './contact.component.html', styleUrls: ['./contact.component.css'] }) export class ContactComponent implements OnInit { public details: string; public sending:boolean = false; constructor() {} send(){ } cancel(){ } ngOnInit() { } }
อยากที่ได้บอกไปข้างต้น หน้า contact นี้เราจะแสดงในอีกรูปแบบ คือจะแสดงใน router outlet ที่มีการกำหนดชื่อ
ของ outlet เพื่ออ้างอิงการใช้งาน
ปกติแล้วเราเคยใช้งาน outlet ที่ไม่มีการกำหนดชื่อ outlet ในรูปแบบ
<router-outlet></router-outlet>
การกำหนดชื่อให้กับ Outlet และการใช้งาน
แต่ทีนี้ เราจะกำหนดชื่อให้กับ outlet โดยใช้งานพร้อมกับ outlet ที่ไม่ได้กำหนดชื่อ ในหน้า app.component.html
รวมถึงกำหนด ลิ้งค์ให้กับเมนู contact us โดยจะให้แสดงผลใน outlet ที่กำหนดชื่อเพิ่มเข้ามา
ให้เราปรับไฟล์ app.component.html เป็นดังนี้
ไฟล์ app.component.html
<nav class="navbar navbar-inverse navbar-fixed-top"> <div class="container"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" routerLink="/home" routerLinkActive="active">Home</a> <a class="navbar-brand" routerLink="/users" routerLinkActive="active">User</a> <a class="navbar-brand" routerLink="/products" routerLinkActive="active">Product</a> <a class="navbar-brand" [routerLink]="[{ outlets: { popup: ['contact'] } }]" routerLinkActive="active">Contact Us</a> </div> </div> </nav> <div class="container"> <br> <br> <h1>{{title}}</h1> <router-outlet></router-outlet> <router-outlet name="popup"></router-outlet> </div><!-- /.container -->
สังเกตการกำหนดลิ้งค์ในบรรทัดที่ 14 จากเดิมเราใช้เป็น routerLink="/products" ก็ปรับใหม่เป็น
[routerLink]="[{ outlets: { popup: ['contact'] } }]"
ตามรูปแบบก็คือ
[routerLink]="[{ outlets: { ชื่อของ outlet: ['route path ที่จะใช้งาน'] } }]"
การทำงานก็คือให้ route path /contact ไปแสดงผลใน outlet ที่ชื่อ popup นั่นเอง
ซึ่งเรามีการเพิ่ม outlet ที่ชื่อ popup เข้ามาในบรรทัดที่ 25
<router-outlet name="popup"></router-outlet>
outlet ที่เพิ่มเข้ามา เรามีการกำหนดชื่อให้กับ outlet ในที่นี้เรากำหนดชื่อเป็น popup (กำหนดชื่อตามต้องการ ในที่นี้
กำหนดว่า popup เพราะจะให้แสดงเป็นแบบ popup)
สำหรับการใช้งาน router outlet หลายๆ อันพร้อมกันในหน้าแสดงผลหนึ่ง นั้นเราจะสามารถใช้งาน outlet ที่ไม่มีการกำหนดชื่อ
ได้เพียง 1 อัน อย่างโค้ดข้างต้น ในบรรทัดที่ 24 กล่าวคือ เราสามารถมี outlet ที่ไม่มีการกำหนด name ได้ในหน้าแสดงผลหนึ่งๆ
เพียงแค่อันเดียว
ก่อนทดสอบการใช้งาน อย่าลืม import และประกาศใช้งาน contact component ในไฟล์ appModule ส่วนนี้จะขอข้ามไม่อธิบายซ้ำ
ต่อไปก็กำหนด route path ในไฟล์ app-routing.module.ts เป็นดังนี้
ไฟล์ app-routing.module.ts
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { HomeComponent } from './home/home.component'; import { PagenofoundComponent } from './pagenofound/pagenofound.component'; import { ContactComponent } from './contact/contact.component'; const appRoutes: Routes = [ { path: 'contact', component: ContactComponent, outlet: 'popup' }, { path: 'home', component: HomeComponent }, { path: '', redirectTo: '/home', pathMatch: 'full' }, { path: '**', component: PagenofoundComponent } ]; @NgModule({ imports: [ RouterModule.forRoot( appRoutes, { enableTracing: false } // <-- debugging purposes only set true ) ], exports: [ RouterModule ] }) export class AppRoutingModule { }
บรรทัดที่ 6 import ContactComponent มาใช้งาน
บรรทัดที่ 10 - 12 เป็นการกำหนด path ให้กับ contact component สังเกตในบรรทัดที่ 12 มีการกำหนด
outlet property เพิ่มเข้ามา เพื่อระบุว่าให้ไปแสดงที่ outlet ที่ชื่อ popup
ดูตัวอย่างการทำงานได้ที่ demo 1 ผลลัพธ์เมื่อเราคลิกที่เมนู contact us จะแสดงผลดังรูป
รูปแบบ url ที่แสดงจะเป็นในรูปแบบนี้
http://localhost:4200/home(popup:contact)
เนื้อหาของ contact จะไปแสดงที่ outlet ที่ชื่อ popup ต่อจาก outlet เดิม รูปแบบใน url ก็คือมี
(popup:contact)
ต่อท้ายเข้าไปใน url ซึ่งก็คือ (ชื่อ outlet:ชื่อ path) โดยส่วนของการแสดงข้อมูลผล outlet ที่กำหนดชื่อนี้ จะแสดงต่อท้าย
ตลอดไปทุกหน้าแม้ว่าเราจะเปลี่ยน path ไปหน้าอื่น ก็ตาม ดูรูปด้านล่างประกอบ
สังเกตว่า ถึงเราคลิกเปลี่ยน path อื่นๆ ตัว contact path ก็จะยังคงอยู่เสมอ ซึ่งจะต้องสร้างฟังก์ชั่นขึ้นมาเพื่อล้างค่าส่วนนี้
จะได้นำเสนอต่อไป ในขณะที่ contact path แสดง ข้อมูลของ contact component ก็จะไปแสดงในทุกหน้า เพราะว่าเรากำหนด
outlet ไว้ใน root ของ app ที่ไฟล์ app.component.html ทั้งนี้ก็เพราะว่าเราจะใช้เป็น popup ที่แสดงได้ในทุกหน้า
การจัดรูปแบบการแสดงของ Outlet ด้วย css style
ต่อไปเราจะจัดรูปแบบการแสดงผลของส่วนของเนื้อหา contact ให้แสดงคล้ายรูปแบบ popup โดยจัดการผ่านการ
กำหนด css โดยตัว outlet จะอ้างอิงผ่าน :host ดังนี้
ไฟล์ contact.component.css
:host { position: fixed; z-index: 90000; /* ค่ายิ่งมาก ยิ่งแสดงอยู่เหนือตัวอื่น */ margin: auto; /* กำหนดเพื่อให้ค่าอยู่ตรงกลาง */ width: 60%; top: 35%; left: 50%; /* ต้องกำหนดเป็น 50% */ margin-top: -100px; margin-left: -30%; /* เป็นค่าติดบ ครึ่งหนึ่งของความกว้าง width*/ padding: 10px; padding-top: 30px; z-index: 99999; background-color: #FFFFFF; box-shadow: 6px 5px 5px 0px rgba(196,196,196,1); border: 1px solid #868686; }
ผลลัพธ์ที่ได้จะเป็นดังรูป
ต่อไป เราจะกำหนดโค้ดเพิ่มเติมในไฟล์ contact.component.ts เพื่อจัดการการทำงานเพิ่มเติม เช่น การสมมติการส่งข้อมูล
และการปิด หรือไม่แสดงข้อมูลหน้า contact ใน outlet ที่ชื่อ popup
ไฟล์ contact.component.ts
import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; @Component({ // selector: 'app-contact', templateUrl: './contact.component.html', styleUrls: ['./contact.component.css'] }) export class ContactComponent implements OnInit { public details: string; public sending:boolean = false; constructor(private router: Router) {} // ฟังก์ชั่นจำลองการส่งข้อมูล send() { this.sending = true; this.details = 'Sending Message...'; // จำลองว่าหลังส่งข้อมูลไปแล้ว 2 วิ ก็ปิด popup ไป setTimeout(() => { this.sending = false; this.closePopup(); }, 2000); } // คำส่ังเมื่อคลิกปุ่มยกเลิก แล้วเรียกคำสั่งปิด popup ให้ทำงาน cancel() { this.closePopup(); } // คำสั่งปิด popup closePopup() { // การกำหนดค่า `null` ให้กับชื่อ outlet ที่ต้องการ // เป็นการล้างข้อมูลที่แสดงใน outlet นั้นๆ this.router.navigate([{ outlets: { popup: null }}]); } ngOnInit() { } }
บรรทัดที่ 2 เรา import Router มาใช้งาน และ Inject เข้ามาใช้งานใน component ตามบรรทัดที่ 14
สำหรับฟังก์ชั่น send() เราจะจำลองว่าส่งข้อมูล ไปแล้ว และจะปิด popup ในอีก 2 วินาที ไม่มีการส่งข้อมูลใดๆ
ส่วนฟัง์ชั่น cancel() จะใช้สำหรับเรียกฟังก์ชั่น closePopup() อีกที
ให้เราสังเกตในส่วนของฟังก์ชั่น closePopup() จะเป็นรูปแบบการใช้งาน router ในการเปลี่ยนหน้าโดยใช้คำสั่ง
navigator()
this.router.navigate([{ outlets: { popup: null }}]);
การทำงานคือทำให้ outlet ที่ชื่อ popup เดิมมีข้อมูลแสดง ไม่มีข้อมูลหรือคือส่งค่า null ไม่มีค่าใดๆ เข้าไป
ทำให้ path มีการติดส่วนของ (popup:contact) ออกจาก url และปิด popup ไป
ทดสอบการทำงานได้ที่ demo 2 คลิกที่เมนู contact us จากนั้น คลิก send หรือ cancel ดูผลลัพธ์
การกำหนด Animation ให้กับ Component
ก่อนจบเรามาเพิ่มลูกเล่นให้กับ popup ด้วย effect animation จะให้เห็นการทำงานเท่านั้น จะไม่ลงลึกในรายละเอียด
มากนัก เนื้อหาเกี่ยวกับ animation ใน angular จะได้นำเสนอต่อไป
ก่อนใช้งาน animation ใน angular เราต้อง import BrowserAnimationsModule module มาใช้งานก่อน
ปรับไฟล์ app.module.ts เป็นดังนี้
ไฟล์ app.module.ts
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { AppComponent } from './app.component'; import { AppRoutingModule } from './app-routing.module'; import { ProductModule } from './product/product.module'; import { UserModule } from './user/user.module'; import { HomeComponent } from './home/home.component'; import { PagenofoundComponent } from './pagenofound/pagenofound.component'; import { ContactComponent } from './contact/contact.component'; @NgModule({ declarations: [ AppComponent, HomeComponent, PagenofoundComponent, ContactComponent ], imports: [ BrowserModule, BrowserAnimationsModule, ProductModule, UserModule, AppRoutingModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
จากนั้นให้เราสร้างไฟล์ animations.ts ใช้คำสั่งเดียวกับรูปแบบการสร้าง class ดังนี้
C:\projects\simplerouter>ng g class animations
เราจะได้ไฟล์ animations.ts ใน root โฟลเดอร์ เดียวกับไฟล์ app.component.ts
จากนั้นให้ปรับโค้ด เกี่ยวกับการทำ animate เป็นดังนี้
ไฟล์ animations.ts
import { animate, AnimationEntryMetadata, state, style, transition, trigger } from '@angular/core'; export const slideInDownAnimation: AnimationEntryMetadata = trigger('routeAnimation', [ // การเคลื่อนไหวเมื่อเปลี่ยนแปลง ค่า routeAnimation state('*', // สภาวะที่กำลังแสดงอยู่ style({ opacity: 1, // แสดงผลและเห็นได้ transform: 'translateX(0)' // ไม่มีการเปลี่ยนแปลงการเคลื่อนไหวใดๆ }) ), transition(':enter', [ // การเปลี่ยนแปลงตอนเริ่มแสดง style({ opacity: 0, // เริ่มจากมองไม่เห็นหรือไม่แสดง transform: 'translateX(-100%)' // เข้ามาจากทางด้านซ้ายมือจากขอบที่ติดลบ }), animate('0.2s ease-in') // ขยับเข้ามาแบบยืดหยุ่นภายใน 0.2 วินาที ]), transition(':leave', [ // การเปลี่ยนแปลงตอนปิด หรือสิ้นสุดการแสดง animate('0.5s ease-out', style({ // ขยับออกแบบยืดหยุ่นภายใน 0.5 วินาที opacity: 0, // สิ้นสุดที่มองไม่เห็น transform: 'translateY(100%)' // เลื่อนออกในเนวตั้งเลื่อนลงแบบค่อยๆ จางหาย })) ]) ]);
ไฟล์นี้จะส่งออกตัวแปรคงที่ที่ชื่อ slideInDownAnimation ที่มีการกำหนดรูปแบบการใช้งาน animation
หรือการเคลื่อนๆ ตามรูปแบบที่กำหนดไปใช้งาน
ส่วนต่อไปก็คือ การนำไปใช้งานกับ contact component ให้เราแก้ไขไฟล์ contact.component.ts เป็นดังนี้
ไฟล์ contact.component.ts
import { Component, OnInit, HostBinding } from '@angular/core'; import { Router } from '@angular/router'; import { slideInDownAnimation } from './../animations'; @Component({ // selector: 'app-contact', templateUrl: './contact.component.html', styleUrls: ['./contact.component.css'], animations: [ slideInDownAnimation ] }) export class ContactComponent implements OnInit { @HostBinding('@routeAnimation') routeAnimation = true; public details: string; public sending:boolean = false; constructor(private router: Router) {} // ฟังก์ชั่นจำลองการส่งข้อมูล send() { this.sending = true; this.details = 'Sending Message...'; // จำลองว่าหลังส่งข้อมูลไปแล้ว 2 วิ ก็ปิด popup ไป setTimeout(() => { this.sending = false; this.closePopup(); }, 2000); } // คำส่ังเมื่อคลิกปุ่มยกเลิก แล้วเรียกคำสั่งปิด popup ให้ทำงาน cancel() { this.closePopup(); } // คำสั่งปิด popup closePopup() { // การกำหนดค่า `null` ให้กับชื่อ outlet ที่ต้องการ // เป็นการล้างข้อมูลที่แสดงใน outlet นั้นๆ this.router.navigate([{ outlets: { popup: null }}]); } ngOnInit() { } }
บรรทัดแรกเรามีการ import HostBinding เพื่มเข้ามา เพื่อใช้สำหรับเชื่อมโยงข้อมูลภานใน outlet ไปใช้งาน
ใน App ในที่นี้เรากำหนดผ่านชื่อ routeAnimation ในบรรทัดที่ 13 โดยค่านี้ จะไปใช้งานส่วนจัดการ animation
ในโค้ดไฟล์ animations.ts เมื่อค่า routeAnimation เป็น true นั้นคือสามารถทำ animation การเคลื่อนตามค่า
ที่กำหนดได้
บรรทัดที่ 4 เป็นการ import slideInDownAnimation ที่เป็นรูปแบบ animation จากที่เรากำหนดเข้ามาใช้งาน
บรรทัดที่ 10 เป็นการกำหนดให้ contact component นี้ใช้งาน การกำหนด animations จากค่า slideInDownAnimation
ส่วนบรรทัดที่ 13 ก็เหมือนที่กล่าวไปแล้วข้างต้น เป็นการเชื่อมโยงข้อมูลว่า ถ้ามีการแสดง contact component หรือมีการ
เรียกให้หน้า contact component แสดง ก็จะทำให้ค่า routeAnimation ที่กำหนดการเชื่อมโยงแบบ HostBinding ไว้มีการ
เปลี่ยนแปลงค่า หรือมีค่าเท่ากับ true ส่งผลให้ ในส่วนของ animation ทำงาน
ทดสอบผลลัพธ์ได้ที่ demo 3 ด้านล่าง
ตอนนี้ เราได้รู้จัก หรือรับรู้ถึงวิธีการใช้งาน routing ในรูปแบบต่างๆ ไปมากพอสมควรแล้ว แต่เนื้อหาในส่วนนี้ยังไม่หมด
ในตอนหน้า เราจะมาดูในส่วนของการการป้องกันเนื้อหา หรือ path เฉพาะ รูปแบบเดียวกับกรณีหน้า admin ที่จะสามารถเข้า
ดูได้เฉพาะสมาชิกเท่านั้น รอติดตาม