facebook

Blog

Resta aggiornato

Aspettando il nuovo Edge con Chromium, cerchiamo di capire come supportare IE in Angular 8
Come supportare Internet Explorer in Angular 8
martedì 30 Luglio 2019

Per qualche strana ragione, il mondo non ha ancora dimenticato Internet Explorer. Se siete sviluppatori front-end, questa è una realtà davvero stressante, specialmente se Angular è il vostro framework di front-end preferito.

Come probabilmente già sapete, Angular supporta Internet Explorer fino alla versione 9, ma bisogna usare alcuni script “polyfill”. Se usate la CLI, e spero che non stiate ancora creando nuovi progetti senza la CLI, avete già il polyfills.ts nella vostra cartella src. Questo file contiene alcuni polyfill obbligatori, come “zone.js” e altri import opzionali, commentati di default, sulla base delle necessità del vostro progetto.

Nell’ultima major release (8.x.x) di Angular, il team ha introdotto una nuova feature, chiamata “differential loading” che crea due pacchetti quando buildate il vostro progetto: uno per i browser moderni, usando la sintassi ES2015 senza polyfills, e un altro, più grande del primo, che contiene i polyfill e usa la sintassi ES5 per supportare i vecchi browser.

Come funziona questa feature? Come fa il browser a scegliere il pacchetto corretto? È più semplice di quanto si pensi: index.html punta tutti i file e usa gli attributi nomodule e type=”module” per capire se il browser supporta i moduli ES. Tutti i browser moderni scaricano solo gli script con l’attributo type=”module”, mentre quelli legacy non conoscono questo attributo e scaricano i file con l’attributo nomodule. A dire il vero, alcuni browser meno recenti scaricano tutti i file, ma eseguono solo quelli corretti.

Questa feature funziona perfettamente con il comando build, ma con i comandi servetest e e2e, vengono creati solo i bundle es2015. Quindi, se eseguiamo il classico comando ng serve, per testare la nostra applicazione in modalità di sviluppo, non possiamo farlo con Internet Explorer. Possiamo risolvere questo problema modificando la proprietà “target” del nostro tsconfig.json su “es5” anziché “es2015”, ma se vogliamo mantenere entrambe le configurazioni, possiamo aggiungere un “tsconfig-es5.app.json” nella cartella principale con questa configurazione:

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

Ora possiamo usare l’angular.json per aggiungere una nuova configurazione al nostro progetto. Nelle sezioni “build” e “serve”, dobbiamo aggiungere una nuova configurazione “es5”, per specificare il nuovo tsconfig creato e il suo utilizzo nel comando “serve”:

"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"
        }
    }
}

Adesso possiamo eseguire il nostro progetto in modalità di sviluppo con il comando:

ng serve --configuration es5

Quindi il problema è risolto? Non abbiamo bisogno di nient’altro? Sarebbe meraviglioso ma non è così. Dobbiamo comunque testare la nostra applicazione su Internet Explorer. Se siamo su piattaforma Windows, possiamo avviare Internet Explorer e testare la nostra applicazione, mentre se ci troviamo su un Mac e abbiamo una licenza di Parallels, possiamo seguire le istruzioni di uno dei miei articoli (Debuggare Angular in Windows con Parallels) ed eseguire il server su Mac, testando l’applicazione su un’istanza virtualizzata di Windows.

Usando l’emulazione di Explorer, possiamo testare qualsiasi vecchia versione del browser:

Facciamo un esempio. Spesso può essere utile utilizzare l’attributo hidden invece di *ngIf per nascondere un pezzo di HTML, sia per motivi funzionali che di prestazioni. Se vogliamo usare un pulsante per mostrare o nascondere un elemento DIV con l’attributo hidden, possiamo scrivere questo semplice codice:

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

Questo funziona senza problemi con qualsiasi browser moderno e in IE11, ma non con IE10.

Abbiamo bisogno di un polyfill aggiuntivo per aggiungere la funzionalità hidden a IE10, ma, per implementarla, dobbiamo capire qual è il problema. Se applichiamo manualmente l’attributo nascosto all’elemento HTML, il pezzo dell’interfaccia utente viene nascosto correttamente. Il problema non è l’attributo hidden, ma il binding di Angular che non aggiunge l’attributo se non esiste per l’elemento. Quindi, dobbiamo aggiungere l’attributo “hidden” agli HTMLElement del browser quando non esistono, inserendo, ad esempio, questo codice in un hidden-attribute-polyfill.ts e importandolo nel polyfills.ts principale:

((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) {
   return;
 }
 
 Object.defineProperty(HTMLElement.prototype, 'hidden', {
   get() {
     return this.hasAttribute('hidden');
   },
   set(value) {
     if (value) {
       this.setAttribute('hidden', '');
     } else {
       this.removeAttribute('hidden');
     }
 
     return value;
   },
 });
})(typeof window === 'undefined' ? this : window);

Adesso funziona:

Morale della favola: bisogna sempre testare il proprio codice perché, come dice un mantra TDD, “tutto il codice è colpevole fino a prova contraria!”.

Trovate il codice di esempio qui: 

https://github.com/apomic80/angular8-and-internet-explorer

Happy coding e buona estate! Ci vediamo a settembre!