Platform specific globals and providers
Last modified on Fri 17 Mar 2023

Because a Universal application runs the codebase in both the browser and in node, we have to safe-guard access to platform-specific APIs and create abstractions around them.

For example, server-side applications can't reference browser-only global objects such as window, document, navigator, or location. Likewise, the part which runs in the browser can not access globals like process, or the fs API.

Angular provides some injectable abstractions over these objects, such as Location or DOCUMENT; it might substitute adequately for these APIs. If Angular doesn't provide it, it's possible to write new abstractions that delegate to the browser APIs while in the browser and to an alternative implementation while on the server (also known as shimming).

A crucial part in creating these abstractions is the ability to check on which platform the codebase is currently running.

Here is an example of how to provide the Window object only on client-side:

{
    provide: WINDOW,
        useFactory: (platform: PlatformService) => {
        if (!platform.isBrowser) {
            return undefined;
        }

        return window;
    },
        deps: [PlatformService]
}

We can then inject WINDOW anywhere where we would like to use it. Note that the reference will be undefined on server-side.

constructor(@Inject(WINDOW) @Optional() private readonly window?: Window) {}

Platform Service

PlatformService is a service used to check which platform the codebase is running in. It is just a simple wrapper around Angular's PLATFORM_ID token and isBrowser and isPlatformServer functions:

@Injectable({
    providedIn: 'root',
})
export class PlatformService {
    constructor(@Inject(PLATFORM_ID) private readonly platformId: Record<string, any>) {}

    public get isBrowser(): boolean {
        return isPlatformBrowser(this.platformId);
    }

    public get isServer(): boolean {
        return isPlatformServer(this.platformId);
    }
}