Search code examples
angularangular-servicesangular-dependency-injectionangular-ssr

DOCUMENT injection token for SSR angular service causes NotYetImplemented error


I have an Angular SSR app with a service that references document. I use the DOCUMENT injection token to provide document as a DI. Here is the repo: https://github.com/JakeLo123/ng-ssr

import { Inject, Injectable } from "@angular/core";
import { DOCUMENT } from "@angular/common";

@Injectable({
  providedIn: "root",
})
export class DocumentService {
  constructor(@Inject(DOCUMENT) private document: Document) {}

  get myCookie() {
    let cookies;

    try {
      cookies = this.document?.cookie;
    } catch (e) {
      // catch error if document is not defined
      console.error('Cookie error', e);
      return null;
    }

    const cookie = cookies
      .split('; ')
      .map((c) => c.split('='))
      .find(([name]) => name === 'my-cookie');

    return cookie?.[1] ?? null;
  }
}

I use the service in a component like so:

import { Component, inject } from "@angular/core";
import { DocumentService } from "./document-service/document.service";

@Component({
  selector: "app-root",
  standalone: false,
  templateUrl: "./app.component.html",
  styleUrl: "./app.component.css",
})
export class AppComponent {
  private service: DocumentService = inject(DocumentService);

  ngOnInit(): void {
    this.printCookie();
  }

  printCookie() {
    console.log("🍪", this.service.myCookie);
  }
}

The code in ngOnInit causes this error: Error: NotYetImplemented. My understanding is that the dependency injection strategy would allow me to reference document freely, so what am I missing here?

(This is a minimum reproducible example of a larger problem I'm working on.)


Solution

  • When you access the document in SSR, you get a custom object instead of the actual document.

    On the server, cookie does not exist, atleast the domino document created by angular doesn't have it.

    Instead we can check if we are on the server using either isPlatformBrowser or isPlatformServer and return undefined in these scenarios.

    DOM in Angular SSR with Domino package.

    import { Inject, Injectable, Optional, PLATFORM_ID } from '@angular/core';
    import { DOCUMENT, isPlatformBrowser } from '@angular/common';
    
    @Injectable({
      providedIn: 'root',
    })
    export class DocumentService {
      isBrowser: boolean;
    
      constructor(
        @Optional() @Inject(DOCUMENT) private document: Document,
        @Inject(PLATFORM_ID) platformId: Object
        ) {
          this.isBrowser = isPlatformBrowser(platformId);
        }
    
      get myCookie() {
        let cookies;
        if(this.isBrowser) {
          try {
            cookies = this.document.cookie;
          } catch (e) {
            // catch error if document is not defined
            console.error('Cookie error', e);
            return null;
          }
      
          const cookie = cookies
            .split('; ')
            .map((c) => c.split('='))
            .find(([name]) => name === 'my-cookie');
      
          return cookie?.[1] ?? null;
        }
        return undefined;
      }
    }
    

    The code was configured incorrectly in your version, which looked like a mix of standalone and modular approach. This is wrong because we can use only either one, not both.

    I have made few changes to the github repo, do check it out.

    Github repo

    Stackblitz Demo SSR