Angular 5 - How to access Window, Document and other browser types in Angular Universal

Angular 5 - How to use LocalStorage, Window, Document and other browser types in Angular Universal

A short while ago I wrote this post explaining how to properly access browser types - such as localStorage - from an Angular Universal web application using a situational approach built upon the PLATFORM_ID  token and the isPlatformBrowser  & isPlatformServer  methods. Although that workaround might definitely point us in the right direction to write isomorphic JavaScript code, it won't automatically make us write good Angular code... unless we understand something else.

The first thing we need to consider is that accessing global variables within an Angular component is never the proper way of doing things, regardless of the executing context. As a matter of fact it's always a bad practice, as it goes against the Angular DI pattern, even if we do that when these variables are available.

To better understand that, let's pick the following code:

Is this code isomorphic? YesWill it work fine in an Universal web app? YesCan it be considered a good piece of Angular code? No.

Writing isomorphic code and writing decent Angular code are two completely different things. In the above code we're not injecting anything that contains the localStorage  object, we're just trying to access it directly as a global variable: as a general rule we could say that, when we're looking at an Angular component code, any global access means that there's something wrong. There are no exceptions here: it could work fine, it could get us where we want to, it could even be the only way to get the job done, yet it's still wrong for a number of reasons, including the following:

  • It could cause issues with some TypeScript compiler, expecially if they are configured to perform strict type checks with no browser and/or generic types support.
  • It will cause issues on some AoT compilers, including those who don't exist yet but we might choose (o be required) to adopt in future.
  • It will not give us testable code, thus making it harder to understand what's going on.

What's the proper way to deal with these types then? If we want to stick to the Angular DI-based pattern, the best thing we can do is inject an adapter for such types that will work for both the server-side and client-side executing contexts with specific behaviours. That adapter can either be a function, a custom-made class or anything else, depending on our scenario. For the localStorage  object a function returning an untyped option should be more than enough:

We can then put this function in our AppModule file and then use it as a factory for a LOCALSTORAGE provider in the following way:

and then use it as a factory for a LOCALSTORAGE provider in the following way:

If we have different AppModule files for the browser and for the server, we can safely use this function in the   app.module.shared.ts  file, unless we want to enforce completely different behaviours for the server and client contexts. If that's the case, it could be wiser to implement a custom class factory with a getValue()  method that will conditionally operate and return something depending on the executing platform type, which you can determine checking the PLATFORM_ID  token against the   isPlatformBrowser  method like already explained here.

Once done, we can inject the LOCALSTORAGE generic object in any of our Angular components and check for the platform type before using it:

It's worth noting that, since our getLocalStorage()  function will return null if the window object isn't available, we could just check for this.localStorage  nullability and entirely skip the platform type check. However, the above one is the recommended approach: we can't take for granted that function return value, as its implementation might be subject to change in the future; conversely, the isPlatformBrowser  / isPlatformServer  return values are something that we can always trust by design.

If you want to know more about .NET Core and Angular check out the ASP.NET Core 2 and Angular 5 book, available in paperback and/or digital format. Promo Code: ASPCA50 to get it with a 50% discount! The book's latest edition, updated to ASP.NET Core 5 and Angular 11, is available here.

That's it, at least for now: happy coding!

 

About Ryan

IT Project Manager, Web Interface Architect and Lead Developer for many high-traffic web sites & services hosted in Italy and Europe. Since 2010 it's also a lead designer for many App and games for Android, iOS and Windows Phone mobile devices for a number of italian companies. Microsoft MVP for Development Technologies since 2018.

View all posts by Ryan

3 Comments on “Angular 5 - How to access Window, Document and other browser types in Angular Universal”

  1. Pingback: Angular 5 - Use LocalStorage in Angular Universal (Isomorphic JavaScript)
  2. How would you explain that such behaviour has not been anticipated by Angular Universal ?
    It seems pretty obvious that one would need to save data in the browser using SSR.
    Many thanks for the great article.

Leave a Reply

Your email address will not be published. Required fields are marked *


The reCAPTCHA verification period has expired. Please reload the page.

This site uses Akismet to reduce spam. Learn how your comment data is processed.