ngxs / store

🚀 NGXS - State Management for Angular
http://ngxs.io
MIT License
3.54k stars 402 forks source link

[FR] SSR #465

Closed John0x closed 5 years ago

John0x commented 6 years ago

Feature Request

It would be great to have some builtin way to handle SSR or at least have a section in the docs for it

amcdnl commented 6 years ago

I have no experience with SSR, can you write this up for the community?

odahcam commented 6 years ago

Basically you gotta polyfill or check if browser specific things exists before start using them, like the localStorage, it is not available in the server and throws an error when running in the server. The NgxsStoragePluginModule is one guy who breaks SSR because of localStorage usage.

odahcam commented 6 years ago

For example, in these lines: https://github.com/ngxs/store/blob/b42b4a405a3b11fbdf4243c4a3821228c069477d/packages/storage-plugin/src/storage.module.ts#L23-L31

We should verify if both localStorage and sessionStorage are defined before returning any, otherwise we get errors like that:

$ yarn serve:universal
yarn run v1.7.0
$ cd dist && node server
Node Express server listening on http://localhost:4000
ReferenceError: localStorage is not defined
    at engineFactory (\path\to\my\project\Havan.Vc.Interface.PWA\dist\server.js:228592:9)
    at _callFactory (\path\to\my\project\Havan.Vc.Interface.PWA\dist\server.js:12348:20)
    at _createProviderInstance (\path\to\my\project\Havan.Vc.Interface.PWA\dist\server.js:12304:26)
    at initNgModule (\path\to\my\project\Havan.Vc.Interface.PWA\dist\server.js:12234:32)
    at new NgModuleRef_ (\path\to\my\project\Havan.Vc.Interface.PWA\dist\server.js:12960:9)
    at Object.createNgModuleRef (\path\to\my\project\Havan.Vc.Interface.PWA\dist\server.js:12949:12)
    at NgModuleFactory_.create (\path\to\my\project\Havan.Vc.Interface.PWA\dist\server.js:15491:25)
    at \path\to\my\project\Havan.Vc.Interface.PWA\dist\server.js:8221:43
    at ZoneDelegate.invoke (\path\to\my\project\Havan.Vc.Interface.PWA\dist\server.js:533:26)
    at Object.onInvoke (\path\to\my\project\Havan.Vc.Interface.PWA\dist\server.js:7732:33)
odahcam commented 6 years ago

Workaround for localStorage in server-side:

app.module

// app.module

// @angular
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

// @env
import { environment } from '@env/environment';

// State management
import { NgxsModule } from '@ngxs/store';
import { NgxsStoragePluginModule } from '@ngxs/storage-plugin';
import { NgxsLoggerPluginModule } from '@ngxs/logger-plugin';
import { NgxsReduxDevtoolsPluginModule } from '@ngxs/devtools-plugin';

import { AppComponent } from '@app/app.component';

import { AppSettingsState } from '@shared/state/app-settings.state';

/**
 * Main Module class.
 */
@NgModule({
  declarations: [

    // Components

    AppComponent,

    // ...

  ],
  imports: [

    // Application
    BrowserModule.withServerTransition({ appId: 'com.myapp' }),

    // State management
    NgxsModule.forRoot([
      AppSettingsState
    ]),
    NgxsStoragePluginModule.forRoot(),
    NgxsLoggerPluginModule.forRoot({
      disabled: environment.production
    }),
    NgxsReduxDevtoolsPluginModule.forRoot({
      name: 'MY_APP_STORE',
      disabled: environment.production
    }),

    // ...

  ],
  providers: [
    // ...
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

app.server.module

// app.server.module

import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';

import { AppModule } from '@app/app.module';

import { STORAGE_ENGINE } from '@ngxs/storage-plugin';

import { LOCAL_STORAGE } from '@shared/local-storage.token';

import { LocalStoragePolyfill } from '@shared/local-storage.polyfill';

@NgModule({
  imports: [
    // The AppServerModule should import your AppModule followed
    // by the ServerModule from @angular/platform-server.
    AppModule,
    ServerModule,
    // ...
  ],
  providers: [

    // ...

    // replaces NGXS storage engine on server
    { provide: STORAGE_ENGINE, useClass: LocalStoragePolyfill }, 

    // you can also create a token for yourself
    { provide: LOCAL_STORAGE, useClass: LocalStoragePolyfill }, <-- PROVIDE YOUR CUSTOM LOCAL STORAGE POLYFILL
  ],
  // Since the bootstrapped component is not inherited from your
  // imported AppModule, it needs to be repeated here.
  bootstrap: [AppComponent],
})
export class AppServerModule { }

LocalStoragePolyfill

export class LocalStoragePolyfill {

  private _data = {};

  constructor() {
    this._data = {};
  }

  setItem(id, val: string) {
    return this._data[id] = String(val);
  }

  getItem(id) {
    return this._data.hasOwnProperty(id) ? this._data[id] : null;
  }

  removeItem(id) {
    delete this._data[id];
  }

  clear() {
    return this._data = {};
  }
}
Gorniv commented 6 years ago

https://github.com/Angular-RU/angular-universal-starter/tree/ngxs - problem with HTTP transfer state - we send get HTTP request twice(server+browser).

clark0x commented 6 years ago

@Gorniv you should use TransferHttpCacheModule to avoid duplicated requests between server and browser. Check this https://github.com/angular/universal/blob/master/docs/transfer-http.md

BTW, make sure to import this module before any other interceptor which may change req.url, or it won't work.

Gorniv commented 6 years ago

@clarkorz problem isn't in HTTP. ssr dosn't wait complete store.dispatch(new Action()); https://ssr.angular.su/back (delay 3 sec for http get) and https://github.com/Angular-RU/angular-universal-starter/blob/dc632fbc46278d1486346dc432ff17a982b6fc09/src/app/transfer-back/transfer-back.component.ts#L21 work good.

Gorniv commented 6 years ago

any idea?

odahcam commented 6 years ago

Not from me this time. :/

squelix commented 5 years ago

Is there a solution to make it works today ?

squelix commented 5 years ago

There is not only this problem, when an action is dispatched, the universal renderer doesn't wait the end of this action to render..... So a basic init dispatch doesn't work and I tried a lot of solutions.... NgRx works with SSR, and I don't know what to do...

splincode commented 5 years ago

You can try: npm i @ngxs/store@dev -D Is the problem solved?

squelix commented 5 years ago

Yes it solved, the problem :) Nice thank you :)

wunaidage commented 5 years ago

is there other way to fix the localstorage error other that provide local storage polyfill?

splincode commented 5 years ago

@wunaidage open a new issue with reproduce

adamgasiorek commented 4 years ago

There is not only this problem, when an action is dispatched, the universal renderer doesn't wait the end of this action to render..... So a basic init dispatch doesn't work and I tried a lot of solutions.... NgRx works with SSR, and I don't know what to do...

This still not working...

splincode commented 4 years ago

@call-me-adas create repo for reproduce

We have integration test with SSR in @ngxs/store repo and it works