JavaScript developers frequently rely on browser globals like window, navigator, requestAnimationFrame, and location. Some of these have been part of the platform for a long time, others belong to the continually expanding set of Web APIs. Angular provides its own abstractions, such as the Location class and the DOCUMENT token. Let's explore why these exist and what they can teach us about building cleaner and more adaptive applications.
The DOCUMENT token
DOCUMENT is a built-in Angular token. Consider the following typical pattern:
constructor(@Inject(ElementRef) private readonly elementRef: ElementRef) {}
get isFocused(): boolean {
return document.activeElement === this.elementRef.nativeElement;
}
Angular offers an alternative approach:
constructor(
@Inject(ElementRef) private readonly elementRef: ElementRef,
@Inject(DOCUMENT) private readonly documentRef: Document,
) {}
get isFocused(): boolean {
return this.documentRef.activeElement === this.elementRef.nativeElement;
}
In the first snippet, a global variable is accessed directly. The second one injects the same object through the constructor. Rather than diving into theoretical principles or design patterns, let's focus on the concrete advantages of the second approach. A look at the token's definition in @angular/common shows its simplicity:
export const DOCUMENT = new InjectionToken<Document>('DocumentToken');
However, its actual value is provided in @angular/platform-browser (code snippet simplified):
{provide: DOCUMENT, useValue: document}
Adding BrowserModule to your application module registers a collection of implementations for built-in tokens, including RendererFactory2, Sanitizer, EventManager, and DOCUMENT. This design exists because Angular is a platform-agnostic framework. It relies heavily on dependency injection to work seamlessly across browsers, servers, and mobile environments. For comparison, let's inspect the ServerModule from another bundled platform (code snippet simplified):
{provide: DOCUMENT, useFactory: _document, deps: [Injector]},
// ...
function _document(injector: Injector) {
const config = injector.get(INITIAL_CONFIG);
const window = domino.createWindow(config.document, config.url);
return window.document;
}
Notice that it utilizes domino to construct a simulated document based on configuration obtained through DI. This is what powers server side rendering with Angular Universal. The primary benefit is already apparent: the DOCUMENT token functions flawlessly in SSR, whereas direct access to the global document would simply fail.
Other global entities
Angular handles document, but what about other globals? For instance, checking the browser via the [userAgent](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorID/userAgent) string typically involves navigator.userAgent. In the browser, this means accessing the global window object first, then its navigator property. Let's create a WINDOW token using a factory, which can be declared directly with the token:
export const WINDOW = new InjectionToken<Window>(
'An abstraction over global window object',
{
factory: () => inject(DOCUMENT).defaultView!
},
);
This setup allows us to inject WINDOW just like DOCUMENT. A similar approach gives us a NAVIGATOR token:
export const NAVIGATOR = new InjectionToken<Navigator>(
'An abstraction over window.navigator object',
{
factory: () => inject(WINDOW).navigator,
},
);
We can go a step further and create a dedicated
USER_AGENTtoken in the same manner. We'll see why this becomes useful shortly!
Sometimes a simple token isn't enough. Angular's Location class, for example, is a more developer-friendly wrapper around the native location object. Given our familiarity with RxJS streams, we can replace requestAnimationFrame with an Observable-based alternative:
export const ANIMATION_FRAME = new InjectionToken<
Observable<DOMHighResTimeStamp>
>(
'Shared Observable based on `window.requestAnimationFrame`',
{
factory: () => {
const performanceRef = inject(PERFORMANCE);
return interval(0, animationFrameScheduler).pipe(
map(() => performanceRef.now()),
share(),
);
},
},
);
The PERFORMANCE token follows an identical pattern, so we won't repeat it. We now have a single shared stream of timestamps driven by requestAnimationFrame, usable throughout the application. By converting everything to tokens, our components stop depending on implicitly available globals and instead receive all their dependencies explicitly through DI. This is a significant improvement.
Server Side Rendering
Consider a scenario where we need window.matchMedia('(prefers-color-scheme: dark)'). On the server, the WINDOW token may exist, but it likely doesn't implement the full Window API. Calling that method during SSR will probably raise an error like undefined is not a function. One strategy is to guard such calls with isPlatformBrowser checks, but that's not elegant. The real power of DI is the ability to override. Instead of special-casing these situations, we can provide a type-safe mock for WINDOW in app.server.module.ts that guards against accessing non-existent properties.
This reveals another key benefit: token values can be replaced. Testing components that depend on browser APIs becomes trivial, especially in environments like Jest where native implementations are unavailable. Mocks, however, can be dull. Sometimes we can provide genuine values. In SSR, the request object often contains the user agent. This is precisely why we created a separate USER_AGENT token: so we can source it independently. We can transform the request into a provider like so:
function provideUserAgent(req: Request): ValueProvider {
return {
provide: USER_AGENT,
useValue: req.headers['user-agent'],
};
}
This provider can be used in our server.ts when configuring Angular Universal:
server.get('*', (req, res) => {
res.render(indexHtml, {
req,
providers: [
{provide: APP_BASE_HREF, useValue: req.baseUrl},
provideUserAgent(req),
],
});
});
Node.js also offers its own Performance implementation, which we can supply server-side:
{
provide: PERFORMANCE,
useFactory: performanceFactory,
}
// ...
export function performanceFactory(): Performance {
return require('perf_hooks').performance;
}
For requestAnimationFrame, however, Performance isn't needed. We probably don't want our Observable chain to run on the server anyway, so we can provide EMPTY:
{
provide: ANIMATION_FRAME,
useValue: EMPTY,
}
By following this pattern, we can create tokens for every global object we use and provide appropriate alternatives for each target platform.
In conclusion
This technique results in well-abstracted code. Even if server-side rendering isn't currently a goal, it's a future possibility, and this approach ensures readiness. Additionally, testing becomes far simpler when dependencies can be swapped. We've packaged our common tokens into a small library:
[
ng-web-apis/common
A set of common utils for consuming Web APIs with Angular – ng-web-apis/common
GitHubng-web-apis

](https://github.com/ng-web-apis/common)
If you find something missing, feel free to file an issue! A companion package offers SSR-specific versions of these tokens:
[
ng-web-apis/universal
A counterpart to common package to be used with Angular Universal – ng-web-apis/universal
GitHubng-web-apis

](https://github.com/ng-web-apis/universal)
See a practical demonstration of these concepts in this Rick and Morty themed project by Igor Katsuba, which uses SSR. For a deeper look at Angular Universal challenges, read his article on the problems he encountered and how he solved them.
This pattern is what allowed our Angular components library Taiga UI to work smoothly with both Angular Universal and Ionic without additional configuration. I hope it proves just as valuable for your projects.
