angular / angular-cli

CLI tool for Angular
https://cli.angular.io
MIT License
26.72k stars 11.98k forks source link

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

Open Ozzcer opened 10 months ago

Ozzcer commented 10 months ago

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

BeCaDRI commented 6 months ago

@alan-agius4 https://github.com/BeCaDRI/angular-ssr-demo Checkout

npm i ng build npm run serve:ssr:angular_ssr

Open http://localhost:4000/?email=test@test.com&name=user

Check node console => queryparams are there Check Browser DEV Consolte => queryparams are null

alan-agius4 commented 6 months ago

Hello @BeCaDRI, it seems this isn't the appropriate issue for addressing your concern. Nevertheless, according to your reproduction, the functionality appears to be working correctly as expected because QUERYPARAMS is configured solely within the server application. Additionally, it's advisable to utilize the router for accessing query parameters in general: https://angular.io/api/router/ActivatedRoute

BeCaDRI commented 6 months ago

@alan-agius4 Router Params are simply used to have some input example (and it does not affect how we want to inject data from the server to the client). And the idea was to inject some token which is used by the node server to get detailed data from another backend before rendering the page (based on the query params/other calls). Or it is a general misunderstanding, that the rendering is not done at runtime?

So how is it possible to use providers in production? Target is to provide data from the server while using SSR without using an extra API call.

I still don't see why this is not an issue, other users have the same problem https://github.com/angular/angular-cli/issues/26323#issuecomment-1859954143

As the documentation is non-existent for SSR providers for Angular 17 and any idea I have found here or on the Internet so far hasn't worked it is frustrating to get a simple task done.

Sorry for poking around here, but if there is a solution I don't see/find it Thanks for feedback anyway!

alan-agius4 commented 6 months ago

@BeCaDRI, DI and provider tokens values will not be sent from the server to the client. DI functions identically on both the server and client applications, so there's no specific documentation for server-side DI.

For data transfer from the server to the browser application, you should utilize the TransferState API.

BeCaDRI commented 6 months ago

@alan-agius4 Transferstate was my first approach which didn't work either. I'll try again, thanks all.

andrewatwood commented 6 months ago

I think the quickest path forward that would unblock a majority of people is injecting Request properly during the Vite builder SSR, as this is how most apps are going to be accessing cookies, and cookies are basically the only way to do any sort of authenticated SSR.

Beyond that, even if full custom server.ts support isn't feasible at this time, registering custom middlewares in some way would handle basically every other use case.

Side note @alan-agius4 as you may have some insight here: I would work on a PR for this myself but I cannot run an clean npm install in the parent workspace of the CLI repo or the build_angular package without either peer dep errors or a missing package.json that the installer is trying to grab from the cache.

hittten commented 6 months ago

Indeed, several things about SSR need to be documented, for example since the move from @nguniversal/express-engine to @angular/ssr the express tokens were lost:

https://github.com/angular/universal/blob/e798d256de5e4377b704e63d993dc56ea35df97d/modules/express-engine/tokens/injection-tokens.ts

I had to search the internet for a while to understand that I had to add the "src/express.tokens.ts" file now because it no longer comes in the @angular/ssr package, so I wrote the file manually and found another problem:

The REQUEST and RESPONSE are undefined when you inject:
@Optional() @Inject(RESPONSE) private response: Response

I have published a repo with the minimum to recreate the SSR problem with angular 17: https://github.com/hittten/angularSSR17

Clone & Setup:

git clone https://github.com/hittten/angularSSR17
npm install
npm start

Log

Navigate to http://localhost:4200/404

ERROR TypeError: Cannot read properties of null (reading 'status')
    at _NotFoundPageComponent (/Workspace/hittten/angularSSR17/src/app/pages/not-found-page/not-found-page.component.ts:17:21)

Angular 16 code (working):

import {RESPONSE} from "@nguniversal/express-engine/tokens";
import {Response} from "express";

