# Day29 【牙起來】 彈跳互動視窗 - Modal **彈跳視窗**有很多種類似的名稱 例如:`Alert`、`Dialog`、`Modal`、`Popup` 等等,彼此各有[些許的差異](https://ux.stackexchange.com/questions/90336/whats-the-difference-between-a-modal-popup-popover-and-lightbox),在此統一以`Modal`來稱呼。 在網頁中,**彈跳視窗**有兩種作法 除了用Bootstrap原生套件做效果之外 Angular也有內件的 `ng-bootstrap` ,透過`NgbModal` 搭配`NgbActiveModal`模組來做出彈跳視窗元件,也是本文接下來會介紹的作法 ## 安裝 `ng-bootstrap` 開啟一個新的專案,並在專案中導入Bootstrap套件 > ng add @ng-bootstrap/ng-bootstrap Bootstrap套件的`Modal`是透過樣板和純粹的Javascript來完成 ## 自己手刻陽春版Modal 若要使用Modal也可以自己寫 先來看看 **陽春版的Modal** > ng g c modal01 修改`modal01.component.html` ```html= <div class="p-3" style="border: solid 1px black"> <div class="modal-header"> <h5 class="modal-title flex-fill">彈跳視窗標題</h5> </div> <div class="modal-body"> <div class="mb-5"> <p>請確認是否繼續進行?</p> </div> <div class="d-flex flex-wrap justify-content-center"> <div class="w-50 btn bg-info" (click)="confirm()">是</div> <div class="w-50 btn bg-light" (click)="deny()">否</div> </div> </div> </div> ``` 可以為這兩個按鈕各寫一個方法來進行處理,偵測使用者按下了哪個按鈕 修改`modal01.component.ts` ```typescript= import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-modal01', templateUrl: './modal01.component.html', styleUrls: ['./modal01.component.css'] }) export class Modal01Component implements OnInit { constructor() { } ngOnInit(): void { } confirm() { alert('您點擊了"是"'); } deny() { alert('您點擊了"否"'); } } ``` 在需要用到此元件的地方來呼叫使用 修改`app.component.html` ```html= <div class="container"> <div class="row justify-content-center"> <div class="col-4"> <app-modal01></app-modal01> </div> </div> </div> ``` 結果畫面 ![](https://i.imgur.com/wv0avSZ.png) --- ## Angular作法 - `NgbModal` 搭配`NgbActiveModal` 使用Angular彈跳視窗的元件作法的話,需要用到Angular中Bootstrap模組的這兩個物件 * `NgbActiveModal` 指的是當前被激活、正作用中彈跳視窗本身,就是子元件身上的物件 * `NgbModal` 是呼叫彈跳視窗的父元件 > 父元件呼叫子元件彈跳視窗,彈窗元件被使用者點擊後,子元件將點擊的資訊傳給父元件 ### 子元件(彈跳視窗)傳值 在 **子元件(彈跳視窗)** 注入`NgbActiveModal`服務 修改`modal01-component.ts` ```typescript= import { Component, OnInit } from '@angular/core'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; @Component({ selector: 'app-modal01', templateUrl: './modal01.component.html', styleUrls: ['./modal01.component.css'] }) export class Modal01Component implements OnInit { constructor(public activeModal: NgbActiveModal) { } ngOnInit(): void { } confirm() { alert('您點擊了"是"'); this.activeModal.close(true); } deny() { alert('您點擊了"否"'); this.activeModal.close(false); } } ``` 可以在關閉自己`this.activeModal.close()`時發送值 也可以另外寫`emit`來發送事件(但比較少用,這邊不再示範),都能達成一樣效果 修改樣板`modal01.component.html` 若需要 **右上角能關閉的的X選項**,可把註解拿掉試試 ```html= <div class="p-3" style="border: solid 1px black"> <div class="modal-header"> <h5 class="modal-title flex-fill">彈跳視窗標題</h5> <!-- <div class="btn bg-danger" (click)="activeModal.dismiss('你按了叉叉')">X</div> --> </div> <div class="modal-body"> <div class="mb-5"> <p>請確認是否繼續進行?</p> </div> <div class="d-flex flex-wrap justify-content-center"> <div class="w-50 btn bg-info" (click)="confirm()">是</div> <div class="w-50 btn bg-light" (click)="deny()">否</div> <!-- 也可替換成以下兩行 --> <!-- <div class="w-50 btn bg-info" (click)="activeModal.close(true)">是</div> --> <!-- <div class="w-50 btn bg-light" (click)="activeModal.close(false)">否</div> --> </div> </div> </div> ``` 在 **父元件(需要用到此元件的地方)** 注入 `NgbModal` 服務 修改`app.component.ts` ```typescript= import { Component } from '@angular/core'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { Modal01Component } from './modal01/modal01.component'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { constructor(private ngbModal: NgbModal) { } public openModal() { this.ngbModal.open(Modal01Component); } } ``` 修改樣板`app.component.html` 留下一個按鈕,點擊此按鈕後開啟彈跳視窗 ```html= <div class="text-center"> <button (click)="openModal()">點擊我開啟彈跳視窗</button> </div> ``` 同時要修改`app.module.ts`,引入`NgbModule`套件 ```typescript= import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { Modal01Component } from './modal01/modal01.component'; @NgModule({ declarations: [ AppComponent, Modal01Component ], imports: [ BrowserModule, NgbModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } ``` 完成畫面 ![](https://i.imgur.com/xav2Cp7.gif) --- 彈跳視窗出現會讓背景有微微變暗效果(如果出現N個彈跳視窗時,背景會黑掉XD) 此時的彈跳視窗是可以透過**點擊背景**、**按下ESC鍵**來關閉視窗的 可以透過參數來修改 * `size`:視窗尺寸大小 * `fullscreen`:全螢幕滿版大小 * `backdrop`:背景變暗效果`true`, `false`、以及滑鼠點擊背景關閉彈跳視窗`'static'` * `keyboard`:鍵盤按下ESC按鍵關閉彈跳視窗`true`, `false` 修改`app.component.ts` 父元件中呼叫彈跳視窗的地方 ```typescript= ... export class AppComponent { constructor(private ngbModal: NgbModal) { } public openModal() { this.ngbModal.open(Modal01Component, { size: 'xl', // fullscreen: 'sm', backdrop: 'static', // or true, false keyboard: false, }) } } ``` ### 父元件接收值 修改`app.component.ts` 父元件中呼叫彈跳視窗的地方 分成**成功回傳**與**回傳失敗**兩個方法 回傳失敗代表使用者提前關閉了彈跳視窗,導致沒有任何回傳值 ```typescript= ... export class AppComponent { constructor(private ngbModal: NgbModal) { } public openModal() { const modal = this.ngbModal.open(Modal01Component) modal.result.then( (result) => { alert('按下其中一個按鈕' + result) }, () => { alert('關閉視窗') } ) } } ``` **回傳值來源** 是前面子元件 `modal01-component.ts` 帶入 `this.activeModal.close();` 的值 可以帶入各種型別,對不同的回傳值做不同處理,如下 ```typescript= ... public openModal() { const modal = this.ngbModal.open(Modal01Component) modal.result.then( (result) => { // 按下按鈕 close if (result === true){ alert('使用者按下了"是"') } else { alert('使用者按下了"否"') } }, (reason) => { // 關閉視窗 dismiss console.log(reason) } ) } ``` 兩個 `() => {}` 函式,分別代表成功與失敗的處理 * 成功的處理 `result` 帶著使用者按下按鍵的回傳結果 * 失敗的處理 `reason` 返回失敗原因:透過滑鼠關閉`0`、透過鍵盤關閉`1` 除了合在一起寫,也可以使用 `.then()`、`.catch()` 方式分別做處理 ```typescript= ... public openModal() { const modal = this.ngbModal.open(Modal01Component) modal.result.then( (result) => { // 按下按鈕 close if (result === true){ alert('使用者按下了"是"') } else { alert('使用者按下了"否"') } } ).catch(reason => { // 關閉視窗 dismiss console.log(reason) }) } ```