# 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了