Una feature molto utile quando stiamo presentando una serie di dati all'utente è quella di poterli scaricare direttamente sul proprio PC, magari in un formato pratico e leggero come il CSV, per poterli utilizzare offline e con l'ausilio di altri programmi dedicati, ad esempio l'onnipresente Excel.

Normalmente abbiamo quindi un bel tasto di download che effettua una chiamata al server, che con gli header appropriati ci restituisce lo stream di dati che il browser ci permette di salvare come file in locale; questa è sicuramente la via migliore, più semplice da controllare e gestire (sia in caso di successo che di errore) nonchè universalmente supportato.

Con l'evolversi nel tempo dell'EcmaScript è diventato possibile, tuttavia, avere una funzionalità simile senza dover ricorrere ad un server esterno, lasciando tutto l'onere di creare lo stream di dati a JavaScript ed il merito di tutto ciò va all'oggetto Blob, di cui l'oggetto File è un sottoinsieme.

"Vanilla" javascript

Immaginiamo di avere, ad esempio, questo semplice array di oggetti:

[
  {'col1' : 'val1', 'col2' : 'val2', 'col3' : 'val3', 'col4': 'val4'},
  {'col1' : 'val11', 'col2' : 'val22', 'col3' : 'val33', 'col4': 'val44'},
  {'col1' : 'val111', 'col2' : null, 'col3' : 'val333', 'col4': 'val444'},
  {'col1' : 'val_1', 'col2' : 'val_2', 'col3' : 'val_3', 'col4': 'val_4'},
  {'col1' : 'val_11', 'col2' : 'val_22', 'col3' : 'val_33', 'col4': 'val_44'}
]

che magari è rappresentato sulla pagina da una tabella:

Col1Col2Col3Col4
val1val2val3val4
val11val22val3val44
val111val333val444
val_1val_2val_3val_4
val_11val_22val_33val_44

Usando del semplice javascript possiamo creare una funzione come questa richiamabile al click su un <button>

function download() {
 let a = document.createElement('a');
 let blob = new Blob([my_csv_data], {type: 'text/csv' }),
 url = window.URL.createObjectURL(blob);
 a.href = url;
 a.download = "reports.csv";
 a.click();
 window.URL.revokeObjectURL(url);
 a.remove();
}

che non fa altro che creare "al volo" un elemento <a> che ha come attributo href uno stream di dati che vive per la sola durata della richiesta, generato dal metodo window.URL.createObjectURL() a partire dai dati binari contenuti, appunto, nell'oggetto Blob() che abbiamo creato con i valori che vogliamo mostrare; la proprietà download contiene il nome che daremo file, mentre il resto del codice non è altro che un trigger per l'evento "click", la distruzione dell'oggetto url e la rimozione dell'elemento dal DOM.

Insomma, Blob() prende un array (di stringhe, un ArrayBuffer, ecc.) e lo converte in stream di dati, usando il MIME specificato come secondo argomento (poteva anche essere "plain/text", o "application/json", ecc.).

Ma come facciamo a convertire il JSON originario in CSV?

function jsonToCsv(jsonData) {
   const header = Object.keys(data[0]);
   let csv = jsonData.map(row => header.map(fieldName => JSON.stringify(row[fieldName], replacer)).join(',')); // separatore per i valori
   csv.unshift(header.join(',')); // separatore per l'header
   return csv.join('\r\n'); // terminatore di riga
}

Questa funziona itera sugli elementi dell'array di oggetti, creando l'intestazione del csv con le chiavi del primo record ed unendo i valori di quelli successivi con il separatore di campo ed il terminatore di riga specificati (nell'esempio sopra si potrebbero parametrizzare questi valori, come possibile miglioria).
replacer invece è un callback usato per effettuare trasformazioni sul valore passato, come trasformare un null in stringa vuota o qualunque altra cosa vorremmo fare.

Angular 7+

Per semplificarci la vita, e per ridurre problematiche di compatibilità (che è comunque buona, https://developer.mozilla.org/en-US/docs/Web/API/Blob), possiamo affidarci alla libreria file-saver:

npm install file-saver --save

Ricordiamoci poi che se stiamo usando TypeScript dobbiamo installare anche la definizione del @type corrispondente:

npm install @types/file-saver --save-dev

Questa libreria mette a disposizione un'interfaccia saveAs per cui basta passargli l'oggetto Blob ed il nome del file.
Et voilà: