
Dalam artikel ini saya akan mencoba untuk membahas secara rinci ruang lingkup teknologi yang sempit dalam kerangka kerja Angular dan asistennya yang sudah terpisahkan - RxJs, sementara kami tidak akan sengaja menyentuh implementasi server, seperti Ini adalah topik lengkap untuk artikel terpisah.
Teks ini akan berguna bagi mereka yang sudah terbiasa dengan Angular, tetapi ingin memperdalam pengetahuan mereka langsung pada topik tersebut.
Pertama, beberapa informasi dasar.
Apa itu WebSocket dan mengapa Anda membutuhkannya
Menurut Wikipedia , WebSocket adalah "protokol komunikasi dupleks (dapat mengirim dan menerima pada saat yang sama) melalui koneksi TCP, yang dirancang untuk pengiriman pesan langsung antara browser dan server web.
WebSocket dirancang untuk diimplementasikan di browser web dan server web, tetapi dapat digunakan untuk aplikasi klien atau server apa pun. Protokol WebSocket adalah protokol independen yang didasarkan pada protokol TCP. Ini memungkinkan interaksi yang lebih dekat antara browser dan situs web, memfasilitasi distribusi konten interaktif dan pembuatan aplikasi real-time. "
Dengan kata lain, WebSocket memungkinkan server untuk menerima permintaan dari klien dan mengirim permintaan ke klien pada waktu yang diinginkan, dengan demikian, browser (klien) dan server menerima hak yang sama dan kemampuan untuk bertukar pesan ketika terhubung. Permintaan AJAX biasa membutuhkan transfer header HTTP lengkap, yang berarti peningkatan lalu lintas di kedua arah, sedangkan overhead soket web setelah membuat koneksi hanya dua byte. Soket web mengurangi jumlah informasi yang dikirimkan dalam header HTTP sebanyak ratusan dan ribuan kali dan secara signifikan mengurangi waktu tunggu. Koneksi soket web mendukung lintas domain seperti CORS.
Di sisi server, ada paket untuk mendukung soket web, pada klien itu adalah HTML5 WebSocket API, yang memiliki antarmuka tiga metode:
WebSocket - antarmuka utama untuk menghubungkan ke server WebSocket, dan kemudian mengirim dan menerima data melalui koneksi;
CloseEvent - acara yang dikirim oleh objek WebSocket ketika koneksi ditutup;
MessageEvent - Suatu acara yang dikirim oleh WebSocket ketika pesan diterima dari server.
Ini adalah tampilannya di tingkat implementasi JavaSript:
const ws = new WebSocket("ws://www.example.com/socketserver", "protocolOne"); ws.onopen = () => { ws.onmessage = (event) => { console.log(event); } ws.send("Here's some text that the server is urgently awaiting!"); };
onmessage - mendengarkan pesan dari server
kirim - kirim pesan Anda ke server
Artinya, dalam bentuk dasarnya, semuanya sangat sederhana, tetapi jika Anda memiliki keinginan untuk mempelajari topik ini, Anda dapat beralih ke
MDN Web Documents dan pada saat yang sama mempelajari perpustakaan yang mengimplementasikan lapisan mereka sendiri di atas API ini.
Mengapa tidak takut menggunakan WebSocket
Poin pertama yang dapat menakuti adalah
dukungan browser . Saat ini tidak ada masalah seperti itu - WebSocket didukung hampir sepenuhnya baik di web maupun di segmen seluler.

