Cookies with Angular Universal/SSR

Hello friends,

A while back, I encountered an issue with Angular SSR that we cannot get the client-side data as we can fetch the data from localStorage and cookies with the client-side rendered Angular app, we just cannot get those values in SSR to directly render the result.

However, I created a solution to get the cookie data from the client browser and that is quite easy to build.

Please follow along this tutorial to get the most out of it.

We will start with the new project

First create a folder angular-ssr-cookies and run the command to setup an angular application.

ng new angular-ssr-cookies –directory ./

Now add the SSR dependencies.

ng add @nguniversal/express-engine

Now we have the base project structure.

Here we must update some files initially to get the request and response objects of Angular SSR request cycle.

  1. Server.ts:
    1. Add the imports of REQUEST and RESPONSE injection tokens from @nguniversal/express-engine/tokens.
    1. And add the providers for these tokens along with the other token provider(s) in the server.get callback.
  2. The above step will ensure that you are getting the request and response objects access in your angular application.

The code after the changes.

TOP:

import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens';

CALLBACK:

// All regular routes use the Universal engine
  server.get('*', (req, res) => {
    res.render(indexHtml, {
      req,
      providers: [
        { provide: APP_BASE_HREF, useValue: req.baseUrl },
        { provide: REQUEST, useValue: req },
        { provide: RESPONSE, useValue: res },
      ],
    });
  });

Now we must create a new service to inject for cookies and must not use the default document.cookie object.

import { Injectable, Inject, Optional } from '@angular/core';
import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens';
import { Request, Response } from 'express';

@Injectable({
  providedIn: 'root',
})
export class CookieService {
  cookies: any = {};
  document: any = { cookie: '' };
  constructor(
    @Optional() @Inject(REQUEST) private req: Request<any>,
    @Optional() @Inject(RESPONSE) private res: Response<any>
  ) {
    if (this.req !== null) {
      this.cookies = this.req.cookies;
    } else {
      this.document = document;
    }
  }

  getItem(name: string): string | null {
    const cookies: { [key: string]: string | null } = this._getPairs();
    if (name && typeof cookies[name] !== 'undefined') {
      return cookies[name];
    }

    return null;
  }

  setItem(
    name: string,
    value: string,
    expiry?: Date | string,
    path?: string
  ): boolean {
    if (!name) {
      return false;
    }

    if (!path) {
      path = '/';
    }

    if (this.req === null) {
      let expiryStr = '';

      if (expiry) {
        if (!(expiry instanceof Date)) {
          expiry = new Date(expiry);
        }
        expiryStr = '; expires=' + expiry.toUTCString();
      }

      this.document.cookie = `${name}=${value}${expiryStr}; path=${path}`;
    } else {
      if (expiry) {
        if (!(expiry instanceof Date)) {
          expiry = new Date(expiry);
        }
        const dt = new Date();
        if (expiry.getTime() <= dt.getTime()) {
          this.removeItem(name, path);
        } else {
          this.cookies[name] = value;
          this.res.cookie(name, value, {
            expires: expiry,
            path,
            encode: String,
          });
        }
      } else {
        this.cookies[name] = value;
        this.res.cookie(name, value, { path, encode: String });
      }
    }

    return true;
  }

  removeItem(name: string, path?: string): boolean {
    if (this.req !== null || !name) {
      return false;
    }

    if (!path) {
      path = '/';
    }

    if (this.req === null) {
      this.document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}`;
    } else {
      this.cookies[name] = null;
      const expiry = new Date('Thu, 01 Jan 1970 00:00:00 UTC');
      this.res.cookie(name, null, { expires: expiry, path, encode: String });
    }

    return true;
  }

  _getPairs(): { [key: string]: string | null } {
    if (this.req === null) {
      const parsed = this.document.cookie.split('; ');
      const cookies: { [key: string]: string | null } = {};

      parsed.forEach((element: string) => {
        if (element) {
          const pair = element.split('=');

          cookies[pair[0]] = typeof pair[1] !== 'undefined' ? pair[1] : null;
        }
      });

      return cookies;
    } else {
      return this.cookies;
    }
  }
}

Now this service will make sure that we are using cookies safely and will not let SSR server fail the request.

Try this service in your project and let me know if this helped you in any way.

Thanks for reading.

Leave a Comment