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.)
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.