constructor(
    @Optional() @Inject(RESPONSE) private response: Response,
    @Inject(PLATFORM_ID) private platformId: object,
  ) {
    if (isPlatformServer(this.platformId)) {
      this.response.status(404)
    } else {
      console.error(`response status code 404`)
    }
  }

Angular 17 code (not working):

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

const REQUEST = new InjectionToken<Request>('REQUEST');
const RESPONSE = new InjectionToken<Response>('RESPONSE');

constructor(
    @Optional() @Inject(RESPONSE) private response: Response,
    @Inject(PLATFORM_ID) private platformId: object,
  ) {
    if (isPlatformServer(this.platformId)) {
      this.response.status(404)
    } else {
      console.error(`response status code 404`)
    }
  }

Info

basically I have created a project like this:

npx @angular/cli@17.0.8 new angularSSR17 --ssr --routing --scss --skip-tests
cd angularSSR17
npm i -D firebase-tools@13.0.2
ng g c pages/homePage
ng g c pages/aboutPage
ng g c pages/notFoundPage

I also disable pre-render in angular.json:

{
    "scripts": [],
    "server": "src/main.server.ts",
    "prerender": false,
    "ssr": {
      "entry": "server.ts"
    }
}

and I have done the settings of the routes for lazy load. I have not installed @angular/fire because it is not necessary to recreate the SSR error.

And...

I also intent to provide express tokens in /server.ts: provide: REQUEST, useValue: req }, { provide: RESPONSE, useValue: res }, which is not necessary to do in angular 16, but it doesn't work either.

  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 },
        ],
      })
      .then((html) => res.send(html))
      .catch((err) => next(err));
  });

Versions

Angular CLI: 17.0.8
Node: 20.10.0
Package Manager: npm 10.2.5
OS: darwin arm64

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

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1700.8
@angular-devkit/build-angular   17.0.8
@angular-devkit/core            17.0.8
@angular-devkit/schematics      17.0.8
@schematics/angular             17.0.8
rxjs                            7.8.1
typescript                      5.2.2
zone.js                         0.14.2
CarlosTorrecillas commented 6 months ago

@hittten perhaps you are missing the file that the migrations create for you:

https://stackoverflow.com/a/77459798

I had the same problem but looking around I found it. I haven’t seen that in the docs though and in my view it should be created by default when you select SSR enabled

CarlosTorrecillas commented 6 months ago

@hittten perhaps you are missing the file that the migrations create for you:

https://stackoverflow.com/a/77459798

I had the same problem but looking around I found it. I haven’t seen that in the docs though and in my view it should be created by default when you select SSR enabled

omansak commented 6 months ago

I

Indeed, several things about SSR need to be documented, for example since the move from @nguniversal/express-engine to @angular/ssr the express tokens were lost:

https://github.com/angular/universal/blob/e798d256de5e4377b704e63d993dc56ea35df97d/modules/express-engine/tokens/injection-tokens.ts

I had to search the internet for a while to understand that I had to add the "src/express.tokens.ts" file now because it no longer comes in the @angular/ssr package and that's what I did

I have published a repo with the minimum to recreate the SSR problem with angular 17: https://github.com/hittten/angularSSR17

Clone & Setup:

git clone https://github.com/hittten/angularSSR17
npm install
npm start

Log

Navigate to http://localhost:4200/404

ERROR TypeError: Cannot read properties of null (reading 'status')
    at _NotFoundPageComponent (/Workspace/hittten/angularSSR17/src/app/pages/not-found-page/not-found-page.component.ts:17:21)

Angular 16 code (working):

import {RESPONSE} from "@nguniversal/express-engine/tokens";
import {Response} from "express";

constructor(
    @Optional() @Inject(RESPONSE) private response: Response,
    @Inject(PLATFORM_ID) private platformId: object,
  ) {
    if (isPlatformServer(this.platformId)) {
      this.response.status(404)
    } else {
      console.error(`response status code 404`)
    }
  }

