Angular組件間通信的新解決方案詳解
引言
其中首創(chuàng)了公共的dataService,用于任意組件間通信。 dataService通過angular service特性和注冊表的使用,可以實現(xiàn)消息的一收一發(fā),無需再寫單獨的service邏輯。
背景
一般來講,Angular 已有的組件間通信方式有哪些?
序號 | 通信方式 | 描述 |
---|---|---|
1 | 輸入屬性(@Input) | 通過屬性綁定,將數(shù)據(jù)從父組件傳遞給子組件。 |
2 | 輸出屬性(@Output) | 通過事件綁定,子組件可以發(fā)送事件給父組件,并傳遞數(shù)據(jù)。 |
3 | 父子組件直接訪問 | 在某些情況下,父組件可以通過 ViewChild 或 ContentChild 裝飾器直接訪問子組件或模板中的元素。 |
4 | 服務(Service) | 創(chuàng)建共享的服務,組件可以注入該服務來存儲和獲取數(shù)據(jù)。 |
5 | RxJS Subject 和 Observable | 使用 RxJS 中的 Subject 和 Observable 來實現(xiàn)組件之間的消息傳遞。 |
6 | Angular 路由參數(shù) | 通過路由參數(shù)在不同組件之間傳遞數(shù)據(jù)。 |
7 | NgRx | 使用 NgRx 狀態(tài)管理庫來實現(xiàn)更復雜的組件間通信和數(shù)據(jù)共享。 |
但以上方法各有局限性,要么代碼繁多,要么學習成本高,尤其是跨越多個組件的通信,例如下圖中從組件D
到組件G
,用前兩種方式就太繁瑣了,方式 4 和 5 可能是常用解決方案,但是仍然有些繁瑣——需要為每一組消息傳遞寫專有的 service 代碼。
組件A ???????/??????\ ?????組件B??????組件C ????/???\??????/????\ ??組件D?組件E?組件F??組件G
本文介紹的通信方式是在方式 4 和 5 的基礎上,進行了做了特殊的抽象處理,實現(xiàn)了一個公共的 dataService,最終實現(xiàn)了消息一收一發(fā),無需再寫中間環(huán)節(jié)的代碼。
常規(guī)方式實現(xiàn)組件間消息通信
為了做對比,這里先介紹一下前文所示的方式4+5
方案,這個方案通過 service 和 rxjs 的 subject 結合使用,實現(xiàn)任意組件間通信。
首先,我們創(chuàng)建一個名為MessageService
的服務,用于在組件之間傳遞消息:
//?message.service.ts import?{?Injectable?}?from?'@angular/core'; import?{?Subject?}?from?'rxjs'; @Injectable({ ??providedIn:?'root', }) export?class?MessageService?{ ??private?messageSubject?=?new?Subject<string>(); ??//?Observable?string?stream ??message$?=?this.messageSubject.asObservable(); ??//?Service?method?to?send?a?message ??sendMessage(message:?string)?{ ????this.messageSubject.next(message); ??} }
接下來,我們有兩個組件,SenderComponent
和ReceiverComponent
。SenderComponent
用于發(fā)送消息,而ReceiverComponent
用于接收消息。
//?sender.component.ts import?{?Component?}?from?'@angular/core'; import?{?MessageService?}?from?'./message.service'; @Component({ ??selector:?'app-sender', ??template:?` ????<input?type="text"?[(ngModel)]="message"?/> ????<button?(click)="sendMessage()">發(fā)送消息</button> ??`, }) export?class?SenderComponent?{ ??message:?string; ??constructor(private?messageService:?MessageService)?{} ??sendMessage()?{ ????this.messageService.sendMessage(this.message); ????this.message?=?'';?//?清空輸入框 ??} }
//?receiver.component.ts import?{?Component?}?from?'@angular/core'; import?{?MessageService?}?from?'./message.service'; @Component({ ??selector:?'app-receiver', ??template:?`?<div>接收到的消息:?{{?receivedMessage?}}</div>?`, }) export?class?ReceiverComponent?{ ??receivedMessage:?string; ??constructor(private?messageService:?MessageService)?{ ????this.messageService.message$.subscribe(message?=>?{ ??????this.receivedMessage?=?message; ????}); ??} }
在這個示例中,我們通過MessageService
來實現(xiàn)了SenderComponent
向ReceiverComponent
發(fā)送消息的功能。MessageService
中使用了 RxJS 的Subject
來創(chuàng)建一個可觀察的消息流,然后在SenderComponent
中調用sendMessage
方法來發(fā)送消息,而在ReceiverComponent
中使用subscribe
來訂閱消息流并接收消息。
請注意,為了使MessageService
成為全局可用的單例服務,我們在@Injectable
裝飾器中設置了providedIn: 'root'
。這樣一來,MessageService
將成為整個應用程序中所有組件共享的單一實例。
為了使示例正常工作,別忘了將SenderComponent
和ReceiverComponent
添加到所屬的模塊中,并在模塊的模板中放置對應的組件選擇器。
這樣,SenderComponent
發(fā)送的消息將通過MessageService
傳遞給ReceiverComponent
,并顯示在ReceiverComponent
中。這就完成了通過 Service 和 RxJS 的 Subject 實現(xiàn)組件間消息通信的示例。
新的解決方案
理解本方案,默認需要熟悉 Angular 的 service 存儲傳遞數(shù)據(jù)原理 和 rxjs 的多播用法。
本方案原理是,通過 service 單例的特性(service 在模塊內組件間是共享的)和 Subject 的多播特性,實現(xiàn)一個公共的 service,通過公共的 service 實現(xiàn)數(shù)據(jù)傳遞。相對的,如同前文中的message.service.ts
文件所示,開發(fā)者需要為每一組通信單獨創(chuàng)建 service 文件,單獨寫響應的邏輯。
理解本方案的三個關鍵點:
單例 通過在@Injectable
裝飾器中設置providedIn: 'root'
,service 成為整個應用程序中所有組件共享的單一實例。因為是共享的,所以能作為通信中消息的載體。這是本方案的根本前提。
rxjs 的多播 基礎原理是觀察者模式(即發(fā)布訂閱模式)
注冊表 為了復用 service,簡化代碼,本方案引入了一個注冊表,用來存儲對應每個消息事件的 Subject 對象。Subject 對象在創(chuàng)建監(jiān)聽時(需要接收消息的地方)創(chuàng)建。
service 代碼如下:
//?dataService.service.ts import?{?Injectable?}?from?'@angular/core'; import?{?Subject,?Subscription?}?from?'rxjs'; @Injectable({ ??providedIn:?'root', }) export?class?DataService<T>?{ ??//?創(chuàng)建注冊表,用于存放監(jiān)聽器 ??private?events?=?new?Map(); ??/** ???*?發(fā)送消息 ???* ???*?@param?{string}?event?事件。用于區(qū)別不同的監(jiān)聽 ???*?@param?{T}?value?消息內容 ???*?@returns?{void} ???*?@memberof?DataService ???*/ ??sendMessage(event:?string,?value:?T):?void?{ ????if?(!this.events.has(event))?{ ??????return; ????} ????this.events.get(event).subject.next(value); ??} ??/** ???*?獲取監(jiān)聽器 ???* ???*?監(jiān)聽器其實就是一個rxjs?Subject對象,通過訂閱來獲取數(shù)據(jù)。 ???* ???*?注意: ???*?1.getListener()應該在sendMessage()之前,否則sendMessage()中獲取不到監(jiān)聽器,無法發(fā)消息 ???*?2.getListener()應放在ngOnInit()、ngAfterViewInit()等只會執(zhí)行一次的生命周期函數(shù)中 ???* ???*?@param?{string}?event?事件。用于區(qū)別不同的監(jiān)聽 ???*?@returns?{Subject<T>} ???*?@memberof?DataService ???*/ ??getListener(event:?string):?Subject<T>?{ ????//?多處監(jiān)聽時會走到此分支 ????if?(this.events.has(event))?{ ??????const?current?=?this.events.get(event); ??????current.count++; ??????return?current.subject; ????} ????const?listener?=?{ ??????count:?1,?//?該字段用于記錄監(jiān)聽(訂閱)者個數(shù) ??????subject:?new?Subject<T>(), ????}; ????/** ?????*?創(chuàng)建監(jiān)聽器,并將其加入注冊表 ?????* ?????*?所在函數(shù)在創(chuàng)建監(jiān)聽(訂閱)時調用,監(jiān)聽發(fā)生在發(fā)送消息之前,所以在監(jiān)聽這里將監(jiān)聽器加入注冊表 ?????*/ ????this.events.set(event,?listener); ????return?listener.subject; ??} ??/** ???*?取消訂閱 ???* ???*?必須手動取消訂閱。 ???*?取消時檢查監(jiān)聽者個數(shù)。如果沒有監(jiān)聽者了,就移除監(jiān)聽器。 ???* ???*?@param?{string}?event ???*?@param?{Subscription}?subscription ???*?@returns ???*?@memberof?DataService ???*/ ??cancelSubscription(event:?string,?subscription:?Subscription)?{ ????if?(!this.events.has(event))?{ ??????return; ????} ????const?current?=?this.events.get(event); ????current.count--; ????if?(current.count?===?0)?{ ??????//?沒有監(jiān)聽者了,就移除監(jiān)聽器 ??????this.events.delete(event); ????} ????subscription.unsubscribe(); ??} }
使用時只需引入上面的公共 dataService,然后以如下示例的方式直接調用 api 就行。
創(chuàng)建監(jiān)聽的示例:
//?接收傳遞過來的消息 ??receiveChangeMessage; ??ngOnInit()?{ ????... ????this.subscription?=?this.dataService.getListener('event_name').subscribe(message?=>?{ ????????this.receiveChangeMessage?=?message; ??????}); ????... ??} ??ngOnDestroy()?{ ????this.dataService.cancelSubscription('event_name',?this.subscription); ??}
發(fā)送消息的示例:
public?onSomethingChange(value:?boolean):?void?{ ????... ????this.dataService.sendMessage('event_name',?value); ??}
給出一個較完整的使用示例
以下是一個使用上文提供的DataService
實現(xiàn)組件間消息通信的示例:
假設我們有兩個組件:SenderComponent
和ReceiverComponent
,它們需要通過DataService
來傳遞消息。
//?sender.component.ts import?{?Component?}?from?'@angular/core'; import?{?DataService?}?from?'./data.service'; @Component({ ??selector:?'app-sender', ??template:?` ????<input?type="text"?[(ngModel)]="message"?/> ????<button?(click)="sendMessage()">發(fā)送消息</button> ??`, }) export?class?SenderComponent?{ ??message:?string; ??constructor(private?dataService:?DataService<string>)?{} ??sendMessage()?{ ????this.dataService.sendMessage('customEvent',?this.message); ????this.message?=?'';?//?清空輸入框 ??} }
//?receiver.component.ts import?{?Component,?OnInit,?OnDestroy?}?from?'@angular/core'; import?{?DataService?}?from?'./data.service'; import?{?Subscription?}?from?'rxjs'; @Component({ ??selector:?'app-receiver', ??template:?`?<div>接收到的消息:?{{?receivedMessage?}}</div>?`, }) export?class?ReceiverComponent?implements?OnInit,?OnDestroy?{ ??receivedMessage:?string; ??subscription:?Subscription; ??constructor(private?dataService:?DataService<string>)?{} ??ngOnInit()?{ ????this.subscription?=?this.dataService.getListener('customEvent').subscribe(message?=>?{ ??????this.receivedMessage?=?message; ????}); ??} ??ngOnDestroy()?{ ????this.dataService.cancelSubscription('customEvent',?this.subscription); ??} }
在這個示例中,我們通過DataService
實現(xiàn)了SenderComponent
向ReceiverComponent
發(fā)送消息的功能。DataService
的sendMessage
方法用于發(fā)送消息,而getListener
方法用于訂閱消息,并在接收到消息時更新ReceiverComponent
中的receivedMessage
屬性。cancelSubscription
方法用于取消訂閱,并在不再有監(jiān)聽者時從注冊表中移除監(jiān)聽器。
請確保將SenderComponent
和ReceiverComponent
添加到所屬的模塊中,并在模塊的模板中放置對應的組件選擇器。
通過使用DataService
,SenderComponent
發(fā)送的消息將傳遞給ReceiverComponent
,并顯示在ReceiverComponent
中。這樣,我們成功地實現(xiàn)了組件間消息通信。
實踐拓展
后續(xù)可以做成插件,以裝飾器的形式調用。
export?class?ReceiveComponent?{ ??@listen('messageEvent') ??message; } export?class?SendComponent?{ ??@send('messageEvent') ??message; }
以上就是 屈金雄 同學的分享,如果你也有更多前端技術想與我們交流,歡迎投稿。除此之外,也歡迎你參與到 OpenTiny 開源中來??,一起共建項目,一起研討前端技術。
關于 OpenTiny
OpenTiny 是一套企業(yè)級組件庫解決方案,適配 PC 端 / 移動端等多端,涵蓋 Vue2 / Vue3 / Angular 多技術棧,擁有主題配置系統(tǒng) / 中后臺模板 / CLI 命令行等效率提升工具,可幫助開發(fā)者高效開發(fā) Web 應用。
核心亮點:
跨端跨框架
:使用 Renderless 無渲染組件設計架構,實現(xiàn)了一套代碼同時支持 Vue2 / Vue3,PC / Mobile 端,并支持函數(shù)級別的邏輯定制和全模板替換,靈活性好、二次開發(fā)能力強。組件豐富
:PC 端有80+組件,移動端有30+組件,包含高頻組件 Table、Tree、Select 等,內置虛擬滾動,保證大數(shù)據(jù)場景下的流暢體驗,除了業(yè)界常見組件之外,我們還提供了一些獨有的特色組件,如:Split 面板分割器、IpAddress IP地址輸入框、Calendar 日歷、Crop 圖片裁切等配置式組件
:組件支持模板式和配置式兩種使用方式,適合低代碼平臺,目前團隊已經將 OpenTiny 集成到內部的低代碼平臺,針對低碼平臺做了大量優(yōu)化周邊生態(tài)齊全
:提供了基于 Angular + TypeScript 的 TinyNG 組件庫,提供包含 10+ 實用功能、20+ 典型頁面的 TinyPro 中后臺模板,提供覆蓋前端開發(fā)全流程的 TinyCLI 工程化工具,提供強大的在線主題配置平臺 TinyTheme
以上就是Angular組件間通信的新解決方案詳解的詳細內容,更多關于Angular組件間通信的資料請關注腳本之家其它相關文章!
相關文章
AngularJS 購物車全選/取消全選功能的實現(xiàn)方法
下面小編就為大家?guī)硪黄狝ngularJS 購物車全選/取消全選功能的實現(xiàn)方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-08-08使用Angular CLI快速創(chuàng)建Angular項目的一些基本概念和寫法小結
這篇文章主要介紹了使用Angular CLI快速創(chuàng)建Angular項目的一些基本概念和寫法小結,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-04-04