You are currently viewing Angular Universal: Setting HTTP Status Code
Angular

Angular Universal: Setting HTTP Status Code

Angular Universal gives superpowers to traditional Angular apps such as server-side rendering (SSR) and full features using Express.js server. This allows us to implement features like returning HTTP status codes in appropriate scenarios.

In this post, I will show you how to set the HTTP status code from an Angular application with SSR.

Background

Let’s address why you might even want to set HTTP status code from an Angular component in the first place.

As an example, my application renders the following component when it does not recognise a URL:

import { Component } from '@angular/core';
@Component({
  selector: 'app-not-found-page',
  template: `
      <h1>Not Found</h1>
      <p>This page does not exist.</p>
  `
})
export class NotFoundPageComponent {}Code language: TypeScript (typescript)

When the component is rendered in the browser, the status code is actually 200 OK.

Not Found 200 OK
Not Found 200 OK

Search engines would consider this response as valid and continue indexing the contents of this page.

In this case, we should be returning the correct status code: 404 Not Found.

Another possible scenario is returning status code 401 Unauhorised when securing pages from anonymous access.

Inject response from Express.js

Like any other server-side application, we need to modify the HTTP response to set the status code.

To do this in Angular Universal, we can inject the Response object from Express.js server into the constructor of the component:

import { Component, Inject, Optional } from '@angular/core';
import { RESPONSE } from '@nguniversal/express-engine/tokens'
import { Response } from 'express'
@Component({
  selector: 'app-not-found-page',
  template: `
      <h1>Not Found</h1>
      <p>This page does not exist.</p>
  `
})
export class NotFoundPageComponent {
  constructor(
    @Optional() @Inject(RESPONSE) private response: Response
  ) { }
}
Code language: TypeScript (typescript)

Notice the dependency is optional since Angular Universal applications run on both the server and the browser.

An optional parameter becomes null when the application is running in the browser. This means any usage of the response should be protected with a null guard.

Setting the status code

With the response available in the component, we can implement our logic inside the OnInit event.

We can pass in the status code to the status method as shown below as per Response API docs.

export class NotFoundPageComponent implements OnInit {
  constructor(
    @Optional() @Inject(RESPONSE) private response: Response
  ) { }

  ngOnInit() {
    // Return 404 when running server side
    if (this.response) {
      this.response.status(404);
    }
  }
}
Code language: TypeScript (typescript)

Since the response can be null in the browser context, we guard against null value to avoid our component from failing.

Let’s now serve an SSR build locally to test the status code.

npm run build:ssr
npm run serve:ssrCode language: Bash (bash)

Running the same scenario now yields 404 Not Found response in the browser.

Not Found 404
Not Found 404

Full source code

Putting it all together this is how the component looks like now:

import { Component, Inject, OnInit, Optional } from '@angular/core';
import { RESPONSE } from '@nguniversal/express-engine/tokens'
import { Response } from 'express'
@Component({
  selector: 'app-not-found-page',
  template: `
      <h1>Not Found</h1>
      <p>This page does not exist.</p>
  `
})
export class NotFoundPageComponent implements OnInit {

  constructor(
    @Optional() @Inject(RESPONSE) private response: Response
  ) { }

  ngOnInit() {
    // Return 404 when running server side
    if (this.response) {
      this.response.status(404);
    }
  }
}Code language: TypeScript (typescript)

Unit tests

It helps to put in unit test coverage around the features of the component to act as a safety net. Tests also help document our code.

This is the full spec file containing the implementation details for all of the tests:

import { ComponentFixture, TestBed } from '@angular/core/testing';

import { NotFoundPageComponent } from './not-found-page.component';
import { RESPONSE } from '@nguniversal/express-engine/tokens';
import { By } from '@angular/platform-browser';

describe('NotFoundPageComponent', () => {
  let component: NotFoundPageComponent;
  let fixture: ComponentFixture<NotFoundPageComponent>;
  let response: any;

  describe('client-side', () => {
    beforeEach(async () => {
      await TestBed.configureTestingModule({
        declarations: [NotFoundPageComponent],
        providers: [
          {
            provide: RESPONSE,
            useValue: null
          }
        ]
      })
        .compileComponents();

      fixture = TestBed.createComponent(NotFoundPageComponent);
      component = fixture.componentInstance;
      response = TestBed.inject(RESPONSE);
      fixture.detectChanges();
    });

    it('should create', () => {
      expect(component).toBeTruthy();
    });

    it('should render elements', () => {
      expect(fixture.debugElement.query(By.css("h1")).nativeElement.textContent).toEqual("Not Found");
      expect(fixture.debugElement.query(By.css("p")).nativeElement.textContent).toEqual("This page does not exist.");
    });
  });

  describe('server-side', () => {
    beforeEach(async () => {
      await TestBed.configureTestingModule({
        declarations: [NotFoundPageComponent],
        providers: [
          {
            provide: RESPONSE,
            useValue: jasmine.createSpyObj("Response", ["status"])
          }
        ]
      })
        .compileComponents();

      fixture = TestBed.createComponent(NotFoundPageComponent);
      component = fixture.componentInstance;
      response = TestBed.inject(RESPONSE);
      fixture.detectChanges();
    });

    it('should set 404 status code on response', () => {
      expect(response.status).toHaveBeenCalledOnceWith(404);
    });
  });

});
Code language: TypeScript (typescript)

Umut Esen

I am a software developer and blogger with a passion for the world wide web.

Leave a Reply