Graphql Subscription with Angular and Rxjs
In this article, I want to show you how easy it is to use only Angular and its build in Rxjs to do Graphql subscription. (There are existing graphql clients such as Apollo Client, but we show you how simple it it to roll your own.)
Graphql has a subscription
type besides query
and mutation
. Most implementations use websocket to implement subscription
type and send data from server to client.
This is a very nice feature if the client needs to track server’s data in realtime. You can periodically query update from the server, but this is
not efficient. GQL subscription solve this problem nicely: you subscribe only once, then just wait for the server to send you updates. This is exactly where
the stream model of Rxjs
fits in perfectly.
GraphQL Server
We focus on the client implementation, so I just use a python GQL server called Ariadne and copy their example without detail explanations (pip install ariadne uvicorn
). An Apollo server should work the same.
All the server do is to increment the counter every second, five times total.
import asyncio
from ariadne import SubscriptionType, make_executable_schema
from ariadne.asgi import GraphQL
type_def = """
type Query {
_unused: Boolean
}
type Subscription {
counter: Int!
}
"""
subscription = SubscriptionType()
@subscription.source("counter")
async def counter_generator(obj, info):
for i in range(5):
await asyncio.sleep(1)
yield i
@subscription.field("counter")
def counter_resolver(count, info):
return count + 1
schema = make_executable_schema(type_def, subscription)
app = GraphQL(schema, debug=True)
Save it as server.py
then start the server by running:
uvicorn server:app
Now launch a browser and go to http://localhost:8000
, you should be presented with the GQL playground interface.
In the playground you can try subscription { counter }
manually and see the output to make sure the server is working.
Angular
Create a new angular app by using angular-cli:
ng new gql-test
Clean up src/app/app.component.html
to have just a counter and a button:
<div style="text-align:center">
<h1>Counter: {{counter}}</h1>
<button (click)="sub()">GQL Subscribe</button>
</div>
What we want to do is to click the GQL Subscribe
button, and see the Counter
increasing every second.
WebSocket in the Component
The subscription feature are implemented in WebSocket
. WebSocket
establishes one tcp connection to the server and does not tear it down as regular http does. The server can send data over the tcp connection to the client at any time.
WebSocket is defined in rxjs/webSocket
. Add this line to src/app/app.component.ts
:
import { webSocket, WebSocketSubject } from 'rxjs/webSocket'
Add a member gqlSocket: WebSocketSubject<any>
and start the
web socket on init:
export class AppComponent implements OnInit {
gqlSocket: WebSocketSubject<any>
ngOnInit() {
this.gqlSocket = webSocket({
url: 'ws://localhost:8000',
protocol: 'graphql-ws'
})
}
}
Now we have a websocket, and we connect it to our GQL server when the component is initialized.
Sending query subscription and subscript to response
At this step, you should be able to do and ng serve
and browse to http://localhost:4200
. From the browser’s developer tool, you will see the web socket has be established.
All we need to do now is to send command and get results through this socket. But what is the format?
Simple! Just go to the network tab in the developer tool on the Graphql playground page, and check the communication.
As you can see, the client first send a connection_init
message, then a start
message with a query string in payload
field, then the server starts to send
data back, until it finished with a complete
message.
Nice! Now all we need to do is to send the same message and wait for the results!
Send message and parse results
You call next()
on the websocket to send a message to the server, and subscribe
on the websocket to get a response every time the server sends a message back. That is the sweet spot of Rxjs!
ngOnInit() {
this.gqlSocket = webSocket({
url: 'ws://localhost:8000',
protocol: 'graphql-ws'
})
this.gqlSocket.subscribe(response => {
console.log(response)
})
}
sub() {
this.gqlSocket.next({ "type": "connection_init", "payload": {} })
this.gqlSocket.next({
"id": "1",
"type": "start",
"payload": {
"variables": {},
"extensions": {},
"operationName": null,
"query": "subscription {counter\n}\n"
}
})
}
In above code, when we click the button, the component will send two messages to the
GQL server: connection_init
and start
. The component will also subscribe to
any responses from the server by subscribing to the gqlSocket
observable.
Now, let’s go to http://localhost:4200
and click the button, we will see the
response being printed out in the console! Our very simple subscription client is working!
Complete code
Do some experiments in the next
function and we can figure out the following about the protocol used in GQL subscription:
- The
connection_init
message is not necessary. - Whatever you put in the
id
field, will be reflected back in the response. {}
ornull
fields can be omitted
For those who want to find the details, here is the protocol specification from Apollo Server.
For our own small example, the complete component .ts
file is here:
import { Component, OnInit } from '@angular/core'
import { webSocket, WebSocketSubject } from 'rxjs/webSocket'
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
// GQL subscription websoket spec:
// https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md
counter = 0
gqlSocket: WebSocketSubject<any>
ngOnInit() {
this.gqlSocket = webSocket({
url: 'ws://localhost:8000',
protocol: 'graphql-ws'
})
this.gqlSocket.subscribe(response => {
switch (response.type) {
case 'connection_ack':
console.log('connection ack')
break
case 'complete':
console.log('sub completed')
break
case 'data':
this.counter = response.payload.data.counter
break
default:
// not parsed
console.log(response)
}
})
}
sub() {
let id = 'random id'
let query = `subscription {
counter
}`
this.gqlSocket.next({
id: id,
type: 'start',
payload: {
query: query
}
})
}
}
Conclusion
GQL subscription is a natural fit for Rxjs’s observer/subscriber stream model. Handling GQL subscription in Angular is quite straight forward and easy.