Skip to content

Pattern di Trasformazione Pratici ​

Gli operatori di trasformazione sono uno dei gruppi di operatori più frequentemente usati in RxJS. Giocano un ruolo essenziale nella programmazione reattiva per elaborare e trasformare flessibilmente i dati.

In questa sezione, organizzeremo i pattern di utilizzo degli operatori di trasformazione presentando esempi pratici tipici.

💬 Pattern di Utilizzo Tipici ​

PatternOperatori RappresentativiDescrizione
Conversione semplice di valorimapApplica funzione di conversione a ogni valore
Accumulo e aggregazionescan, reduceAccumulo sequenziale di valori
Elaborazione asincrona annidatamergeMap, switchMap, concatMap, exhaustMapGenera e combina Observable
Elaborazione batch e raggruppamentobufferTime, bufferCount, windowTimeElaborazione collettiva e gestione partizionamento
Estrazione proprietàpluckEstrai campi specifici da oggetti

Validazione e Conversione Input Utente ​

ts
import { fromEvent } from 'rxjs';
import { map, debounceTime, distinctUntilChanged } from 'rxjs';

// Campo di input
const emailInput = document.createElement('input');
const emailStatus = document.createElement('p');
document.body.appendChild(emailInput);
document.body.appendChild(emailStatus);

// Funzione di validazione email
function isValidEmail(email: string): boolean {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(email);
}

// Elaborazione input
fromEvent(emailInput, 'input')
  .pipe(
    debounceTime(400),
    map((event) => (event.target as HTMLInputElement).value.trim()),
    distinctUntilChanged(),
    map((email) => {
      if (!email) {
        return {
          isValid: false,
          message: 'Inserisci un indirizzo email',
          value: email,
        };
      }

      if (!isValidEmail(email)) {
        return {
          isValid: false,
          message: 'Inserisci un indirizzo email valido',
          value: email,
        };
      }

      return {
        isValid: true,
        message: "L'indirizzo email è valido",
        value: email,
      };
    })
  )
  .subscribe((result) => {
    if (result.isValid) {
      emailStatus.textContent = '✓ ' + result.message;
      emailStatus.className = 'valid';
    } else {
      emailStatus.textContent = '✗ ' + result.message;
      emailStatus.className = 'invalid';
    }
  });

Conversione e Aggregazione Array di Oggetti ​

ts
import { from } from 'rxjs';
import { map, toArray } from 'rxjs';

// Dati di vendita
const sales = [
  { product: 'Laptop', price: 120000, quantity: 3 },
  { product: 'Tablet', price: 45000, quantity: 7 },
  { product: 'Smartphone', price: 85000, quantity: 4 },
  { product: 'Mouse', price: 3500, quantity: 12 },
  { product: 'Tastiera', price: 6500, quantity: 8 },
];

// Conversione e aggregazione dati
from(sales)
  .pipe(
    // Calcola importo totale per ogni prodotto
    map((item) => ({
      product: item.product,
      price: item.price,
      quantity: item.quantity,
      total: item.price * item.quantity,
    })),
    // Aggiungi prezzo con IVA inclusa
    map((item) => ({
      ...item,
      totalWithTax: Math.round(item.total * 1.1),
    })),
    // Riconverti in array
    toArray(),
    // Calcola totale complessivo
    map((items) => {
      const grandTotal = items.reduce((sum, item) => sum + item.total, 0);
      const grandTotalWithTax = items.reduce(
        (sum, item) => sum + item.totalWithTax,
        0
      );
      return {
        items,
        grandTotal,
        grandTotalWithTax,
      };
    })
  )
  .subscribe((result) => {
    console.log('Dettagli prodotto:', result.items);
    console.log('Totale complessivo (IVA esclusa):', result.grandTotal);
    console.log('Totale complessivo (IVA inclusa):', result.grandTotalWithTax);
  });
// Output:
// Dettagli prodotto: (5) [{…}, {…}, {…}, {…}, {…}]
// Totale complessivo (IVA esclusa): 1109000
// Totale complessivo (IVA inclusa): 1219900

Normalizzazione Dati JSON ​

ts
import { ajax } from 'rxjs/ajax';
import { map } from 'rxjs';

const resultBox = document.createElement('div');
resultBox.id = 'normalized-results';
document.body.appendChild(resultBox);

ajax
  .getJSON<any[]>('https://jsonplaceholder.typicode.com/users')
  .pipe(
    map((users) => {
      // Converti in oggetto con ID come chiave
      const normalizedUsers: Record<number, any> = {};
      const userIds: number[] = [];

      users.forEach((user) => {
        normalizedUsers[user.id] = {
          ...user,
          // Appiattisci oggetti annidati
          companyName: user.company.name,
          city: user.address.city,
          street: user.address.street,
          // Rimuovi annidamento non necessario
          company: undefined,
          address: undefined,
        };
        userIds.push(user.id);
      });

      return {
        entities: normalizedUsers,
        ids: userIds,
      };
    })
  )
  .subscribe((result) => {
    const title = document.createElement('h3');
    title.textContent = 'Dati Utente Normalizzati';
    resultBox.appendChild(title);

    result.ids.forEach((id) => {
      const user = result.entities[id];
      const div = document.createElement('div');
      div.innerHTML = `
      <strong>${user.name}</strong><br>
      Username: @${user.username}<br>
      Email: ${user.email}<br>
      Azienda: ${user.companyName}<br>
      Indirizzo: ${user.city}, ${user.street}<br><br>
    `;
      resultBox.appendChild(div);
    });

    // Accesso rapido all'utente per ID specifico
    console.log('Utente ID 3:', result.entities[3]);
  });

Combinazione di Trasformazioni Multiple ​

Nelle applicazioni reali, è comune usare una combinazione di più operatori di trasformazione.

ts
import { fromEvent, timer } from 'rxjs';
import {
  switchMap,
  map,
  tap,
  debounceTime,
  takeUntil,
  distinctUntilChanged,
} from 'rxjs';
import { ajax } from 'rxjs/ajax';

type User = {
  id: number;
  name: string;
  username: string;
  email: string;
  company: {
    name: string;
  };
};

// Input di ricerca
const searchInput = document.createElement('input');
const resultsContainer = document.createElement('p');
const loadingIndicator = document.createElement('p');

document.body.append(searchInput);
document.body.append(resultsContainer);
document.body.append(loadingIndicator);

// Elaborazione ricerca
fromEvent(searchInput, 'input')
  .pipe(
    // Ottieni valore di input
    map((event) => (event.target as HTMLInputElement).value.trim()),
    // Attendi 300ms
    debounceTime(300),
    // Ignora stesso valore
    distinctUntilChanged(),
    // Mostra caricamento
    tap(() => {
      loadingIndicator.style.display = 'block';
      resultsContainer.innerHTML = '';
    }),
    // Richiesta API (cancella richiesta precedente)
    switchMap((term) => {
      // Input vuoto restituisce nessun risultato
      if (term === '') {
        return [];
      }

      // Gestione timeout (5 secondi)
      const timeout$ = timer(5000).pipe(
        tap(() => console.warn('Timeout risposta API')),
        map(() => [{ error: 'Timeout' }])
      );

      // Chiamata API
      const response$ = ajax
        .getJSON(
          `https://jsonplaceholder.typicode.com/users?username_like=${term}`
        )
        .pipe(
          // Elabora risultati
          map((users) =>
            (users as User[]).map((user) => ({
              id: user.id,
              name: user.name,
              username: user.username,
              email: user.email,
              company: user.company.name,
            }))
          ),
          // Completa prima del timeout
          takeUntil(timeout$)
        );

      return response$;
    }),
    // Nascondi caricamento
    tap(() => {
      loadingIndicator.style.display = 'none';
    })
  )
  .subscribe((result) => {
    loadingIndicator.style.display = 'none';

    if (Array.isArray(result)) {
      if (result.length === 0) {
        resultsContainer.innerHTML =
          '<div class="no-results">Nessun utente trovato</div>';
      } else {
        resultsContainer.innerHTML = result
          .map(
            (user) => `
          <div class="user-card">
            <h3>${user.name}</h3>
            <p>@${user.username}</p>
            <p>${user.email}</p>
            <p>Azienda: ${user.company}</p>
          </div>
        `
          )
          .join('');
      }
    } else {
      resultsContainer.innerHTML = `<div class="error">⚠️ ${result}</div>`;
    }
  });
  • Per conversioni semplici, usa map
  • Per gestire operazioni asincrone, usa mergeMap, switchMap, concatMap, exhaustMap
  • Per elaborazione batch, usa bufferTime, bufferCount
  • Per estrazione proprietà, usa pluck
  • Nelle app reali, combinare questi operatori è la norma

Una volta padroneggiati gli operatori di trasformazione, puoi gestire flussi di dati asincroni complessi in modo intuitivo e dichiarativo!

Pubblicato sotto licenza CC-BY-4.0.