angular / universal

Server-side rendering and Prerendering for Angular
MIT License
4.04k stars 484 forks source link

"window is not defined" when overwriting window dependent providers in AppServerModule #1286

Closed magnattic closed 4 years ago

magnattic commented 4 years ago

Bug Report

What is the expected behavior?

I have providers in my AppModule that access window. When I overwrite those providers in the AppServerModule, I expect that there will be no errors during npm run serve:ssr.

What is the current behavior?

This worked in the past version, but does not work anymore in Angular 9. The following errors occurs when I build and run the code as seen below.

ReferenceError: window is not defined at Object.ZAI4 (/home/dominik-linux/code/play/ng9-ssr/dist/ng9-ssr/server/main.js:1:1296223) at webpack_require (/home/dominik-linux/code/play/ng9-ssr/dist/ng9-ssr/server/main.js:1:295) at Object.24aS (/home/dominik-linux/code/play/ng9-ssr/dist/ng9-ssr/server/main.js:1:675605) at webpack_require (/home/dominik-linux/code/play/ng9-ssr/dist/ng9-ssr/server/main.js:1:295) at Object.K011 (/home/dominik-linux/code/play/ng9-ssr/dist/ng9-ssr/server/main.js:1:1131614) at webpack_require (/home/dominik-linux/code/play/ng9-ssr/dist/ng9-ssr/server/main.js:1:295) at Object.uj+Y (/home/dominik-linux/code/play/ng9-ssr/dist/ng9-ssr/server/main.js:1:2278765) at webpack_require (/home/dominik-linux/code/play/ng9-ssr/dist/ng9-ssr/server/main.js:1:295) at Object.0 (/home/dominik-linux/code/play/ng9-ssr/dist/ng9-ssr/server/main.js:1:2962) at __webpack_require__ (/home/dominik-linux/code/play/ng9-ssr/dist/ng9-ssr/server/main.js:1:295)

The error shows that window is being accessed in the AppModule providers, although I have overwritten those providers. So I would expect that the providers should not be called.

What modules are related to this issue?

- [ ] aspnetcore-engine
- [ ] common
- [ ] express-engine
- [ ] hapi-engine
- [ ] module-map-ngfactory-loader

Minimal reproduction with instructions:

npm i -g @angular/cli@next

ng new ng9-ssr && cd ng9-ssr ng add @nguniversal/express-engine@next --clientProject ng9-ssr

Add the following code:

local-storage.ts
import { InjectionToken } from '@angular/core';

export const LOCAL_STORAGE = new InjectionToken<Storage>('localStorage');
app-module.ts
[..]
  providers: [{ provide: LOCAL_STORAGE, useValue: window.localStorage }],
[..]
app-server-module.ts
const fakeStorage: Storage = {
  length: 0,
  clear: () => {},
  getItem: (_key: string) => null,
  key: (_index: number) => null,
  removeItem: (_key: string) => {},
  setItem: (_key: string, _data: string) => {}
};

[..]
  providers: [{ provide: LOCAL_STORAGE, useValue: fakeStorage }],
[..]

npm run build:ssr npm run serve:ssr

What is the use-case or motivation for changing an existing behavior?

I want to write consistent code in both browser and SSR when accessing stuff like LocalStorage or window without using if/else.

Environment:

@nguniversal versions

Package Version

@angular-devkit/architect 0.900.0-next.11 @angular-devkit/build-angular 0.900.0-next.11 @angular-devkit/build-optimizer 0.900.0-next.11 @angular-devkit/build-webpack 0.900.0-next.11 @angular-devkit/core 9.0.0-next.11 @angular-devkit/schematics 9.0.0-next.11 @ngtools/webpack 9.0.0-next.11 @nguniversal/common 9.0.0-next.5 @nguniversal/express-engine 9.0.0-next.5 @schematics/angular 9.0.0-next.11 @schematics/update 0.900.0-next.11 rxjs 6.5.3 typescript 3.5.3 webpack 4.41.1



#### Is there anything else we should know?
alan-agius4 commented 4 years ago

The above code should have always thrown an exception since you are using a "variable" which doesn't exists, irreverent if the provider is overridden or not since you are referencing a variable which doesn't exist and hence it will throw an exception during the parsing phase.

There are three ways how you can make this work; 1) You can define a global window mock. Example in your server.ts you have global['window'] = {}; 2) Instead of useValue use useFactory and wrap the variable in an arrow function Ex:

{ provide: LOCAL_STORAGE, useFactory: () => window.localStorage }

3) Verify that the window exists

{ provide: LOCAL_STORAGE, useValue: typeof window !== 'undefined' && window.localStorage }
magnattic commented 4 years ago

Thanks, I landed on the useFactory solution.

angular-automatic-lock-bot[bot] commented 4 years ago

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.