angular: Add support to return different Http Status Code e.g. 404, 301 Redirect and Custom Headers e.g. NoIndex header in renderApplication

Which @angular/* package(s) are relevant/related to the feature request?

platform-server

Description

Returning different HTTP status codes from the renderApplication call is not supported at this moment, it would be really useful to add support for:

  1. Returning custom status codes e.g. 404 status code for not found, 503 e.g. in case an API call returns 503 status code.
  2. Returning 301/302 redirect status code, with option to set redirect url as well.
  3. Return custom headers e.g. NoIndex headers to let search engines know that this page should not be indexed.

Proposed solution

What if the renderApplication could return a response object with properties:

  1. Body
  2. Status Code
  3. Redirect URL (Optional)
  4. Headers

Use case:

I tried using Angular App on Cloudflare Pages/Worker. It works for most of the parts, but when it comes to setting the http status code or custom headers it doesnt seem to work.

I generated a test app using the Cloudflare CLI. The CLI generates an Angular 17 app with below server.ts file:

import { renderApplication } from "@angular/platform-server";
import bootstrap from "./src/main.server";

interface Env {
    ASSETS: { fetch: typeof fetch };
}

// We attach the Cloudflare `fetch()` handler to the global scope
// so that we can export it when we process the Angular output.
// See tools/bundle.mjs
async function workerFetchHandler(request: Request, env: Env) {
    const url = new URL(request.url);
    console.log("render SSR", url.href);

    // Get the root `index.html` content.
    const indexUrl = new URL("/", url);
    const indexResponse = await env.ASSETS.fetch(new Request(indexUrl));
    const document = await indexResponse.text();

    const content = await renderApplication(bootstrap, {
        document,
        url: url.pathname,
    });

    // console.log("rendered SSR", content);
    return new Response(content, indexResponse);
}

export default {
    fetch: (request: Request, env: Env) =>
        (globalThis as any)["__zone_symbol__Promise"].resolve(
            workerFetchHandler(request, env)
        ),
};

So apparently there’s no way to pass the status code or custom headers to the worker workerFetchHandler since renderApplication just retuns a string.

About this issue

  • Original URL
  • State: open
  • Created 7 months ago
  • Reactions: 15
  • Comments: 18 (14 by maintainers)

Most upvoted comments

Although there’s room for enhancing the process of defining status codes, such as for error pages and redirects, currently setting of status codes and headers is possible through Dependency Injection (DI).

tokens.ts

const RESPONSE_INIT_OPTIONS = new InjectionToken<ResponseInit>('RESPONSE_INIT_OPTIONS');

server.ts

const responseInitOptions: ResponseInit = {};
const content = await renderApplication(bootstrap, {
  document,
  url: url.pathname,
  platformProviders: [
    { provide: RESPONSE_INIT_OPTIONS, useValue: responseInitOptions },
  ],
});

return new Response(content, responseInitOptions);

app.component.ts


@Component({
  ...
})
export class AppComponent {
  private readonly responseInitOptions = inject(RESPONSE_INIT_OPTIONS, {
    optional: true,
  });

  constructor() {
    if (!this.responseInitOptions) {
      return;
    }

    this.responseInitOptions.status = 404;
  }
}

I’m glad you are finally able to solve the issue 😃

@naveedahmed1 Thanks, Yeah finally after 4 days I could get it to work on production. This answer really helped me https://stackoverflow.com/a/77459798/10087419 and one thing that might help other people that have the same problem is that you cant use the injected response and request normally because of their type but if you use (this.response as any) the status and redirect functions will work fine.

@Gold3nFox Yes you are right, I had an idea that’s why I mentioned other github issues (which of course are still unresolved).

But as per the https://github.com/angular/angular-cli/issues/26323 it seems that the issue where injection tokens are null occurs only on development i.e. ng serve, in production it should work fine, right?

@naveedahmed1, as the docs mention the redirect is a static method on the Response interface, thus it should be accessed like:

- new Response().redirect(responseInitOptions.statusText, 301);
+ Response.redirect(responseInitOptions.statusText, 301);

In additional to that, the first parameter of the redirect is not statusText, but rather a destination URL.

Thank you so much @alan-agius4 it works perfectly for 404, can you please confirm its thread safe? I mean is there any possibility that two concurrent requests read the same status code?

No, since the responseInitOptions is created for every request.

But its throwing error property append doesnt exist on Headersinit.

Are you initialising the responseInitOptions.headers?

const responseInitOptions: ResponseInit = {
  headers: new Headers()
};