添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

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.