Angular 17 code (not working):

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

const REQUEST = new InjectionToken<Request>('REQUEST');
const RESPONSE = new InjectionToken<Response>('RESPONSE');

constructor(
    @Optional() @Inject(RESPONSE) private response: Response,
    @Inject(PLATFORM_ID) private platformId: object,
  ) {
    if (isPlatformServer(this.platformId)) {
      this.response.status(404)
    } else {
      console.error(`response status code 404`)
    }
  }

Info

basically I have created a project like this:

npx @angular/cli@17.0.8 new angularSSR17 --ssr --routing --scss --skip-tests
cd angularSSR17
npm i -D firebase-tools@13.0.2
ng g c pages/homePage
ng g c pages/aboutPage
ng g c pages/notFoundPage

I also disable pre-render in angular.json:

{
    "scripts": [],
    "server": "src/main.server.ts",
    "prerender": false,
    "ssr": {
      "entry": "server.ts"
    }
}

and I have done the settings of the routes for lazy load. I have not installed @angular/fire because it is not necessary to recreate the SSR error.

And...

I also intent to provide express tokens in /server.ts: provide: REQUEST, useValue: req }, { provide: RESPONSE, useValue: res }, which is not necessary to do in angular 16, but it doesn't work either.

  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 },
        ],
      })
      .then((html) => res.send(html))
      .catch((err) => next(err));
  });

Versions

Angular CLI: 17.0.8
Node: 20.10.0
Package Manager: npm 10.2.5
OS: darwin arm64

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

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1700.8
@angular-devkit/build-angular   17.0.8
@angular-devkit/core            17.0.8
@angular-devkit/schematics      17.0.8
@schematics/angular             17.0.8
rxjs                            7.8.1
typescript                      5.2.2
zone.js                         0.14.2

This method works perfect if you disabled prerender

in angular.json "prerender": false

hittten commented 6 months ago

@hittten perhaps you are missing the file that the migrations create for you:

https://stackoverflow.com/a/77459798

I had the same problem but looking around I found it. I haven’t seen that in the docs though and in my view it should be created by default when you select SSR enabled

Sorry, my message was misunderstood, really the problem I show in my example is about the REQUEST and the RESPONSE . I updated my comment for better understanding:

I had to search the internet for a while to understand that I had to add the "src/express.tokens.ts" file now because it no longer comes in the @angular/ssr package, so I wrote the file manually and found another problem: The REQUEST and RESPONSE are undefined when you inject: @Optional() @Inject(RESPONSE) private response: Response

I have published a repo with the minimum to recreate the SSR problem with angular 17: > https://github.com/hittten/angularSSR17

@CarlosTorrecillas Thanks for the response, but I know I did the same thing as you.

webberig commented 6 months ago

I've been maintaining a project for a while which has more things added to server.ts than just Express tokens:

The Angular app can run fine without them, but when work needs to be done on any of those, a developer must be able to run it locally, run tests, etc...

Roman-Simik commented 6 months ago

I've spent like 3 hours praying to god, how nothing from the server.ts is executed... then found this issue :D How they could make this feature stable with this bottleneck. What's the progress?

Abdulrrahman commented 6 months ago

I almost lost my last brain cell trying to understand why nothing is being executed in server.ts, this need to be documented or at least mentioned while migrating to angular 17

SergeyKardash commented 6 months ago

Is there any solution to return status code 404 in Angular 17 using @angular/ssr?

omansak commented 6 months ago

Is there any solution to return status code 404 in Angular 17 using @angular/ssr?

https://github.com/angular/angular-cli/issues/26323#issuecomment-1954498130 use it without prerender or exclude 404 page from prerender.

alinaderlou commented 6 months ago

if you enable prerender option in @angular-devkit/build-angular:application the build procces failed because i have RESPONSE and REQUEST injections in server.ts file and angluar can not run the app for saving prerender routes, the only way that app runs successfully is to run /dist/server/server.mjs with node and injection works.

