# 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>
```
結果畫面

---
## 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 { }
```
完成畫面

---
彈跳視窗出現會讓背景有微微變暗效果(如果出現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)
})
}
```