Stay updated

Waiting for the new Edge with Chromium, let’s try to understand how to support IE in Angular 8
How to support Internet Explorer in Angular 8
Tuesday, July 30, 2019

For some strange reason, the world has not yet forgotten Internet Explorer. If you’re a front-end developer, this is a very stressful truth, especially if Angular is your favorite front-end framework.

As you probably know, Angular supports Internet Explorer until version 9, but you need to use some “polyfill” scripts. If you use the CLI, and I hope that you are not starting projects without CLI still, you already have a polyfills.ts in your src folder. This file contains some mandatory polyfills like “zone.js”, and some optional imports, commented by default, based on the needs of your project.

In the 8.x.x release of Angular, the team introduced a new feature named “differential loading”, that creates two packages when you build your project: one for the modern browser using the ES2015 syntax with no need of polyfills, and one, larger than the first, that contains polyfills and uses the ES5 syntax to support old browsers.

How does this feature work? How the browser chooses the correct bundle? It is simpler than you think: the index.html points all the files and uses the attributes nomodule and type=”module” to understand if the browser support ES modules. All modern browsers only download the scripts with the attribute type=”module”. Legacy browsers, instead, do not know this attribute and download files with nomodule attributes. Actually, some old browsers download all the files, but they only execute the correct ones.

This feature works fine with the build command, but with the servetest and e2e commands, only the es2015 bundles are created. So, if we execute the classic ng serve command to test our application in development mode, we can’t test it with Internet Explorer. We can solve this problem changing our tsconfig.json “target” property to “es5” instead of “es2015”, but if we want to maintain both the configurations we can add a “tsconfig-es5.app.json” in our root folder with this configuration:

 "extends": "./tsconfig.app.json",
 "compilerOptions": {
   "target": "es5"

Now we can use the angular.json to add a new configuration to our project. In the “build” and “serve” sections, we need to add a new “es5” configuration, to specify the new tsconfig created, and the use of it in the “serve” command:

"build": {
    "builder": "@angular-devkit/build-angular:browser",
    "options": { ...
    "configurations": {
        "production": { ...
        "es5": {
            "tsConfig": "./tsconfig-es5.app.json"
"serve": {
    "builder": "@angular-devkit/build-angular:dev-server",
    "options": { ...
    "configurations": {
        "production": { ...
        "es5": {
            "browserTarget": "IEdemo:build:es5"

We can then run our project in development mode with the command:

ng serve --configuration es5

Is the problem solved therefore? Do we need anything else? It would be wonderful but it’s not so. We still need to test our application on Internet Explorer. If we are on a Windows platform, we can start Internet Explorer and test our application, whereas if we are on a Mac and we have a Parallels license, we can follow the instructions of one of my articles (Debugging Angular in Windows with Parallels) and run the server on Mac, testing the application on a virtualized instance of Windows.

Using the Explorer emulation feature, we can test any old version of the browser:

Let’s take an example. Usually, it can be convenient to use the attribute hidden instead of *ngIf to hide a piece of HTML, both for functional and for performance reasons. If we want to use a button to show or to hide a DIV element with the hidden attribute, we can write this simple code:

<button (click)="elementVisible = !elementVisible">
<div [hidden]="elementVisible">
   Welcome to {{ title }}!

It works without problems with any modern browser and in IE11, but it doesn’t on IE10.

We need an additional polyfill to add the hidden functionality to IE10, but, to implement it, we need to understand why hidden doesn’t work. If we apply manually the hidden attribute to the HTML element, the piece of the UI hides correctly. The problem is not the attribute hidden, but the Angular binding that doesn’t add the attribute if it doesn’t exist for the element. So, we need to add “hidden” attribute to the HTMLElement of the browser if it doesn’t exist, placing, for example, this code in an hidden-attribute-polyfill.ts and importing it in the main polyfills.ts:

((global: any) => {
 'use strict';
 const notInBrowser = !global.HTMLElement || !HTMLElement.prototype;
 const alreadyDefined = 'hidden' in HTMLElement.prototype;
 const notPossibleToImplement = typeof Object.defineProperty === 'undefined';
 if (notInBrowser || alreadyDefined || notPossibleToImplement) {
 Object.defineProperty(HTMLElement.prototype, 'hidden', {
   get() {
     return this.hasAttribute('hidden');
   set(value) {
     if (value) {
       this.setAttribute('hidden', '');
     } else {
     return value;
})(typeof window === 'undefined' ? this : window);

Now it works:

Moral of the story: you always have to test your code, because as a TDD mantra says “all the code is guilty until proven innocent!”.

You can find the sample code here:


Happy coding and have a nice summer! See you in September!