AnwarMEQOR commented 6 months ago

I've been closely following this discussion due to encountering the same challenge.

Upon reviewing the Angular documentation, particularly the esbuild page (https://angular.io/guide/esbuild), I noticed that it lists known issues but surprisingly omits this specific scenario. Given its critical impact on server-side rendering setups and Angular Universal implementations, including this information could be highly beneficial. It would help set proper expectations for other organizations facing similar upgrade decisions and provide much-needed clarity on the issue.

Acknowledging this challenge in the documentation and, ideally, providing a workaround or solution would greatly assist developers and companies like ours in planning our upgrade paths more effectively.

Thank you for all the hard work and dedication to improving Angular. It's genuinely appreciated, and we look forward to overcoming this hurdle with your support.

By the way, you guys can upvote this feature request: https://github.com/angular/angular-cli/issues/27144

Roman-Simik commented 6 months ago

@andrewatwood Why is this a blocker for you to upgrade to Angular 17? You know that it's no requirement to use esbuild/vite in angular 17 and you can still stick to the old webpack builder?

andrewatwood commented 6 months ago

@andrewatwood lit's no requirement to use esbuild/vite in angular 17

It's not a blocker to upgrade at all! But to ship this dev server as the default for new projects when it doesn't support an incredibly common SSR use case without any documentation about that fact, and no alternative, is very silly.

If this was an opt-in beta-level feature, it would be far less egregious. Especially since it seems primarily like an oversight with no real technical justification.

AnwarMEQOR commented 6 months ago

@andrewatwood lit's no requirement to use esbuild/vite in angular 17

But to ship this dev server as the default for new projects when it doesn't support an incredibly common SSR use case without any documentation about that fact, and no alternative, is very silly.

If this was an opt-in beta-level feature, it would be far less egregious. Especially since it seems primarily like an oversight with no real technical justification.

Thank you for pointing that out.

DevExplotel commented 6 months ago

Already several months now and still nothing. Give priority to solving the problem please

zygarios commented 6 months ago

This is indeed very strange. It seems that the problem is very widespread, the latest version has become unusable on SSR without any mention during the update and there is no information. The problem seems to be a major inconvenience and despite the immense gratitude for what you do, it would be nice to hear even an approximate date when you will provide us with some new information, where we stand and what are the obstacles. The RFC + open Reactivity projects showed that you can be transparent in your communication and we as a community appreciate it very much. In the case of such a popular problem, we would like to know similarly where we stand. But of course, you are in charge here.

Greetings to the Angular team and don't take this negatively. We just want to be able to use all the new things you give us :)

parisxmas commented 6 months ago

I'm digging internet for 12 hours and no solution, cookies coming from client not passing to api calls made by node

DevWedeloper commented 6 months ago

I have a guard that passes the cookies in a callback, it is logging undefined in the back-end even though the cookie is existing in the client. I think my issue is related to this one.

mgechev commented 6 months ago

This is a known problem and we're working on resolving it. Currently, the plan is to ship it as part of the v18 release this May. If anything unexpected pops up and we're unable to deliver in this timeframe we'll follow up here.

flecce commented 6 months ago

Until v18 arrives, is there a way to inject Vite requests and read HTTP headers? In production mode, there is the possibility to inject an Express request and use it, for example, in an HTTP interceptor. However, I cannot find a way to do this in development mode with Vite. Thank you.

andrewatwood commented 6 months ago

I did a quick proof-of-concept patch-package patch to test how far away this really is, and it's just a handful of lines! The req is right there: all we have to do is pass it to the render function and add it to the list of providers.

Here's a gist with my POC patch (using a plain string DI token as the actual InjectionToken token didn't seem to resolve properly). Works like a charm, I just have to manually parse the cookies out as you can see, since my use case makes use of the cookie-parser middleware. I'd file an actual PR, but as I mentioned before, the repo npm i --force fails so doing actual contribution work beyond fixing typos is blocked.

