[問題] Angular Material + property binding + component interaction

我們用了Angular Material 的 Toggle 讓使用者可以 turn on 或 turn off產品,但他們turn on 了以後我們會到後端的DB去產生一組產品序號,turn off 以後則也會call API 刪除產品,以及刪除DB的table.

所以如果使用者快速的turn on /off 一直開或關,就會一直call 後端API, 問題是後端是Asynchronous 所以後端如果還沒準備好,前端又一直call就會出錯。

目前我們的解法是在後端完成動作前,就disable 我們的toggle 按鈕,直到後端做完,使用者才可以繼續按。所以我先在component 那邊宣告 isIncompleted: boolean = true, 然後再Promise.then(()=>{isIncompleted = false;})

但問題是, 和後端的function 不在同一個component。所以變成 template A 裡面我寫 <mat-slide-toggle [disabled]= “isIncompleted”> , 可是實際上isIncompleted 這個變數是在 component B 裡面。

整個流程是,使用者按了templateA裡的按鈕後,會event binding 到component A 裡面的change()method , 然後由於component A @Output toggled: EventEmitter, 他會在change()這個裡面去fire toggled()這個event, 進而讓 template B 可使用來event binding Component B, 從component B 去call 後端API

整個binding 大概是使用者按了按鈕(turn on/off)以後, template A -> component A -> template B -> component B

但因為要讓view可以暫時disable toggle 按鈕,等後端完成後,再把它變成enabled. 我目前的想法是,從component B -> component A -> template A 不知道有沒有什麼辦法可以這樣 binding 不同component 的 property?

我目前也試過,直接在component B @Output isIncompleted: boolean = true 看看能不能直接binding template A, <mat-slide-toggle [disabled]= “isIncompleted”> 但也完全沒有反應,還是說不能只 @Output?

Angular Material toggle: https://material.angular.io/components/slide-toggle/overview

官網的property binding 看了很久還是不大懂,要怎麼樣可以讓binding compoment B到template A : https://angular.io/guide/template-syntax#template-expressions

感謝各位> <

提供一個比較常用的解法,切換候用block View 罩住,等完成再解除。

<mat-slide-toggle [disabled]= “isIncompleted”>
應該是可以用的,你output地方確定有確實接到嗎?
寫個function,console.log看看,正常是可以用的

可以考慮把程式碼放上去這裡,大家比較容易幫你看

1個讚

先複述一下你的問題:

// emit
 <mat-slide-toggle /> => ComponentA  => Output => ComponentB

發射數據變化時從組件 mat-slide-toggle 中觸發 ComA ,然後 ComB 通過 Output 捕獲變化來發起 HTTP。這裏可以看出來你的 ComAComB 是嵌套的父子關係,那麽至少可以直接通過屬性來控制。
當然也完全可以使用其他的解決方案,比如引用共同的通信服務,共同依賴父級通信等等。在這之前,說幾句題外話,在任何框架裏面想要做到組件通信,不外乎幾種實現方式:

  1. 通過組件之間的 props 傳遞變量或是引用。(它們往往藉助 HTML ATTR 來綁定或是依賴自身的虛擬節點)
  2. 事件通知
  3. 更高層的父級

像是 公共的組件、公共的服務、共享服務器、共享 Worker、共享全局變量、Event Handle、共享本地存儲、共享路由變化等等都可以用這幾種方式概括。面對這類業務需求時如果對框架本身的通信原理不甚了解,可以退而求其次選擇一些公共的通信方案,不必執著於組件之間的 Binding 。組件之間單向通信是值得推薦的,如果雙向甚至反復通信很可能會使組件本身變髒,後續變得難以維護和擴展。當然,這裏只是一個非常小的功能點,完全可以藉助 Angular 本身的特質來解決。

  1. 通過組件屬性通信
    最基礎的解決方案是在 ComponentA 中增加一個具備 Input 註解的屬性:@Input() toggle: boolean = false ,它攜帶一個初始值,並應用在子組件模板中: <mat-slide-toggle [disabled]= “toggle”>
    每次子組件 mat-slide-toggle 變化時通過 EventEmitter 向上通知,不做其他事。同時增加 ngOnChanges 來監聽 toggle 的變化,A 的父組件 B 也就是監聽者來負責改變它,改變完成后將 <ComA [toggle]="t"> 改變,觸發一次向下的廣博事件完成通信。
    這樣看來已經完成你說的需求了,實際上就是多了一條數據廣博的線:
    HTTP => ComB => ComA.Input => ComA.OnChange => mat-slide-toggle

  2. 通過 Angular 雙向數據綁定方式
    你一定使用過 Angular 的 ngModel ,它是 Angular 自帶的指令,能夠根據數據的變化方式自動響應。實際上 ngModel 就等於 [ngModel] + ngModelChange,一個是 Input 一個是 Output,在綁定一個指令時實際上就是附加了這兩者。
    根據這個原理你可以輕易的把上訴例子改寫成 [(toggleModel)] 。cool!

  3. 通過公共的服務通信
    這個就比較簡單了,你可以參見官方示例來仿寫一個:示例

其實這樣的實現方式並不優雅,因為 ComA 在組件中扮演了傳達者的角色,自身的屬性被擴充卻不做任何事,當有一天你在其他組件中使用 mat-slide-toggle 這個小功能時,被迫又要寫上一堆東西。這時候你可以考慮對業務邏輯抽象變換。

這裏推薦幾個不錯的做法:

  1. mat-slide-toggle 這個用於觸發開關的小東西做一個節流,直到用戶點擊穩定後再向外部發送消息,RxJS 中就有 debounceTime distinctUntilChanged 等等方法,幫你在瀏覽器端過濾一些操作。
  2. 你可以幫上述的過濾或稱作為去抖動的方法寫做一個 pipe ,在任何你需要獲取值的地方都可以加上這個 pipe,它可以保證你獲取的值是完備且不會在一直抖動的。
  3. 由於你總是 “欺騙用戶”,不會真正的發請求出去,在 2 的基礎上你可以繼續完善這個 pipe,使它在一段較長的時間后對當前值與歷史值做一次比較,如果不同就發送一次補償。但要注意這會使 Pipe 變得不可控,如果有隱藏的錯誤會使系統的負擔變得很大。
  4. 在 3 的基礎上,你可以改寫自己的 http 服務,每一次 HTTP 請求在發送時打開一個全局開關,忽略掉此時用戶的輸入值,或是一旦輸入值就主動 cancel 當前的請求。這是一個非常好的做法,很多網站都會在新操作時取消掉之前的通信。

總體來說這是一個比較常見需求,但你面臨的問題說明了應用本身沒有得到良好的設計與公約,類似於廣博、誇組件通信、儲存等等最好要有一個公共規範,它們的解決方案很多 (甚至你可以全部寫成全局變量 :joy:),很容易大家各自有一套做法最後系統內部混亂難以維護,而且誰都不敢改動。
(我不是本土開發者,術語上有一些差別,希望你能看得懂…:hugs:)

3個讚

我@output 出去可是 另一個component的 html沒有接到…是因為 他只能接收一個event嗎?<mat-slide-toggle [disabled]=“disabled” (change)=‘change()’(isComplete)=‘isComplete’> 還是因為html只會load 一次呢?感謝!

@YouShun
我知道為什麼了,因為emit 只能往上層傳,所以一開始我ComponentA 可以往Component B傳,可是如果Component B要往下傳送,一定要用Input, 變成我Input 進ComponentA以後,在Template B使用[] binding property.

@WittBulter
對! 我們目前沒有良好的設計,而且全部的type都用any。後來的解決辦法是,直接用單向向下綁定,因為emit只能向上。