https://caniuse.com/#feat=websocketsPoin kedua adalah
kemudahan implementasi . Ya, pada awalnya ini mengecewakan.
API sangat sederhana sehingga pada pandangan pertama mungkin sulit untuk memahami bagaimana bekerja dengan sejumlah metode yang sederhana, karena semuanya, kecuali satu, melaporkan kesalahan atau koneksi, dan hanya satu di antaranya -
onmessage - membawa untuk soket web apa yang digunakan, mis. untuk menerima data dari server.
Pada saat yang sama, masalahnya adalah bahwa server biasanya mengirimkan data yang berbeda, oleh karena itu, kita memerlukan beberapa pesan yang berbeda? Atau apakah Anda perlu membuat koneksi Anda sendiri untuk setiap model data?
Jadi, tugasnya: Anda harus menerima model pengguna dan model berita terbaru dari server, dan mungkin bahkan sesuatu yang lain.
Saya telah menemukan implementasi yang "elegan":
const wsUser = new WebSocket("ws://www.example.com/user"); wsUser.onmessage = (event) => { // ... }; const wsNews = new WebSocket("ws://www.example.com/news"); wsNews.onmessage = (event) => { // ... }; const wsTime = new WebSocket("ws://www.example.com/time"); wsTime.onmessage = (event) => { // ... }; const wsDinner = new WebSocket("ws://www.example.com/dinner"); wsDinner.onmessage = (event) => { // ... }; const wsCurrency = new WebSocket("ws://www.example.com/currency"); wsCurrency.onmessage = (event) => { // ... }; const wsOnline = new WebSocket("ws://www.example.com/online"); wsOnline.onmessage = (event) => { // ... }; const wsLogin = new WebSocket("ws://www.example.com/login"); wsLogin.onmessage = (event) => { // ... }; const wsLogout = new WebSocket("ws://www.example.com/logout"); wsLogout.onmessage = (event) => { // ... };
Sekilas, semuanya logis. Tapi sekarang bayangkan bagaimana jadinya jika ada puluhan atau ratusan dari mereka. Di salah satu proyek yang kebetulan saya kerjakan, ada sekitar tiga ratus peristiwa.

Kami memecahkan masalah.
Semua perpustakaan pihak ketiga untuk bekerja dengan soket web memungkinkan Anda untuk berlangganan pesan dari tipe addEventListener. Ini terlihat seperti ini:
ws.on("user", (userData) => { / .. })
Seperti yang kita ketahui, kita dapat beroperasi dengan satu metode tunggal -
onmessage , yang menerima semua data sebagai bagian dari koneksinya, sehingga kode ini terlihat agak tidak biasa. Ini diimplementasikan sebagai berikut:
onmessage mengembalikan
MessageEvent yang berisi bidang
data . Ini adalah
data yang berisi informasi yang dikirimkan server kepada kami. Objek ini terlihat seperti ini:
{ "event": "user", "data": { "name": "John Doe", ... } }
di mana
peristiwa adalah kunci yang memungkinkan untuk menentukan informasi apa yang dikirim server. Selanjutnya, di sisi front-end, bus dibuat yang menyaring informasi berdasarkan peristiwa dan mengirimkannya ke alamat yang diinginkan:
const ws = new WebSocket("ws://www.example.com"); ws.onmessage = (event) => { const data = JSON.parse(event.data); if (data.event === 'user') { // ... } if (data.event === 'news') { // ... } };
Ini memungkinkan untuk menerima data yang berbeda dalam koneksi yang sama dan berlangganan melalui sintaksis yang mirip dengan yang biasa untuk acara JS.
WebSockets di Angular
Akhirnya, kami sampai pada hal yang paling penting - menggunakan WebSockets langsung di Angular.
Terlepas dari kesederhanaan bekerja dengan WebSocket API asli, dalam artikel ini kita akan menggunakan RxJs, yang tentu saja, karena kita berbicara tentang Angular.
API WebSocket asli dapat digunakan dalam aplikasi di Angular, membuat antarmuka yang mudah digunakan atas dasar, RxJs Observable, berlangganan pesan yang diperlukan, dll., Tetapi RxJs telah melakukan pekerjaan utama untuk Anda: WebSocketSubject adalah pembungkus reaktif atas WebSocket standar API Itu tidak membuat bus peristiwa atau menghubungkan kembali pemrosesan. Ini adalah Subjek biasa, yang dengannya Anda dapat bekerja dengan soket web gaya reaktif.
RxJs WebSocketSubject
Jadi,
WebSocketSubject mengharapkan
WebSocketSubjectConfig dan tujuan opsional, di mana Anda dapat mengirimkan tautan ke Subjek yang Anda amati, menciptakan Observable di mana Anda dapat mendengarkan dan mengirim pesan untuk soket web.
Sederhananya, Anda meneruskan url koneksi sebagai argumen WebSocketSubject dan berlangganan semua aktivitas soket web dengan cara yang biasa untuk RxJs. Dan jika Anda perlu mengirim pesan ke server, maka gunakan metode webSocketSubject.next (data) yang biasa sama.
Kami membuat layanan untuk bekerja dengan WebSocket Angular
Jelaskan secara singkat apa yang kami harapkan dari layanan:
- Antarmuka terpadu dan ringkas;
- Kemungkinan konfigurasi pada tingkat koneksi dependensi DI ;
- Kemungkinan penggunaan kembali;
- Mengetik;
- Kemampuan berlangganan untuk menerima informasi dengan kunci;
- Kemampuan untuk mengakhiri langganan;
- Mengirim pesan ke server;
- Hubungkan kembali.
Poin terakhir patut diperhatikan. Sambungan ulang, atau organisasi menghubungkan kembali ke server, adalah faktor terpenting ketika bekerja dengan soket web, sebagai jeda jaringan, server lumpuh, atau kesalahan lain yang menyebabkan terputusnya koneksi dapat menyebabkan aplikasi mogok.
Penting untuk dicatat bahwa upaya rekoneksi
tidak boleh terlalu sering dan tidak boleh dilanjutkan tanpa batas, seperti perilaku ini dapat menangguhkan klien.
Mari kita mulai.
Pertama-tama, kami akan membuat antarmuka konfigurasi layanan dan modul yang akan memberikan kemampuan untuk mengonfigurasi saat tersambung.
Jika memungkinkan, saya akan mempersingkat kode, versi lengkap yang dapat Anda lihat
di komunitas Angular berbahasa Rusia di GitHub .
export interface WebSocketConfig { url: string; reconnectInterval?: number; reconnectAttempts?: number; } export class WebsocketModule { public static config(wsConfig: WebSocketConfig): ModuleWithProviders { return { ngModule: WebsocketModule, providers: [{ provide: config, useValue: wsConfig }] }; } }
Selanjutnya, kita perlu menggambarkan antarmuka pesan soket web:
export interface IWsMessage<T> { event: string; data: T; }
di mana
acara adalah kuncinya, dan
data yang diperoleh oleh kunci adalah model yang diketik.
Antarmuka publik layanan terlihat seperti ini:
export interface IWebsocketService { on<T>(event: string): Observable<T>; send(event: string, data: any): void; status: Observable<boolean>; }
Layanan memiliki bidang:
// WebSocketSubject private config: WebSocketSubjectConfig<IWsMessage<any>>; private websocketSub: SubscriptionLike; private statusSub: SubscriptionLike; // Observable interval private reconnection$: Observable<number>; private websocket$: WebSocketSubject<IWsMessage<any>>; // , private connection$: Observer<boolean>; // Observable private wsMessages$: Subject<IWsMessage<any>>; // private reconnectInterval: number; // private reconnectAttempts: number; // private isConnected: boolean; // public status: Observable<boolean>;
Di konstruktor dari kelas layanan, kami mendapatkan objek WebSocketConfig ditentukan ketika modul terhubung:
constructor(@Inject(config) private wsConfig: WebSocketConfig) { this.wsMessages$ = new Subject<IWsMessage<any>>(); // , , this.reconnectInterval = wsConfig.reconnectInterval || 5000; this.reconnectAttempts = wsConfig.reconnectAttempts || 10; // connection$ websocket$ this.config = { url: wsConfig.url, closeObserver: { next: (event: CloseEvent) => { this.websocket$ = null; this.connection$.next(false); } }, // connection$ openObserver: { next: (event: Event) => { console.log('WebSocket connected!'); this.connection$.next(true); } } }; // connection status this.status = new Observable<boolean>((observer) => { this.connection$ = observer; }).pipe(share(), distinctUntilChanged()); // this.statusSub = this.status .subscribe((isConnected) => { this.isConnected = isConnected; if (!this.reconnection$ && typeof(isConnected) === 'boolean' && !isConnected) { this.reconnect(); } }); // , - this.websocketSub = this.wsMessages$.subscribe( null, (error: ErrorEvent) => console.error('WebSocket error!', error) ); // this.connect(); }
Metode koneksi itu sendiri sederhana:
private connect(): void { this.websocket$ = new WebSocketSubject(this.config); // // , , // , // , this.websocket$.subscribe( (message) => this.wsMessages$.next(message), (error: Event) => { if (!this.websocket$) { // run reconnect if errors this.reconnect(); } }); }
Sambung kembali sedikit lebih rumit:
private reconnect(): void { // interval reconnectInterval this.reconnection$ = interval(this.reconnectInterval) .pipe(takeWhile((v, index) => index < this.reconnectAttempts && !this.websocket$)); // , this.reconnection$.subscribe( () => this.connect(), null, () => { // Subject complete if reconnect attemts ending this.reconnection$ = null; if (!this.websocket$) { this.wsMessages$.complete(); this.connection$.complete(); } }); }
Metode
on , juga sangat sederhana, tidak ada yang bisa dikomentari.
public on<T>(event: string): Observable<T> { if (event) { return this.wsMessages$.pipe( filter((message: IWsMessage<T>) => message.event === event), map((message: IWsMessage<T>) => message.data) ); } }
Metode
kirim bahkan lebih sederhana:
public send(event: string, data: any = {}): void { if (event && this.isConnected) { // any , "" string // :) this.websocket$.next(<any>JSON.stringify({ event, data })); } else { console.error('Send error!'); } }
Itu seluruh layanan. Seperti yang Anda lihat, sebagian besar kode jatuh pada organisasi koneksi ulang.
Sekarang mari kita lihat bagaimana menggunakannya. Hubungkan modul WebsocketModule:
imports: [ WebsocketModule.config({ url: environment.ws // 'ws://www.example.com' }) ]
Dalam konstruktor komponen, kami menyuntikkan layanan dan berlangganan pesan dari '
pesan ', mengirim teks kembali ke server:
constructor(private wsService: WebsocketService) { this.wsService.on<IMessage[]>('messages') .subscribe((messages: IMessage[]) => { console.log(messages); this.wsService.send('text', 'Test Text!'); }); }
Nama peristiwa lebih mudah untuk dimasukkan ke dalam konstanta atau enumerasi. Kami membuat suatu tempat file websocket.events.ts dan menuliskannya di dalamnya:
export const WS = { ON: { MESSAGES: 'messages' }, SEND: { TEXT: 'text' } };
Menulis ulang langganan menggunakan objek WS yang dibuat:
this.wsService.on<IMessage[]>(WS.ON.MESSAGES) .subscribe((messages: IMessage[]) => { console.log(messages); this.wsService.send(WS.SEND.TEXT, 'Test Text!'); });

Kesimpulannya
Faktanya, itu saja. Ini adalah minimum yang perlu diketahui oleh pengembang Angular tentang WebSockets. Saya harap saya telah membahas topik ini dengan cukup jelas. Versi lengkap dari layanan ini dapat ditemukan di
GitHub .
Untuk semua pertanyaan, Anda dapat menghubungi di komentar,
kepada saya di Telegram atau di
saluran Angular di tempat yang sama.