NOTE: If you are actually going to use this patch, you'll need to coalesce between the actual Request token injected value and the value injected by this plain string token wherever you consume it since they are two different tokens entirely.

@mgechev please consider scoping this specific Request injection functionality for a v17 release sometime prior to v18 and the May release. There's obviously a lot of scope covered in these comments and this issue overall, but the above patch demonstrates that ensuring Request gets injected in the dev server is a self-contained, low-lift piece of work.

Thanks for fielding this ticket and letting us know things are in the works!

AmirAlOmari commented 6 months ago

Here's a gist with my POC patch

Though this patch seems to fix a concrete issue with cookies, I believe the root cause lies in fact that application builder is not a drop-in replacements for @angular-devkit/build-angular:ssr-dev-server, because the application builder does not run the server.ts but instead creates the in-memory one (not sure what's the right term here) to do SSR.

Not sure what's the vision of the Angular team at this point, yet the drop-in replacement is what I'd personally expect.

someApprentice commented 6 months ago

Summarizing all mentioned above, the following conclusion can be made.

Providing DI tokens for SSR does not work when building an application using @angular-devkit/build-angular:dev-server. This is due to dev-server not using server.ts and Express itself, but using its own middleware and server. Also, this tokens will not be available when using the prerender option.

To solve this problem, the application needs to be built using @angular-devkit/build-angular:application and disable the prerender option (or exclude routes that use the tokens).

The following code can be applied:

./src/express.tokens.ts

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

export const REQUEST = new InjectionToken<Request>('REQUEST');
export const RESPONSE = new InjectionToken<Response>('RESPONSE');
./server.ts

...

  // 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 }
        ],
      })
      .then((html) => res.send(html))
      .catch((err) => next(err));
  });
./src/app/app.component.ts

import {
  Component,
  Inject,
  Optional,
  PLATFORM_ID
} from '@angular/core';

import { APP_BASE_HREF } from '@angular/common';

import { isPlatformServer } from '@angular/common';

import { Request, Response } from 'express';
import { REQUEST, RESPONSE } from '../express.tokens';

import { RouterOutlet } from '@angular/router';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet],
  template: `
    <h1>Welcome to {{title}}!</h1>

    <router-outlet />
  `,
  styles: [],
})
export class AppComponent {
  title = 'ssr-di';

  constructor(
    @Inject(PLATFORM_ID) private platformId: Object,
    @Optional() @Inject(APP_BASE_HREF) private baseUrl: string,
    @Optional() @Inject(REQUEST) private request: Request,
    @Optional() @Inject(RESPONSE) private response: Response
  ) {
    if (isPlatformServer(this.platformId)) {
      console.log(this.baseUrl); // baseUrl string
      console.log(this.request); // Request object
      console.log(this.response); // Response object
    }
  }
}
./src/app/not-found/not-found.component.ts

import {
  Component,
  Inject,
  Optional,
  PLATFORM_ID
} from '@angular/core';

import { isPlatformServer } from '@angular/common';

import { Response } from 'express';
import { RESPONSE } from '../../express.tokens';

@Component({
  selector: 'app-not-found',
  standalone: true,
  imports: [],
  template: `
    <p>
      404 not-found works!
    </p>
  `,
  styles: ``
})
export class NotFoundComponent {
  constructor(
    @Inject(PLATFORM_ID) private platformId: Object,
    @Optional() @Inject(RESPONSE) private response: Response
  ) {
    if (isPlatformServer(this.platformId)) {
      this.response.status(404);
    }
  }
}

The most convenient way to hot reload in a development environment that I've found is to build the application using ng build --watch --configuration development in conjunction with nodemon.

Hope this comment helps other developers to quickly find a solution to this problem, until the Angular team resolves this issue with the v18 release.

