Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

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

Providing a DI token in commonEngine.render() does not provide its value to application #26323

Closed
Ozzcer opened this issue Nov 10, 2023 · 143 comments · Fixed by #28463
Closed

Providing a DI token in commonEngine.render() does not provide its value to application #26323

Ozzcer opened this issue Nov 10, 2023 · 143 comments · Fixed by #28463

Comments

@Ozzcer
Copy link

Ozzcer commented Nov 10, 2023

Which @angular/* package(s) are the source of the bug?

Don't known / other

Is this a regression?

Yes

Description

Prior to upgrading to v17 our application passed express' req/res as REQUEST/RESPONSE DI Tokens through the render function of the engine in server.ts. Since upgrading to v17 from v16 these DI Token values are always null.

This can be seen in the minimal reproduction where all three of APP_BASE_HREF / REQUEST / RESPONSE are null in the console output while running 'ng serve'.

If using @angular-devkit/build-angular:browser instead of @angular-devkit/build-angular:application this problem does not occur. I assume there is a different way to provide dependencies from server.ts now but I have not been able to find any updated documentation on it.

Please provide a link to a minimal reproduction of the bug

https://github.com/Ozzcer/angular-ssr-di-issue

Please provide the exception or error you saw

Console outputs null for all DI Tokens provided in server.ts

Please provide the environment you discovered this bug in (run ng version)

Angular CLI: 17.0.0
Node: 18.17.1
Package Manager: npm 9.6.7
OS: linux x64

Angular: 17.0.2
... animations, common, compiler, compiler-cli, core, forms
... platform-browser, platform-browser-dynamic, platform-server
... router

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1700.0
@angular-devkit/build-angular   17.0.0
@angular-devkit/core            17.0.0
@angular-devkit/schematics      17.0.0
@angular/cli                    17.0.0
@angular/ssr                    17.0.0
@schematics/angular             17.0.0
rxjs                            7.8.1
typescript                      5.2.2
zone.js                         0.14.2

Anything else?

No response

@Ozzcer Ozzcer changed the title Providing a DI token in commonEngine.render() does not provide this value Providing a DI token in commonEngine.render() does not provide its value to application Nov 10, 2023
@JeanMeche JeanMeche transferred this issue from angular/angular Nov 10, 2023
@JeanMeche
Copy link
Member

Hi, have a look at #26175 which updated the schematics to import create the tokens that aren't exported anymore.
You will need to provide the tokens yourself.

@Ozzcer
Copy link
Author

Ozzcer commented Nov 10, 2023

Hi @JeanMeche , tokens are defined under express.tokens.ts in minimal reproduction (https://github.com/Ozzcer/angular-ssr-di-issue/blob/c08049cc39e2993eff903c29902826a1aa1f7d10/src/express.tokens.ts), this file was created automatically by 'ng update' in our original project and I copied the file over to the minimal reproduction.

Also APP_BASE_HREF is coming through as null and I thought that didn't need to be manually defined.

@alan-agius4 alan-agius4 added needs: more info Reporter must clarify the issue and removed needs: more info Reporter must clarify the issue labels Nov 10, 2023
@alan-agius4
Copy link
Collaborator

This is expected as for ng serve the server.ts is not used.

Whilst we can provide an optional request token, this will be not be same interface of the Express tokens as the dev-server does not use express. Also the mentioned tokens will not available when using prerendering.

The recommended approach to treat the tokens set in server.ts as optional.

@Ozzcer
Copy link
Author

Ozzcer commented Nov 10, 2023

Ah ok, got it working now, I have had to run build and run server.mjs independently. Is there a way to get the dev-ssr-server to work with the application output / is there an equivalent builder available for application output? Or do we still need to use dev-ssr-server with seperate server + browser builder instead of application? The current documentation is quite confusing as it appears like running the application builder through ng serve should allow local ssr rendering

@alan-agius4
Copy link
Collaborator

ng serve does allow SSR, but it does not use the server.ts, Instead it has it's own middlewares and server instance. You should definitly update the docs to make this clear.

@SaeedDev94
Copy link

SaeedDev94 commented Nov 11, 2023

ng serve does allow SSR, but it does not use the server.ts, Instead it has it's own middlewares and server instance. You should definitly update the docs to make this clear.

This is a serious issue IMO, we should be able to inject REQUEST and RESPONSE with ng serve
How we can debug our app then ?
By building and running server.mjs in dist folder ?!
So server.ts is used only for production ??
Or am I missing something ??
I will continue using old ssr-dev-server for now
(I remember that in v16 and below, server.ts was used for both development and production. Why shouldn't it be the same as before? Or if we can use server.ts for both envs + esbuild, how can we do that?)

@Enngage
Copy link

Enngage commented Nov 12, 2023

I've been confused by this as well. I was hoping there is an alternative way for ssr-dev-server in the application builder so that we can both develop the app, have it rebuild on the fly and just use easily.

Since the server.ts is not used for ng serve, it also means that none of the custom API routes that are defined are registered. What is the recommended approach here?

@eneajaho
Copy link
Contributor

eneajaho commented Nov 13, 2023

One other use-case where running server.ts in dev mode (ng serve) would be to be able to change providers.

Ex. In browser I want to use normal assets loading, while in SSR mode I want to be able to read from file-system and inline icons for better perf.
Usecase: https://github.com/push-based/ngx-fast-svg#ssr-usage

I don't see any way to make that work with the current setup.

@SaeedDev94
Copy link

SaeedDev94 commented Nov 14, 2023

Imagine we created a fresh new (SSR) project with latest version of @angular/cli and we want to make sure that angular ssr server returns 404 HTTP Status Code instead of 200 for unregistered routes or we want to redirect a path to another based on some conditions only, so we should set 301 HTTP Status Code and ...
We definitely need server response object to set the right HTTP Status Code (and also request for other use cases)
My question is: How we can do it in development environment ?!

@oriollpz

This comment was marked as spam.

@Ozzcer
Copy link
Author

Ozzcer commented Nov 17, 2023

Not a fix for the issue but if you want to use esbuild found there is a browser-esbuild builder you can drop into an existing angular.json build. If, like myself, you do most development without SSR and then just test in SSR this is an alright method of getting the esbuild benefits in development, but it does warn that its temporary so hopefully before its removed we will be able to use application builder.

@mkurcius
Copy link

mkurcius commented Nov 17, 2023

I have ran into same issue (confusion). I generated fresh app using angular 17, with --ssr option.
Everything looks nice, ng serve was working, until I have a need to create new endpoint in server.ts.
To my surprise my endpoint was not registered, and I was getting ERROR RuntimeError: NG04002: Cannot match any routes. URL Segment: 'api/user'
Initially I though I made a mistake and start trying to fix it, and after sometime of failure after failure, I start realizing maybe server.ts was not part of ng serve?

It was confusing and I waste time trying to fix it.

It was super unintuitive, I had fresh app, generated by CLI, I've run app using ng serve which used default (generated) configuration and to found out server entrypoint was not working 🤯

@SaeedDev94
Copy link

SaeedDev94 commented Nov 18, 2023

One of the request use cases: (user authentication)
Getting the cookie from browser request and add it to api request for protected pages like user profile (with interceptor) + allowing cookie header (angular/angular#15730)
Now without request object we can't do that in dev env (SSR)
And imagine if we already have a guard for the routes !! we can't enter the user profile ...

@FranzStrudel
Copy link

I wasted so much time because it was indeed rendered server side but yet the request provider was empty.

The changelog of the last version (adding node run dist/xxx) put me on the path to this issue.

So confusing behavior.

It has to be at least clearly documented on the getting started page...

@zygarios
Copy link

In reference to my closed but related thread, I believe that the lack of use of server.ts in development mode is a very large regression. There are often things that we need to change there and be able to debug based on the RESPONSE and REQUEST objects. The configuration of this and the work on SSR has unnecessarily become more complicated. I hope that this will be somehow sensibly resolved because in its current form it is impossible for me to work with it on a daily basis and I am forced to use the classic approach in the Angular 16 version.

@oriollpz
Copy link

How do they expect to work authentication with cookies without access to REQUEST RESPONSE from the server.ts?

@osnoser1
Copy link

I agree, in development mode, we should be able to get the request object to do, for example, authentication through the cookie.

This is a breaking change between v16 and v17, and I don't know if it was documented.

santoshyadavdev pushed a commit to uiuniversal/ngu-carousel that referenced this issue Nov 26, 2023
Hello @santoshyadavdev,

This PR fixes #476.

What I did:

- I couldn't use `yarn` at all this time, I switched to `npm` (even
after everything was updated with `npm` and switching back to `yarn`)
- I updated all packages to latest versions (except `jasmine-core`)
- The nx cache is now located in the root folder of the repo, I added
`.nx` in `.gitignore`
- There was some problem with how the tsconfigs were done, I fixed all
of them (it was causing problems with nx migrations)
- I applied the migrations from nx
- I migrated `ngu-carousel-example` to use the new control-flow
  - I did not update the `README.md`
- I removed the cypress component testing config from
`ngu-carousel-example` as it was more or less useless
- I fixed errors found by the linter
- I fixed unit tests
- I removed `updateBuildableProjectDepsInPackageJson` from
`ngu-carousel` as it was deprecated
- removing it keeps the same behavior as if it was enable, that's what
we want
- I added missing `tsconfig.json` (cypress) in the `.eslintrc.json` of
`ngu-carousel`
- I updated the current SSR setup
- I first tried to migrate the new `application` builder, but it was not
working properly with modules. ~~I will investigate later if it comes
from `nx` or the `Angular CLI`~~ (thanks @JeanMeche for his help)
  - Could be related to angular/angular#52998
  - As said in https://riegler.fr/blog/2023-10-13-v17-builders: 
- `An interesting point to note is that if either 'ssr' or 'prerender'
are set to 'true' in the configuration file, the pages served with 'ng
serve' will be generated server side.`
- This was not working, that's one of the reason I kept the same setup
with `ssr-dev-server` & co.
- I already did the migration to `browser-esbuild` when I migrated to
`Angular 16`, so it is already using esbuild.
- Another reason is that `ng serve` using the ssr is not using
`server.ts` (which should not affect this repo, but let's play it safe),
track angular/angular-cli#26323
- I cleaned `polyfills` usage
- I updated the `CI` setup
- Angular supports node starting version `18.13.0`, the CI now runs on
`['18.13.0', '20']`
  - Updated to `actions/checkout@v4` & `actions/setup-node@v4`
  - Updated to use `npm`
- I bumped `ngu-carousel` for `Angular 17`
- I bumped `ngu-carousel` to `9.0.0` and I updated `README.md`

TODO on your side:

- Release a new major version `9.0.0`
- Close branches related to package update

Tell me if everything looks ok for you.

Have a nice day.
@mrtariqsaeed
Copy link

I am at a loss for words. I spent last 3 days trying to fix this. Request and response are not passed NOT even in production, and this is almost 1 year ago this issue? And they still haven't done anything about it? It does not surprise me that almost everyone is moving to react

It works fine, but you need to disable prerendering and not use ng serve. You actually don't need to use ng serve because running node --watch server.mjs does the same job.

The way I see it there's nothing to fix here, that's probably why it's taking that long - this is the way prerendering is supposed to work (in any framework, not just angular) - you serve static html - you can't have REQUEST/RESPONSE/etc change your output for different users. It would be great if it turns out there's actually some way around that, but as of now, I don't see it.

In order to run node server.mjs I have to build the application first which I can't do because of the same issue No provider for request. Is there a way to get the application to build?

@create-signal
Copy link

In order to run node server.mjs I have to build the application first which I can't do because of the same issue No provider for request. Is there a way to get the application to build?

Assuming you don't want to prerender your site, you can set "prerender": false in angular.json under projects->app-name->architect->build->options

@Roman-Simik
Copy link

Sooo can we get someone from Core team to answer this how and when they are going to address it? And sorry for being rude - as I said previously providing the REQUEST/RESPONSE tokens is just not enough...

@eneajaho
Copy link
Contributor

eneajaho commented Aug 7, 2024

Sooo can we get someone from Core team to answer this how and when they are going to address it? And sorry for being rude - as I said previously providing the REQUEST/RESPONSE tokens is just not enough...

There is ongoing work to solve this issue here: #28139

Basically what was talked about in the RFC angular/angular#56785

@Grenghis-Khan
Copy link

Grenghis-Khan commented Aug 9, 2024

I've been following this thread and thank you to all who have suggested work arounds. I am able to get DI tokens from server.ts to my app using InjectionTokens, "prerender": false,, npm run build and npm run serve:ssr:my-project-name but only if I use the server's IP Address in the URL to load the page, if I use localhost my tokens value show null.

Does anyone know how to solve this? I'm wanting to use Firebase's App Hosting to host the project so can't really have the server listen on a fixed IP Address.

Thanks

@Grenghis-Khan
Copy link

Grenghis-Khan commented Aug 16, 2024

I've been following this thread and thank you to all who have suggested work arounds. I am able to get DI tokens from server.ts to my app using InjectionTokens, "prerender": false,, npm run build and npm run serve:ssr:my-project-name but only if I use the server's IP Address in the URL to load the page, if I use localhost my tokens value show null.

Does anyone know how to solve this? I'm wanting to use Firebase's App Hosting to host the project so can't really have the server listen on a fixed IP Address.

Thanks

In case it helps anyone, I found the solution to my problem. Angular was having the browser cache the server rendered page so my tokens wouldn't repopulate on page loads/reloads. To fix this I had to add "navigationRequestStrategy": "freshness" to the end of the json object in the ngsw-config.json file. Not the best performance fix but at least I always get my tokens now until Angular fixes the token issue. Now I have my tokens in development mode regardless of visiting localhost or the server's IP address. I also uploaded the project to Firebase's App Hosting and it is functioning as intended there as well!

@ellyx-csg
Copy link

Any workaround for this in angular 18?

@SaeedDev94
Copy link

Any workaround for this in angular 18?

This will fix in v19.0.0 3c9697a (previously the team said ssr fix come with v18.0.0 🙄)
unfortunately 1 year is a huge amount of time 🤦🏻

@PH-Lars
Copy link

PH-Lars commented Aug 21, 2024

@humanely
Copy link

humanely commented Sep 3, 2024

No fixes yet.

@Jhereck
Copy link

Jhereck commented Sep 19, 2024

Hi any news on that pb pls ?

Already spend a whole day to search a way to make providers / DI token from SSR server work in client without results : tried with v18.2.5 AND v19.0.0-next.7 (new project to try with the new version), same (no) results.

My setup :

Tokens :

import {InjectionToken} from '@angular/core';
import {Request, Response} from 'express';

export const REQUEST = new InjectionToken<Request>('REQUEST');
export const RESPONSE = new InjectionToken<Response>('RESPONSE');
export const SERVER_TOKEN = new InjectionToken<string>('ServerToken');

Server :

server.get('**', express.static(browserDistFolder, {
    maxAge: '1y',
    index: 'index.html',
  }));

  // All regular routes use the Angular engine
  server.get('**', (req, res, next) => {
    const { protocol, originalUrl, baseUrl, headers } = req;

    commonEngine
      .render({
        bootstrap,
        documentFilePath: indexHtml,
        url: `${protocol}://${headers.host}${originalUrl}`,
        publicPath: browserDistFolder,
        providers: [{ provide: APP_BASE_HREF, useValue: baseUrl },
			{ provide: REQUEST, useValue: req },
          { provide: RESPONSE, useValue: res },
			{provide: SERVER_TOKEN, useValue: 'TEST TEST'}],
      })
      .then((html) => res.send(html))
      .catch((err) => next(err));
  });

  return server;
}

Component :

export class AppComponent implements OnInit, OnChanges {
  title = 'angular101';

  req!: any

  constructor(@Inject(PLATFORM_ID) private platformId: any,
              @Inject(REQUEST) @Optional() request: Request,
			  @Inject(SERVER_TOKEN) @Optional() serverToken: String,
			  @Inject(APP_BASE_HREF) @Optional() appHref: string,) {

    if (isPlatformServer(this.platformId)) {
      console.log('SERVER')

		this.req = request.headers
      console.log('REQUEST : ', request.headers)
		console.log('appHref : ', appHref)

		console.log('serverToken : ', serverToken)


    } else {
      console.log('CLIENT')
      console.log('REQUEST : ', request?.headers)
		console.log('appHref : ', appHref)
		console.log('serverToken : ', serverToken)
    }

I get results in server console, but nothing (null / undefined) in browser console.

It seems that providers are not transferred to the client, like many others already told.

Any help would be really appreciated !

@aaubry
Copy link

aaubry commented Sep 20, 2024

I use a workaround that is not applicable in every situation but may help someone else. This is not a generic solution but allows to read specific headers from the request. In this case it is the cookie, but it should work with other headers as well.

I have a web server that acts as a reverse proxy for the application. I configured that reverse proxy to take the "Cookie" header and pass it as a query string parameter. I the access the cookie by parsing the URL from PlatformLocation.

This is the reverse proxy configuration that I use, in this case for IIS. It should be fairly easy to reproduce this with other servers such as nginx.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <location path="." inheritInChildApplications="false">
        <system.webServer>
            <rewrite>
                <rules>
                    <!-- Remove the 'cookie' parameter from the query string for resources and the vite server -->
                    <rule name="Reverse Proxy new site (files and vite)" stopProcessing="true">
                        <match url="^([^?]+(?:\.\w+|vite/client))" />
                        <action type="Rewrite" url="http://localhost:4200/{R:1}" />
                    </rule>

                    <rule name="Reverse Proxy new site (ws)" stopProcessing="true">
                        <match url="^$" />
                        <action type="Rewrite" url="http://localhost:4200/" />
                    </rule>

                    <!-- Inject the cookie header as a query string parameter -->
                    <rule name="Reverse Proxy new site and inject cookie" stopProcessing="true">
                        <match url="^(.*)" />
                        <conditions>
                            <add input="{HTTP_COOKIE}" pattern="(.+)" />
                        </conditions>
                        <action type="Rewrite" url="http://localhost:4200/{R:1}?cookie={C:1}" />
                    </rule>

                </rules>
            </rewrite>
        </system.webServer>
    </location>
</configuration>

To access the cookie, I use the following code:

const cookiePrefix = "cookie=";
const location = inject(PlatformLocation);
const queryString = location.href.split("?")[1];
if (queryString) {
    const cookieParam = queryString.split("&").filter(p => p.startsWith(cookiePrefix))[0];
    if (cookieParam) {
        cookieString = decodeURIComponent(cookieParam.substring(cookiePrefix.length));
    }
}

Of course that code is only used in development and on the server-side. I use an environment parameter to know which environment the code is running on, and also check isPlatformServer.

@0ba100
Copy link

0ba100 commented Sep 24, 2024

Thanks angular team

@fluoxa
Copy link

fluoxa commented Sep 24, 2024

Thank you very much

@shady-ahmed-bdr
Copy link

angular would be on fire with these updates
thank you angular team for the effort.
btw when we will be able to use it next patch ? or V19 ?

@alan-agius4
Copy link
Collaborator

alan-agius4 commented Sep 24, 2024

This will only available in v19, which is currently in pre-release.

@zygarios
Copy link

Thank you! Finally we'll be able to migrate 💪

@kewur
Copy link

kewur commented Sep 24, 2024

are there any changes we need to do to get this working on our side?

@humanely
Copy link

How does this work? Is there a demo script?

@Jhereck
Copy link

Jhereck commented Sep 24, 2024

Probably available in next v19 release !

@shady-ahmed-bdr
Copy link

shady-ahmed-bdr commented Sep 25, 2024

Probably available in next v19 release !

https://github.com/JeanMeche/ssr-v19
from a post on mgechev page

@angular-automatic-lock-bot
Copy link

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.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Oct 26, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.