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

Handle 404 and 500 errors #667

Closed
2 of 10 tasks
ivancamilov opened this issue Mar 3, 2017 · 13 comments
Closed
2 of 10 tasks

Handle 404 and 500 errors #667

ivancamilov opened this issue Mar 3, 2017 · 13 comments

Comments

@ivancamilov
Copy link

ivancamilov commented Mar 3, 2017

Note: for support questions, please use one of these channels: https://github.com/angular/universal/blob/master/CONTRIBUTING.md#question. This repository's issues are reserved for feature requests and bug reports. Also, Preboot has moved to https://github.com/angular/preboot - please make preboot-related issues there.

  • I'm submitting a ...
  • bug report
  • feature request
  • support request => Please do not submit support request here, see note at the top of this template.
  • What modules are related to this Issue?
  • express-engine
  • grunt-prerender
  • gulp-prerender
  • hapi-engine
  • universal-next
  • universal
  • webpack-prerender
  • Do you want to request a feature or report a bug?
    Request a feature

  • What is the current behavior?
    There's currently no way I can find to make sure the server responds with an HTTP status code 404 when I need to.

Furthermore, when there's an error the server currently refuses the connection.

  • What is the expected behavior?
    There should be a way to send responses with HTTP status 404.
    Additionally, there should be a way to handle errors and send responses with HTTP status 500.

  • What is the motivation / use case for changing the behavior?
    When application logic determines there should be a 404 error (for instance, having a route for { path: 'clients/:client-id' }, doing an API request and finding the client ID requested doesn't exist) you should respond with a 404 page. Currently, you can handle this in your application and show a 404 page, but it'll be served with a 200 status code.

Additionally, there should be a way to handle any error and show the user a 500 error page, served with the correct status code.

@gxolin
Copy link

gxolin commented Apr 3, 2017

I'm upvoting this one.
This is a serious issue for SEO, because soft-404 is not well handled by search engines.

@kukjevov
Copy link

kukjevov commented Apr 5, 2017

Hi :). I have solved this in angular 4 using so called StatusCodeService

Here you can see my solution. https://github.com/kukjevov/ng-universal-demo/blob/mine/app/pages/notFound/notFound.component.ts

Just before i render content to string i retrieve status code from service and then send it together with html and statusCode and process it in https://github.com/kukjevov/ng-universal-demo/blob/mine/server.js

It is working. Maybe not best solution but working one :). But I am not using ngExpressEngine, but rendering result directly by myselft.

@MarkPieszak
Copy link
Member

Thank you @kukjevov that's one of the best ways to handle it that I'm aware of!

@ivancamilov
Copy link
Author

I'm sorry to revisit an old issue, but I still quite haven't figured this one out. I can't seem to find the definition for StatusCodeService and I'm having a hard time trying to figure out how I should implement my own solution for this.

As far as I understand this, there should be a way to manipulate the status code inside the ngExpressEngine, but I haven't figured out how that should be done.

@kukjevov
Copy link

kukjevov commented Jun 8, 2017

Hi i try to explain myself one more time :).

To make it working you must implement following:

StatusCodeService

import {Injectable} from '@angular/core';

/**
 * Service used for transfering http status code for response
 */
@Injectable()
export class StatusCodeService
{
    //######################### private fields #########################
    
    /**
     * Current status code
     */
    private _statusCode?: number;

    //######################### public properties #########################

    /**
     * Gets current status code
     */
    public get statusCode(): number | null | undefined
    {
        return this._statusCode;
    }

    //######################### public methods #########################

    /**
     * Sets current status code
     * @param {number} code Status code value that will be set
     */
    public setStatusCode(code?: number)
    {
        this._statusCode = code;
    }
}

You need to create your own method for rendering to string see https://github.com/angular/angular/blob/master/packages/platform-server/src/utils.ts

modify function at line 37

function _render<T>(
    platform: PlatformRef, moduleRefPromise: Promise<NgModuleRef<T>>): Promise<{html: string, statusCode?: number}> {
  return moduleRefPromise.then((moduleRef) => {
    const transitionId = moduleRef.injector.get(ɵTRANSITION_ID, null);
    if (!transitionId) {
      throw new Error(
          `renderModule[Factory]() requires the use of BrowserModule.withServerTransition() to ensure
the server-rendered app can be properly bootstrapped into a client app.`);
    }
    const applicationRef: ApplicationRef = moduleRef.injector.get(ApplicationRef);
    return toPromise
        .call(first.call(filter.call(applicationRef.isStable, (isStable: boolean) => isStable)))
        .then(() => {
          let statusCodeService = moduleRef.injector.get(StatusCodeService);
          let statusCode: number | null = null;

          if(statusCodeService)
          {
              statusCode = statusCodeService.statusCode;
          }
     
          const output = {html: platform.injector.get(PlatformState).renderToString(), statusCode};
          platform.destroy();
          return output;
        });
  });
}

That means that method used for rendering to string returns object which contains html as string and status code as number. You will use this in your server, does not matter what kind of server (nodejs, .net) to set response status code and content.

For example if you use Connect nodejs server.

            res.setHeader('Content-Type', 'text/html');

            if(succ.statusCode)
            {
                res.statusCode = succ.statusCode;
            }

            res.end(err || succ.html);

Where succ is returned object with html and statusCode properties. res is nodejs response object and err is error in case that there is any.

Then when you use StatusCodeService to set status code during server side rendering (somewhere in your component or service or anywhere), last set status will be returned to your browser.

I hope this helps.

@ivancamilov
Copy link
Author

@kukjevov after a long time I finally got this to work. Thank you so much! you are a gentleman and a scholar.

@patrickmichalina
Copy link
Contributor

patrickmichalina commented Jul 28, 2017

A simple service using ngExpressEngine response:

https://github.com/patrickmichalina/fusebox-angular-universal-starter/blob/master/src/client/app/shared/services/server-response.service.ts

being used here:

https://github.com/patrickmichalina/fusebox-angular-universal-starter/blob/master/src/client/app/not-found/not-found.component.ts

@alirezamirian
Copy link

A very simple solution is to provide response object, under some injection token (e.g. angular universal's HTTP_RESPONSE), by passing extraProviders option to renderModuleFactory() and set status in your app whenever required by injecting response and calling response.status(status).

@CanKattwinkel
Copy link

@alirezamirian that is the best solution so far. Since I just stumbled upon this problem myself, I recorded my procedure in a blog post:

https://blog.thecodecampus.de/angular-universal-handle-404-set-status-codes/

@msklvsk
Copy link

msklvsk commented May 20, 2018

And this is how you apply @CanKattwinkel’s howto on the latest v6 server.ts from the docs:

app.engine('html', (_, options, callback) =>
  ngExpressEngine({
    bootstrap: AppServerModuleNgFactory,
    providers: [
      provideModuleMap(LAZY_MODULE_MAP),
      {
        provide: REQUEST,
        useValue: options.req,
      },
      {
        provide: RESPONSE,
        useValue: options.req.res,
      },
    ],
  })(_, options, callback)
)

@damienwebdev
Copy link

@msklvsk Thanks for saving my evening.

@odahcam
Copy link

odahcam commented Sep 11, 2018

When I send the 404 status, my express server just shows a Not Found text in the document, does anyone have managed to send the 404 status and still render the Angular NotFoundComponent?

@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 Sep 4, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants