---
# System prepended metadata

title: Day10 【牙起來】 繫結4 雙向綁定 - 程式面介紹

---

# Day10 【牙起來】 繫結4 雙向綁定 - 程式面介紹


再次回到角色區塊

我們在遊戲專案中，加入**金錢**元素

在 `role.component.ts` 加上 `money` 成員變數
```typescript=
...

export class RoleComponent implements OnInit {
  name = '初心冒險者';
  hp = 5;
  atk = 1;
  money = 0;

...
```

今天我想讓玩家自己能**手動輸入自己擁有的錢**

並修改 `role.component.html`
```html=
<h2>角色區塊</h2>
<div>名稱: {{name}}</div>
<div>血量: {{hp}}</div>
<div>攻擊力: {{atk}} <button (click)="addAtk()">點我增加攻擊力</button></div>
<div>金錢: {{money}} </div>
<input type="text" placeholder="自己的錢請自己輸入">
```

結果畫面

![](https://i.imgur.com/S6iiPbE.png)

## 使用其他方式

這個功能可以純粹透過前面章節的**事件繫結**來達成
這邊使用`(input)`事件，當輸入框**偵測到任何輸入**都會觸發
同時要帶入`$event` 事件參數，`$event` 是詳細描述此次事件之各種數值的物件

> 有幾種`Evnet`方式可以玩看看
> 
> * `(input)`: 偵測任何輸入時觸發
> * `(keyup)`: 離開鍵盤按鍵時觸發（連續長按同一個鍵 離開後只算一次）
> * `(keydown)`: 
> * `(keypress)`: 
> * `(focus)`: 
> * `(blur)`: 焦點（當前鼠標或鍵盤聚焦之處）離開輸入框時觸發
> * `(change)`: 焦點**離開輸入框**、並且值被改變時才觸發
> 
> **onChange** 跟 **onInput** 很容易搞混
> 很多人會以為監測正在輸入的每個字是使用 **onChange**
> 其實是用**onInput**

focusOut ?
https://stackoverflow.com/questions/8973532/blur-vs-focusout-any-real-differences

![](https://i.imgur.com/xeoIOQm.png)



修改 `role.component.html` 

```html=
<h2>角色區塊</h2>
<div>名稱: {{name}}</div>
<div>血量: {{hp}}</div>
<div>攻擊力: {{atk}} <button (click)="addAtk()">點我增加攻擊力</button></div>
<div>金錢: {{money}} </div>
<input type="text" placeholder="自己的錢請自己輸入" (input)="changeMoney1($event)" >
```


在 `role.component.ts` 中增加 `changeMoney1`、`changeMoney2` 方法
並且接收 `event` 事件參數

`changeMoney1` 可以換成 `changeMoney2` 試試看效果

修改 `role.component.ts` 

```typescript=
...

export class RoleComponent implements OnInit {
...

  changeMoney1(event: any) {
    this.money = event.target.value;
  }

  changeMoney2(event: Event) {
    let value = (event.target as HTMLInputElement).value;
    this.money = Number(value);
  }

}
```

* `changeMoney1` 是用Javascript的作法取得輸入框的欄位值，因為是`any`型別，Typescript不會對此做型別檢查
* `changeMoney2` 作法是將`Event`事件，轉型成Typescript裡的物件類別`HTMLInputElement`再做取值，最後轉型成數字


完成畫面

![](https://i.imgur.com/9IVx41F.gif)

> 不過，其實有更簡潔的方法可以達成這樣的功能

## ngModel 雙向繫結

使用 `[(ngModel)]="money"` 也能得到相同效果
並且更加精簡，在ts程式碼完全不需要 `changeMoney1`、`changeMoney2` 方法

```html=
<h2>角色區塊</h2>
<div>名稱: {{name}}</div>
<div>血量: {{hp}}</div>
<div>攻擊力: {{atk}} <button (click)="addAtk()">點我增加攻擊力</button></div>
<div>金錢: {{money}} </div>
<input type="text" placeholder="自己的錢請自己輸入" [(ngModel)]="money" >
```

![](https://i.imgur.com/yQYgDRn.png)

若使用到`ngModel`，要在Module模組底下 `app.module.ts` 新增 `FormsModule`

![](https://i.imgur.com/wC3yrpM.png)


> 因為`ngModel`是隸屬於`FormsModule`模組下的套件，所以要import進來
> 
> 為什麼`ngModel`隸屬在`FormsModule`底下？
> 因為輸入值基本上是使用`<input>`，而`<input>`元素通常會是表單元件的一部份

### ngModel 背後原理

`[(ngModel)]="money"` 同時帶了 屬性繫結`[]` 與 事件繫結`()` 兩種方式
短短的 `[(ngModel)]="money"` 其實代表著以下這行

```html=
[ngModel]="money" (ngModelChange)="money = $event">
```

* `[ngModel]="money"` 是屬性繫結`[]`，繫結對象綁定為`money`
* `(ngModelChange)="money = $event"` 偵測到`ngModel`值發生變化時，事件會被觸發
    * 觸發的事件為 `money = $event`
    * 在此處的`$event`為`ngModel的值`，是**字串**。而非前面的`$event`是`描述事件各項數值的物件`

> `(ngModelChange)` 是一個**特殊的事件繫結**
> 沿用了 AngularJS 的用法，他的存在一定伴隨著`[ngModel]`
> 事件觸發條件是當 **`ngModel` 值發生 `change`** 時才會觸發
> 所以才叫作`ngModelChange`



## 雙向繫結 Two-Way binding

在前幾篇提的繫結，都是**單向繫結**
* 內嵌繫結`{{}}`把程式的值綁定到樣板，對元件而言是吐出值
* 事件繫結 `()` 把樣板的值藉由事件傳遞給程式，對元件而言是接收值
* 屬性繫結 `[]` 把程式的值綁定到樣板，對元件而言是吐出值


> 元件接收值： `view target` => `data source`
> 元件吐出值： `view target` <= `data source`
> 
> 關於元件之間的 `@Input` 與 `@Output` 用法，未來會再詳細介紹

**雙向繫結**意思是，對元件而言是**接收值、也可以吐出值**

因為雙向繫結**同時包含 事件繫結 `()`、屬性繫結 `[]`**
所以寫法是用中括號包小括號`[()]`表示，而中括號小括號的順序不能錯

> 有人說 `[()]` 像是 `banana in the box` 香蕉躺在盒子裡
> 但個人覺得看起來更像是 **鮑魚**(食用貝類)，並且鮑魚可以雙向進出
> 純粹記憶用途，千萬別把我黃標
>
> 到這一步已經完成 77.7% 的Angular了