Setting Status Code and Handling 404 Pages in Angular Universal

If you use Angular Universal, you probably know that in addition to Server Side Rendering, Universal provides you with the full range of Node.js functionality on the server-side.
Today we will examine how to use Express.js’s popular Request and Response API within our Angular applications.
To illustrate the setup process, I’ll show how I created the simple 404 Not Found page on this blog.

Laying the foundation

Let’s first create a NotFoundComponent, to which we will redirect our users:

@Component({
 selector: 'blog-not-found',
 template: `<h2>Seems like this page doesn't exist :(</h2>`
})
export class NotFoundComponent {}

And set up proper routes and redirects for our newly-created NotFoundComponent:

... // other routes
{
   path: '404',
   component: NotFoundComponent
 },
...

Now if we go to our 404 page, we’ll see the following:

The screenshot of NotFound

All good, right? Not quite. You see, our Not Found page clearly works for the users (except the godly design, perhaps) but robots (such as search engines) still perceive it to be a valid page of our website that needs to be indexed.
We can verify this if we look at the Network tab in the DevTools, where we see that the status code for our page is 200 (success) instead of expected 404 (not found):

Screenshot of wacky response code in devtools

Using Express.js Request and Response Objects within our application

To set the status code, we will use the Response object. In case you’re not familiar with them, Request (aka req) and Response (aka res) are the primary way of processing HTTP requests in Express.

Providing the Response object to our Angular app

Looking at the source code of Universal, we see that unlike REQUEST, RESPONSE provider is optional and only provided if there is a res object in the RenderOptions:

if (res) {
    providers.push({
        provide: RESPONSE,
        useValue: res
    });
}

Therefore, in our server.ts file we need to add res to the RenderOptions object when rendering our pages:

app.get('*', (req, res) => {
 res.render('index', { req, res });
});

Now we can successfully inject the req and res objects into our NotFoundComponent:

import { Optional, Inject } from '@angular/core';
import { RESPONSE, REQUEST } from '@nguniversal/express-engine/tokens';
import { Request, Response } from 'express';
/*
 ...
 ...
*/
constructor(@Optional() @Inject(REQUEST) private request: Request,
            @Optional() @Inject(RESPONSE) private response: Response){

Notice that I added the @Optional() decorator. This is because Request and Response objects are purely Express concepts and thus can’t exist in the Browser context. With @Optional(), these objects will be equal to null in a Browser environment.

Setting response status code

Now that we injected the Response object into our NotFoundComponent, we can use it as follows:

if (isPlatformServer(this.platformId)) {
  this.response.status(404);
}

As I mentioned earlier, Request and Response objects are only available in the Node context, hence before using them we need to ensure we’re executing on the server side by checking isPlatformServer(...).

Full code of the NotFoundComponent:

import { Component, OnInit, Optional, Inject, PLATFORM_ID } from '@angular/core';
import { RESPONSE, REQUEST } from '@nguniversal/express-engine/tokens';
import { isPlatformServer } from '@angular/common';
import { Request, Response } from 'express';
 
@Component({
 selector: 'blog-not-found',
 template: `<h2>Seems like this page doesn't exist :(</h2>`
})
export class NotFoundComponent implements OnInit {
 constructor(@Optional() @Inject(REQUEST) private request: Request,
             @Optional() @Inject(RESPONSE) private response: Response,
             @Inject(PLATFORM_ID) private platformId: any) { }
 
 ngOnInit() {
   if (isPlatformServer(this.platformId)) {
     this.response.status(404);
   }
 }
}

Now let’s run our app once again and go to 404 with DevTools open:

404 status code screenshot

As you can see, it now works just as we wanted it. Both users and robots must be tremendously happy!

Dancing panda gif

Note: I didn’t show how to use the Request object here. However, once injected in the constructor (shown above), it can be used in a fashion similar to Response.