quedicesebas commented 5 months ago

Can you please @someApprentice explain how to serve the app to test this?

Rouvas commented 5 months ago

I did a quick proof-of-concept patch-package patch to test how far away this really is, and it's just a handful of lines! The req is right there: all we have to do is pass it to the render function and add it to the list of providers.

Here's a gist with my POC patch (using a plain string DI token as the actual InjectionToken token didn't seem to resolve properly). Works like a charm, I just have to manually parse the cookies out as you can see, since my use case makes use of the cookie-parser middleware. I'd file an actual PR, but as I mentioned before, the repo npm i --force fails so doing actual contribution work beyond fixing typos is blocked.

NOTE: If you are actually going to use this patch, you'll need to coalesce between the actual Request token injected value and the value injected by this plain string token wherever you consume it since they are two different tokens entirely.

@mgechev please consider scoping this specific Request injection functionality for a v17 release sometime prior to v18 and the May release. There's obviously a lot of scope covered in these comments and this issue overall, but the above patch demonstrates that ensuring Request gets injected in the dev server is a self-contained, low-lift piece of work.

Thanks for fielding this ticket and letting us know things are in the works!

@andrewatwood Could you please describe here how to use this patch, or rather, how to use it? This would help many who don't have a good understanding.

AmirAlOmari commented 5 months ago

Recently had to dig a bit more into vite, looks like it has the "middleware mode" which should come to the rescue 🛟

Additionally, found a vite-plugin-node which works fine. This plugin might be useful, having the adapter for most of the frameworks including express. Some ideas could be grasped if not to be used directly.

shadow1349 commented 5 months ago

I

Indeed, several things about SSR need to be documented, for example since the move from @nguniversal/express-engine to @angular/ssr the express tokens were lost: https://github.com/angular/universal/blob/e798d256de5e4377b704e63d993dc56ea35df97d/modules/express-engine/tokens/injection-tokens.ts I had to search the internet for a while to understand that I had to add the "src/express.tokens.ts" file now because it no longer comes in the @angular/ssr package and that's what I did I have published a repo with the minimum to recreate the SSR problem with angular 17: https://github.com/hittten/angularSSR17

Clone & Setup:

git clone https://github.com/hittten/angularSSR17
npm install
npm start

Log

Navigate to http://localhost:4200/404

ERROR TypeError: Cannot read properties of null (reading 'status')
    at _NotFoundPageComponent (/Workspace/hittten/angularSSR17/src/app/pages/not-found-page/not-found-page.component.ts:17:21)

Angular 16 code (working):

import {RESPONSE} from "@nguniversal/express-engine/tokens";
import {Response} from "express";

constructor(
    @Optional() @Inject(RESPONSE) private response: Response,
    @Inject(PLATFORM_ID) private platformId: object,
  ) {
    if (isPlatformServer(this.platformId)) {
      this.response.status(404)
    } else {
      console.error(`response status code 404`)
    }
  }

Angular 17 code (not working):

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

const REQUEST = new InjectionToken<Request>('REQUEST');
const RESPONSE = new InjectionToken<Response>('RESPONSE');

constructor(
    @Optional() @Inject(RESPONSE) private response: Response,
    @Inject(PLATFORM_ID) private platformId: object,
  ) {
    if (isPlatformServer(this.platformId)) {
      this.response.status(404)
    } else {
      console.error(`response status code 404`)
    }
  }

Info

basically I have created a project like this:

npx @angular/cli@17.0.8 new angularSSR17 --ssr --routing --scss --skip-tests
cd angularSSR17
npm i -D firebase-tools@13.0.2
ng g c pages/homePage
ng g c pages/aboutPage
ng g c pages/notFoundPage

I also disable pre-render in angular.json:

{
    "scripts": [],
    "server": "src/main.server.ts",
    "prerender": false,
    "ssr": {
      "entry": "server.ts"
    }
}

