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:
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):
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:
As you can see, it now works just as we wanted it. Both users and robots must be tremendously happy!
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
.