You signed in with another tab or window.
Reload
to refresh your session.
You signed out in another tab or window.
Reload
to refresh your session.
You switched accounts on another tab or window.
Reload
to refresh your session.
By clicking “Sign up for GitHub”, you agree to our
terms of service
and
privacy statement
. We’ll occasionally send you account related emails.
Already on GitHub?
Sign in
to your account
This is sort of a tricky question.
Universal can work with socket.io but you have to use it a in a different way.
By default Universal waits for all async operations to complete before rendering to a string, this includes things like http requests, promises, Observables and WebSockets.
Since Web Sockets are long lived they stay open and will actually block the app from rendering on Universal so you need to somehow close this connection so that Universal can render.
Sorry I can't provide specifics on how to do this since i've never used socket.io
sam-berry, hassanjuniedi, kenberkeley, The-Noah, xtianus79, cryptpa, and mazba07 reacted with thumbs up emoji
cryptpa reacted with eyes emoji
All reactions
By default Universal waits for all async operations to complete before rendering to a string
To follow-up on this comment,
renderModuleFactory
, the main action in Universal rendering, will wait for the first stable VM tick to emit before rendering.
This may or may not include the WebSocket activity from socket.io
. As such, the main concern should be whether or not socket.io prevents that tick from completing or not. It's possible that with WebSockets, which are just event producer/consumers, they will just be passively considered like DOM event listeners. Please feel free to test this and report back!
@matheusdavidson
socket.io should work assuming the client you're using is Universal compatible (i.e. doesn't try to reference
document
, or
window
) and pulls in its own version of WebSocket for Node (I know this is an issue with Apollo atm, but socket.io I think is fine). The only issues you may face come with duplicate requests, since TransferStateHttpModule does not work with
wss
connections, which may result in flicker (and you will also need to provide full URLs). You can of course disable socket.io on the server by only including it in a browser-specific module or by placing socket calls behind
isPlatformBrowser
checks.
@CaerusKaru
This here
https://github.com/angular/angular/blob/d2707f14574bdfd4ea0c0ecf3d85383f6d54186a/packages/platform-server/src/utils.ts#L46
Which means it waits for all Zones caught operations, which should be all async operations
After throwing together a basic WebSocket example, which socket.io will use if available, it looks like WebSockets are tracked by Zones.
Here
's the example on StackBlitz, running the WebSocket echo test and then closing the socket. I also updated the appRef subscribe to match what
renderModuleFactory
does. You can see that it is initially shown as stable, and then no other stability checks are rendered, implying that once you invoke a WebSocket, your application is unstable for the duration of its existence. See comment below for resolution.
@CaerusKaru
WebSockets are tracked be Zones, you can see that by putting
NgZone.assertInAngularZone()
inside the handler event. Also you can see the patch here
https://github.com/angular/zone.js/blob/a66595af18d4c42584401dd2df387b02cb01d8d9/lib/browser/websocket.ts
However as I mentioned above, WebSockets are long lived, they stay open untill they are closed. In your example the WebSocket is never closed, that's why you don't see the stable event fire and why Universal wont work with socket.io or firebase without special handling for the websockets
@Toxicable
on line 25 the socket is closed once the message is received. I saw that code too and this leads me to believe that closing the socket is not sufficient to restoring stability.
After looking into this a little more, this is apparently a
documented and long-lived issue
. However, I think the one advantage Universal has in this case is that I think it's almost guaranteed this won't hold up rendering, since the appRef call is almost immediately invoked after the components render (ie the initial view is stable, and then immediately not stable). But if this turns out not to be the case after testing, the recommendation would be (1) move socket.io into the browser build only or (2) run all socket.io-related code outside the Angular Zone, which may result in a performance hit/change-detection issues.
@CaerusKaru
Whops, sorry missed that line, in that case I don't know whats going on in that example.
While both of those could be a solution, I personally think comming up with another way / api for handling when to render on the server should be investigated.
I did try but ended up using socket only in the browser build because as you guys pointed out angular doesn't finish rendering the page, probably its waiting for a response from the socket event.
Not sure if closing the connection all the time i receive a response would be very good because we could have other code waiting for socket events.
Maybe an option would be using
socket.io with promises
, i will try this when i have some time.
Hi - You can make a task trackable by Zone.js by doing something similar to here
https://github.com/angular/angular/blob/14e8e880222431eeccdacac6540574b49903bd79/packages/platform-server/src/http.ts#L38
Would that satisfy your use case?
"Unexpected token import" with angularfire2 on angular-cli, AOT, SSR, and firebase-functions
angular/angularfire#1073
It is very useful an official example with server + client using socket.io.
I tried to include it in my server code (based on the official node.js example with express) but it isn't working.
> [email protected] serve:ssr /Users/ks89/git/project
> node dist/server.js
/Users/ks89/git/project/dist/server.js:160136
return /*require.resolve*/(!(function webpackMissingModule() { var e = new Error("Cannot find module \".\""); e.code = 'MODULE_NOT_FOUND'; throw e; }()));
Error: Cannot find module "."
at webpackMissingModule (/Users/ks89/git/project/dist/server.js:160136:76)
A workaround in my case was to force close the socket after a certain time. It increases the render time but it works:
this.socket = io(this.wssEndpoint);
setTimeout(() => {
this.socket.close();
}, 10000);
I can't wait to see a more elegant solution :)
@Ks89 , We are experiencing the same error with Express + Universal. Were you able to find a solution ?
Error: Cannot find module "."
at webpackMissingModule (/Users/ks89/git/project/dist/server.js
Check this. an answer on stackoverflow. You need to detect if it is browser of or server side.
https://stackoverflow.com/a/41721980/6261137
I found a solution that works for me. I'm using feathers.js and don't handle the socket directy. The second step is therefore a guess how it works with pure socket.io.
There is somewhere a similar issue where I found parts of the solution, but I could not find it anymore - so thanks original author :)
First, the socket creation needs to run outside of angular:
this.ngZone.runOutsideAngular(() => {
this.socket = io(this.BASE_URL)
Second, the on
callback needs to run in the zone again (don't ask me why :D)
this.socket.on('message', (data) => {
ngZone.run(() => {
doStuff(data)
Now it should work, but you just have created a memory leak because the socket does not get closed. You need to add a ngOnDestroy method to your service that created the socket:
ngOnDestroy(): void {
this.socket.close()
For completeness here is my code that works with feathers.js:
data.service.ts:
import { Services } from './../../../../shared/types/services'
import { Injectable, NgZone, OnDestroy } from '@angular/core'
import { environment } from '../../environments/environment'
import feathers, { Application } from '@feathersjs/feathers'
import
socketio from '@feathersjs/socketio-client'
import * as rx from 'feathers-reactive'
import * as io from 'socket.io-client'
import { ngFeathersWebsocketSupport } from '../../lib/ng-feathers-websocket-support'
@Injectable()
export class DataService implements OnDestroy {
constructor(
public ngZone: NgZone
this.init()
BASE_URL = environment.dataUrl
app = feathers() as Application<Services>
socket: SocketIOClient.Socket = null
protected init() {
this.ngZone.runOutsideAngular(() => {
this.socket = io(this.BASE_URL)
this.app
.configure(socketio(this.socket, {
timeout: 10 * 1000
}))
.configure(rx({ idField: '_id' }))
.configure(ngFeathersWebsocketSupport({ ngZone: this.ngZone }))
ngOnDestroy(): void {
this.socket.close()
ng-feathers-websocket-support.ts:
export function ngFeathersWebsocketSupport(options: { ngZone: any }) {
const mixin = function (service) {
const originalOn = service.on.bind(service)
const serviceMixin = {
on(event, cb) {
return originalOn(event, (...args) => {
options.ngZone.run(() => {
cb(...args)
// get the extended service object
service.mixin(serviceMixin)
return function (app) {
app.mixins.push(mixin)
Hope this helps anyone :)
Simply we can check for browser
and initilize the socket
things
import { isPlatformBrowser } from '@angular/common';
import { Inject, PLATFORM_ID } from '@angular/core';
constructor(@Inject(PLATFORM_ID) private platformId: Object) {
if(isPlatformBrowser(this.platformId)) { // check if this is runing in browser
this.socket = io('http://localhost:3000/'); // call io constructor
this.init(); // do the all initilization, e.g. this.socket.on('message', msg => {})
@sean-nicholas
thank you so much !! it works !
by the way, using fromEvent
in RxJs makes code much more clean.
in Websocket.service.ts
@Injectable({
providedIn: 'root'
export class WebsocketService implements OnDestroy {
private url = 'ws://localhost:8081';
private socket: SocketIOClient.Socket;
constructor(private readonly ngZone: NgZone) {
this.ngZone.runOutsideAngular(() => {
this.socket = io(this.url, {
reconnection: true,
reconnectionDelay: 5000,
reconnectionAttempts: 5,
this.on$('connection').subscribe(console.log);
this.on$('error').subscribe(console.log);
this.on$('wsException').subscribe(console.log);
this.on$('connect_error').subscribe(console.log);
emit(channel: string, emitValue: any) {
this.socket.emit(channel, emitValue);
on$(channel: string) {
return fromEvent(this.socket, channel);
off(channel: string) {
this.socket.off(channel);
ngOnDestroy() {
this.socket.close();
in Other.Component.ts
// ....
ngOnInit() {
this.websocketService.on$('message').subscribe((message:any) => {
this.ngZone.run(() =>{
this.message = message;
this.changeDetectorRef.markForCheck() // if you need.
sendMessage() {
this.websocketService.emit('message', 'emit a message to server');
ngOnDestroy() {
this.websocket.off('message');
@sean-nicholas @hsuanyi-chou
I tried these solutions, but what about the acknowledgements (callback) in the emit method? Because in this case we are not able to wait for that.
Actually I implemented a queue system, and wait for the response form the server, and if the queue is get emptied I close the connection.
in Other.Component.ts
, use on$
to subscribe and will be received the data when socket.io server emitted.
//...
ngOnInit() {
this.WebsocketService.on$('message').subscribe(data =>
this.ngZone.run(() =>{
this.message = message;
this.changeDetectorRef.markForCheck() // if you need.
//...
you can send any keyword (replace 'message') you like to listen to.
remember using ngZone
in the subscribe callback function.
@hsuanyi-chou
I speak about this case:
emit(channel: string, emitValue: any, cb: Function) {
this.socket.emit(channel, emitValue, cb);
In this case the angular won't wait for the response of the server.
Check this. an answer on stackoverflow. You need to detect if it is browser of or server side.
https://stackoverflow.com/a/41721980/6261137
Esta solución es sencilla y efectiva
Does universal work with socket.io? and how to?
I’m just a beginner trying to understand the
business, maybe it can help me in my
writing and illustration of children’s books.
@sean-nicholas so I am in the middle of trying what you are speaking of and this other solution that is working per se and seems like a hack.
Let me show you what I have:
this is the SSE service, same would go for websockets.
import { isPlatformBrowser } from '@angular/common';
import { Injectable, NgZone, Inject, PLATFORM_ID } from "@angular/core";
import { Observable } from "rxjs";
@Injectable({
providedIn: 'root'
export class SseService {
private isBrowser: boolean = isPlatformBrowser(this.platformId);
constructor(
@Inject(PLATFORM_ID) private platformId: Object,
private _zone: NgZone
if (this.isBrowser) {
console.log('this happened here');
getServerSentEvent(url: string): Observable<EventSource> {
return Observable.create(observer => {
const eventSource: EventSource = this.getEventSource(url);
eventSource.onmessage = event => {
this._zone.run(() => {
observer.next(event);
});
eventSource.onerror = error => {
this._zone.run(() => {
observer.error(error);
});
});
private getEventSource(url: string): EventSource {
if (this.isBrowser) {
console.log('this happened here 2');
return new EventSource(url);
this is the implementation.
ngOnInit() {
this.sseService
.getServerSentEvent('http://localhost:4201/user/stream')
.subscribe(data => console.log('data here >>>>> ', data));
I am trying to implement this.ngZone.runOutsideAngular(() => {
instead
If I don't use the browser detection hack then I get this error. FYI, I don't know why I am calling it a hack but it something about it smells.
ERROR ReferenceError: EventSource is not defined
@xtianus79 Okay, so I'm not quite sure if you need the runOutsideAngular if you are using server sent events. But if you do, I would think that this might work (just a guess, it's long ago that I implemented my solution and I don't know SSE)
getServerSentEvent(url: string): Observable<EventSource> {
return Observable.create(observer => {
this._zone.runOutsideAngular(() => {
const eventSource: EventSource = this.getEventSource(url);
eventSource.onmessage = event => {
this._zone.run(() => {
observer.next(event);
});
eventSource.onerror = error => {
this._zone.run(() => {
observer.error(error);
});
});
});
The isPlatformBrowser
function is not a hack at all. I use it all the time. And especially in your use case it makes sense because EventSource
does only exist in the browser and not in node.js. Therefore you need to make that distinction.
But I think you should return an EventSource mock from getEventSource
if you are on the server otherwise const eventSource
would be undefined and eventSource.onmessage
would fail.
I'm using Angular SSR with ASP.Net using SPA Services. Application became irresponsive when i pass 1K concurrent request using apache JMeter or WebSurg. Most of the time connection timeout shows up or application took more then 120 secs to load the home page.
It would be very helpful if someone could suggest a possible solution
Simply we can check for browser
and initilize the socket
things
import { isPlatformBrowser } from '@angular/common';
import { Inject, PLATFORM_ID } from '@angular/core';
constructor(@Inject(PLATFORM_ID) private platformId: Object) {
if(isPlatformBrowser(this.platformId)) { // check if this is runing in browser
this.socket = io('http://localhost:3000/'); // call io constructor
this.init(); // do the all initilization, e.g. this.socket.on('message', msg => {})
work fine for me
Simply call WebSocket only in the browser mood. Follow the answer.
https://stackoverflow.com/a/41721980/5978179
Here is my socket Service
import { Inject, Injectable, PLATFORM_ID } from '@angular/core';
import { io } from 'socket.io-client';
import { Observable } from 'rxjs';
import { isPlatformBrowser } from '@angular/common';
@Injectable({
providedIn: 'root'
export class SocketServiceService {
isBrowser: boolean | undefined;
socket: any;
// uri: string = "ws://localhost:3000";
uri: string = "http://localhost:3000";
constructor(@Inject(PLATFORM_ID) platformId: Object) {
this.isBrowser = isPlatformBrowser(platformId);
if (this.isBrowser) {
this.socket = io(this.uri);
listen(eventName: string) {
return new Observable((subscriber) => {
this.socket.on(eventName, (data: any) => {
subscriber.next(data);
emit(eventName: string, data: any) {
this.socket.emit(eventName, data);
close() {
this.socket.close();
This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.
Read more about our automatic conversation locking policy.
This action has been performed automatically by a bot.