and I have done the settings of the routes for lazy load. I have not installed @angular/fire because it is not necessary to recreate the SSR error.

And...

I also intent to provide express tokens in /server.ts: provide: REQUEST, useValue: req }, { provide: RESPONSE, useValue: res }, which is not necessary to do in angular 16, but it doesn't work either.

  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 },
        ],
      })
      .then((html) => res.send(html))
      .catch((err) => next(err));
  });

Versions

Angular CLI: 17.0.8
Node: 20.10.0
Package Manager: npm 10.2.5
OS: darwin arm64

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

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1700.8
@angular-devkit/build-angular   17.0.8
@angular-devkit/core            17.0.8
@angular-devkit/schematics      17.0.8
@schematics/angular             17.0.8
rxjs                            7.8.1
typescript                      5.2.2
zone.js                         0.14.2

This method works perfect if you disabled prerender

in angular.json "prerender": false

Dude, I've been looking for hours about how to solve this specific issue. Thank you.

csabe812 commented 5 months ago

Hi, I have an Angular 11 (using Angular Universal) project and after struggling with hours I was able to update it to Angular 17 (with SSR). After spending extra 5-6 hours to find the solution I found this open issue. In fact I am facing with the same issue that @mkurcius wrote. Am I understand correctly that currently there is no way to make fullstack Angular v17 apps using the generated server.ts file?

zygarios commented 5 months ago

No, it's not possible for now. https://github.com/angular/angular-cli/issues/26323#issuecomment-1971390348

geexup commented 4 months ago

I need host from headers to properly render og meta tags, and spend so much time to make it work, but providers not providing...

zygarios commented 4 months ago

This is a known problem and we're working on resolving it. Currently, the plan is to ship it as part of the v18 release this May. If anything unexpected pops up and we're unable to deliver in this timeframe we'll follow up here.

Hey, @mgechev. Is there still a chance for SSR fix in version 18?

SkyZeroZx commented 4 months ago

I need host from headers to properly render og meta tags, and spend so much time to make it work, but providers not providing...

If you refer usage of meta tags of Document it's possible with Resolver and inject Meta

See this https://medium.com/@shravanvishwakarma5/meta-tags-in-angular-c483c3e6f0d4

Only use in resolvers and work correctly , in my case usage a variant more overhead because i need show imagen a pre prender pages

AlexVFornazieri commented 3 months ago

I am not the best in Angular. I just wanted to work with server.ts locally in Angular 17. So I came up with the following:

  • Open 2 terminals.
  • On the first terminal run ng build --watch --configuration development. Your code will re-build every time you save the code.
  • On the second terminal run node dist/my-project/server/server.mjs.
  • Reload page after save. Sometimes build is cached, so to fix this, check Disable cache in the Network tab in browser's DEV tools.

This is simple workaround. If someone can show better approach, please reply to this comment. Thanks!

That way works, but losse the hot reload and source maping for all application, anyone have a better workarround for while?

I try improve with nodemon and --enable-source-maps but nothing change, only auto restart the server when files changes, but need refresh the browser.

I'm ussing the server.ts expresse to run API Proxy and Auth routes (Back For Front)

SaeedDev94 commented 3 months ago

For SSR development accessing to server request and response objects is a MUST TO HAVE feature in both development and production envs
This is becoming scary as I didn't see any changes about this issue in development versions of v18
No alternative and clean solution for this until now!
The solutions introduced here are absolutely dirty!!
If this remains without a proper solution (fix or alternative) I'll probably reconsider about my entire frontend stack!!

Fronikuniu commented 3 months ago

Hello, when can this problem be expected to be solved?

zygarios commented 3 months ago

@Fronikuniu I hope tomorrow with v18 release.

SkyZeroZx commented 3 months ago

@Fronikuniu I hope tomorrow with v18 release.

Today in my country release Angular 18 but i dont see relative change to issue , I haven't tried it yet in my project

