Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular Universal and browser feature checks

When developing a web app with jQuery or normal JavaScript, it is commonplace to check for feature availability first. So for example, if I want to use the document.oncopy event, I should first have something like this to ensure my code doesn't break for lesser browsers:

if ("oncopy" in document) {
  // Feature is available
}

I'm a bit puzzled about how this would work in Angular2. I could still use the same if I expect to only run in the browser, but I'm specifically told to leave the DOM alone if I want to use Angular Universal and depend on templates or the DomRenderer instead. This allows the page to be pre-rendered on the server and provides a truly impressive performance gain.

But suppose I want a specific div to be invisible if the document.oncopy is unavailable. My understanding is that this is not recommended:

<div *ngIf="hasFeature()">...</div>

and

hasFeature() {
  return 'oncopy' in document;
}

because then I'm still manipulating the DOM. Note that my example is about the document.oncopy but I could choose any feature whatsoever that doesn't have universal support.

I tested this using Chris Nwamba's tutorial on Scotch and added the following to the end of his Home template:

<div *ngIf="hasFeature()">Feature is supported</div>
<div *ngIf="!hasFeature()">Feature is NOT supported</div>

Update: Interestingly, it gave different results on different browsers. On Chrome 55, it executed as it would normally and showed the "Feature is supported" message. On IE11, I received the "not supported" message. In both instances the server log shows a EXCEPTION: document is not defined message, but the page still seems perfectly okay.

So what is the correct way to check for browser features if I want to use Angular Universal?

Update: I also toyed around with using a field in the template and assigning that field from one of the life cycle hooks. ngAfterContentInit seemed like a fine candidate, but also causes an error on the server. It still runs fine in the browser with no weird effects (that I have noticed so far).

like image 557
Cobus Kruger Avatar asked Nov 08 '22 03:11

Cobus Kruger


1 Answers

There are two ways to approach this:

  1. Do the check only once the server is done rendering and the client is completely initialised (including the replay of user events done by preboot.js).
  2. Return a reasonable default when the page is running on the server and perform the actual check only in the browser.

I started looking at the first option, but none of the Angular2 life cycle events will help with this. In fact, you can clearly see them all executing on the server and only then on the client.

I then started looking for something usable in preboot.js but quickly realised it was more complex than it needed to be.

So onto option 2 I went. It turns out checking for the browser is as easy as importing and checking isBrowser.

import { isBrowser } from "angular2-universal";

@Component({
 // All the usual stuff
})
export class MyComponent {
  // ...
  hasFeature(): boolean {
    return isBrowser && 'oncopy' in document;
  }
  // ...
}

And then use the template as I showed in the question.

To check if you're running on the server, import and use isNode in exactly the same way. There doesn't seem to be an obvious way to distinguish between Node and ASP.NET Core, but perhaps it's best not to write too much code that specific to platform.

like image 75
Cobus Kruger Avatar answered Nov 14 '22 22:11

Cobus Kruger