前言
上一篇解釋了什麼是單向資料流以及不可變的物件,這一篇要透過實作來了解。
需求描述
我想要實作一個修改文章標題的功能
- 在 ArticleHeader 內新增一個按鈕叫做編輯標題
- 按下後標題顯示輸入框供使用者輸入新標題,按下 Enter 確定修改;按下 ESC 則退出
了解需求後立馬動手吧~
先從 UI 開始
因為這個需求所以要調整 ArticleHeader 元件下的 Template 以及 class ,順便移除掉一些不相干的東西讓這個範例看起來更單純。
ArticleHeader Template 的調整
- 新增編輯標題按鈕、取消編輯按鈕並加上點擊事件
- 把之前的 PipeComponent 給取消
- 重新調整一下文章的資料格式,讓範例更簡單易懂
- 把
subject
那一層拿掉、刪除subtitle
- 把
1 | <header class="post-header"> |
現在網頁呈現的畫面是這樣的:
邏輯是這樣的:
- 使用結構型指令 ngIf 控制標題與輸入框以及編輯與取消按鈕的顯示
- 當使用者點擊編輯標題按鈕時修改
isEdit
的值為True
、點擊取消編輯按鈕時isEdit
的值為False
為了做到這些事情,所以必須先到 ArticleHeader class 進行調整
ArticleHeader class 的調整
這裡需要做的調整如下:
- 新增屬性
isEdit
用來判斷是不是處於編輯模式,預設為False
1 | import { Component, OnInit, Input, Output, EventEmitter} from '@angular/core'; |
回到 Template 補上 ngIf
1 | <header class="post-header"> |
測試一下目前的邏輯是否正確
實作編輯文章標題
這邊有個一個簡單但不太建議的做法:
- 直接在 input 上使用雙向繫結綁定
item.title
這麼做會直接把修改的內容直接寫回去,而需求瞬間就完成了。
但伴隨而來的缺點是
- 會讓子元件與父元件間相依性太重
- 因為是雙向繫結,一但修改後是沒辦法透過取消編輯取消的,會直接修改到原始的資料。
所以這個做法肯定是不太行的,得換個方式。
比較好的方式 - 透過單向資料流與不可變的物件方式
- 在 ArticleHeader Template 的 input 上建立二個 Keyup 事件,並傳入參數
$event
- 當使用者按下 Enter 時觸發
editTitle
方法 - 當使用者按下 ESC 時觸發
exitEdit
方法
- 當使用者按下 Enter 時觸發
- 在 ArticleHeader class 內建立一個屬性值
newTitle
- 進行
ngOnInit()
時將newTitle
賦值為item.title
- input 上使用屬性繫結 value 的值改用
newTitle
1 | <header class="post-header"> |
1 | import { Component, OnInit, Input, Output, EventEmitter} from '@angular/core'; |
- 當觸發
editTitle
方法時,會將 input 內的 value 值取出並賦值給newTitle
,等待傳送給父元件 - 當觸發
editExit
方法時,將原始資料塞回 input 內的 value
前置作業都做完了,剩下就是通知父元件 ArticleList 囉!
- 建立一個新的
@Output()
並宣告一個titleChange
的事件 - 當觸發
editTitle
方法時,使用emit
發射要變更的資料給父元件
1 | import { Component, OnInit, Input, Output, EventEmitter} from '@angular/core'; |
接下來就是到父元件上設定接收並做出實際的改變啦~
- 事件繫結
changeTitle
並且當這個事件被觸發時執行doChange()
方法,使用$event
參數接收子元件送來的資料
ArticleList Template
1 | <!-- Article START--> |
到了最後這個步驟了,為了讓底下的所有子元件都知道資料物件發生改變,所以
atticleData
屬性指向的陣列要重新建立,而陣列裡面的物件如果裡面的屬性有發生修改,也要重新建立。
ArticleList class
1 | import { Component, OnInit } from '@angular/core'; |
- 為了讓陣列重新建立,使用 ES6 的陣列方法 map ,它會回傳一個新的陣列
- 值得注意的是,當進入 if 判斷時,不可以直接寫
item.title = $event.title
這樣就落入之前說的陷阱了- 正確應該使用
Object.assign
方法,回傳一個新的物件並且合併item
以及$event
- 正確應該使用
根據 MDN 的解釋,使用
Object.assign
方法時,如果在目標物件裡的屬性名稱 (key) 和來源物件的屬性名稱相同,將會被覆寫。若來源物件之間又有相同的屬性名稱,則後者會將前者覆寫。
都完成了,來測試看看吧!
糟糕程式好像出了點問題,加上幾行 console.log
觀察看看吧。
原來問題是沒有正確合併,因位子元件傳送給父元件的資料物件內的屬性名稱要與標題的屬性名稱一致才會覆蓋,因此必須回去修改。
1 | editTitle(e) { |
重新測試!
小結
這個範例主要是練習實作一個稍微複雜一點的單向資料流與不可變物件特性的開發技巧,透過這個練習未來在開發多個元件時,可以有效降低元件與元件間的相依性也提升可維護性。
看來有空必須要常常回來這篇文章複習哩~