JihedHmida commented 3 months ago

For SSR development accessing to server request and response objects is a MUST TO HAVE feature in both development and production envs This is becoming scary as I didn't see any changes about this issue in development versions of v18 No alternative and clean solution for this until now! The solutions introduced here are absolutely dirty!! If this remains without a proper solution (fix or alternative) I'll probably reconsider about my entire frontend stack!!

sadly i totally agree. i guess no one can start a project with absolutely dirty solutions hoping for fixs..

This is a known problem and we're working on resolving it. Currently, the plan is to ship it as part of the v18 release this May. If anything unexpected pops up and we're unable to deliver in this timeframe we'll follow up here.

and as of version 18 nothing changed .. nothing is mentioned in the CHANGELOG.md nor in the Angular v18 is now available! blog post.

i don't want be that guy but i guess we waiting for the follow up ..

SkyZeroZx commented 3 months ago

For SSR development accessing to server request and response objects is a MUST TO HAVE feature in both development and production envs This is becoming scary as I didn't see any changes about this issue in development versions of v18 No alternative and clean solution for this until now! The solutions introduced here are absolutely dirty!! If this remains without a proper solution (fix or alternative) I'll probably reconsider about my entire frontend stack!!

sadly i totally agree. i guess no one can start a project with absolutely dirty solutions hoping for fixs..

This is a known problem and we're working on resolving it. Currently, the plan is to ship it as part of the v18 release this May. If anything unexpected pops up and we're unable to deliver in this timeframe we'll follow up here.

and as of version 18 nothing changed .. nothing is mentioned in the CHANGELOG.md nor in the Angular v18 is now available! blog post.

i don't want be that guy but i guess we waiting for the follow up ..

I hope it is fixed in a minor fix or something soon, which I also hope to update my monorepo project in Nx

JihedHmida commented 3 months ago

NX upgrades lag behind Angular, usually taking a few days to catch up. we all aboard the wait train

rezonant commented 3 months ago

I just tried an experimental upgrade to v18 to see if this issue is resolved and it's clear that it has not, though given the lack of any official communication on this issue leading up to release, this outcome is not surprising.

For my team's use case, we didn't have a good way to bring SSR directly into our dev server prior to v17 anyway (given the complexity of our application), so it's not dire that we can't run a valid SSR configuration right now, but clearly other users did have this capability in previous Angular versions with Universal. It's highly unfortunate that the Angular team could not provide even a cursory update about how or whether this issue would be addressed in Angular 18, despite promising to do so (https://github.com/angular/angular-cli/issues/26323#issuecomment-1971390348). The upgrade marketing was that we would not need Angular Universal at all, because it was all brought into the core framework. As part of that, Angular Universal was never updated to work with v17, so folks who had a solid workflow in the old system either have to abandon SSR in their development workflow or stay behind on previous versions- it looks like that will continue with Angular 18.

The promise of a straightforward, fast dev server native solution for Angular SSR which runs the application's entire codebase is extremely appealing for the community, regardless of whether an SSR dev experience worked prior. Not providing updates on resolution here raises eyebrows.

I'm not mad about v18 not shipping a solution, I'm just incredibly disappointed in the lack of communication. I'm sure I'm not the only one hoping that Angular can provide a suitable explanation for failing to address this in v18. It's highly questionable that the v17 Application Builder was shipped at all without considering what would happen if the core server entrypoint was not executed. I still struggle to understand what the Angular team expected "ssr": true to be useful for with the state it was shipped in.

Platonn commented 3 months ago

You might find the following workaround very useful, until Angular fixes this issue - the article by @pawelfras: 🙌

Angular 17: Overcoming SSR Challenges Of The New 'application' Builder 🔎

✅ run SSR dev server ✅ server.ts included ✅ fast application builder ✅ Angular 17+

quedicesebas commented 3 months ago

I can't do anything useful with SSR without accessing the request in my app 😡.