Skip to content

partition - dividi in due stream su condizione

partition è una Funzione di Creazione che divide un Observable in due Observable basandosi su una condizione. Puoi specificare la condizione con una funzione predicato (predicate) e ottenere i valori che soddisfano la condizione e i valori che non la soddisfano come stream separati.

Sintassi e utilizzo base

ts
import { partition, of } from 'rxjs';

const source$ = of(1, 2, 3, 4, 5, 6);

// Dividi in numeri pari e dispari
const [evens$, odds$] = partition(source$, (value) => value % 2 === 0);

evens$.subscribe((value) => console.log('Pari:', value));
// Output: Pari: 2, Pari: 4, Pari: 6

odds$.subscribe((value) => console.log('Dispari:', value));
// Output: Dispari: 1, Dispari: 3, Dispari: 5
  • partition restituisce un array contenente due Observable.
  • [0]: uno stream di valori che soddisfano la condizione.
  • [1]: uno stream di valori che non soddisfano la condizione.

🌐 Documentazione Ufficiale RxJS - partition

Pattern di utilizzo tipici

  • Elaborazione separata di successo/fallimento (ordinamento per codice di stato HTTP)
  • Classificazione eventi (click sinistro/click destro)
  • Classificazione dati (valido/invalido, adulto/bambino, ecc.)
  • Divisione stream basata su condizioni.

Esempi di codice pratici (con UI)

Quando viene cliccato un pulsante, il processo si ramifica a seconda che le coordinate del click siano per la metà sinistra o destra dello schermo.

ts
import { partition, fromEvent } from 'rxjs';
import { map } from 'rxjs';

// Crea area output
const leftArea = document.createElement('div');
leftArea.innerHTML = '<h3>Click Sinistro</h3><ul id="left-list"></ul>';
leftArea.style.float = 'left';
leftArea.style.width = '45%';
leftArea.style.padding = '10px';
leftArea.style.background = '#e3f2fd';
document.body.appendChild(leftArea);

const rightArea = document.createElement('div');
rightArea.innerHTML = '<h3>Click Destro</h3><ul id="right-list"></ul>';
rightArea.style.float = 'right';
rightArea.style.width = '45%';
rightArea.style.padding = '10px';
rightArea.style.background = '#fce4ec';
document.body.appendChild(rightArea);

// Eventi click
const clicks$ = fromEvent<MouseEvent>(document, 'click');

// Coordinata X centrale dello schermo
const centerX = window.innerWidth / 2;

// Dividi in metà sinistra e destra
const [leftClicks$, rightClicks$] = partition(
  clicks$,
  (event) => event.clientX < centerX
);

// Elabora click sinistri
leftClicks$.pipe(
  map(event => ({ x: event.clientX, y: event.clientY }))
).subscribe((pos) => {
  const leftList = document.getElementById('left-list')!;
  const li = document.createElement('li');
  li.textContent = `Posizione: (${pos.x}, ${pos.y})`;
  leftList.appendChild(li);
});

// Elabora click destri
rightClicks$.pipe(
  map(event => ({ x: event.clientX, y: event.clientY }))
).subscribe((pos) => {
  const rightList = document.getElementById('right-list')!;
  const li = document.createElement('li');
  li.textContent = `Posizione: (${pos.x}, ${pos.y})`;
  rightList.appendChild(li);
});
  • Cliccando sullo schermo verrà registrato nelle liste sinistra e destra secondo la posizione del click.
  • Due stream indipendenti possono essere creati da un'unica sorgente.

Esempio pratico: Elaborazione ramificata delle risposte API

Esempio di divisione successo e fallimento per codice di stato HTTP

ts
import { partition, from, of } from 'rxjs';
import { mergeMap, map, catchError, share } from 'rxjs';

interface ApiResponse {
  status: number;
  data?: any;
  error?: string;
}

// Chiamate API fittizie
const apiCalls$ = from([
  fetch('/api/users/1'),
  fetch('/api/users/999'), // Utente inesistente
  fetch('/api/users/2'),
]);

// Elabora Response e converti in ApiResponse
const responses$ = apiCalls$.pipe(
  mergeMap(fetchPromise => from(fetchPromise)),
  mergeMap(response =>
    from(response.json()).pipe(
      map(data => ({
        status: response.status,
        data: response.ok ? data : undefined,
        error: response.ok ? undefined : (data.message || 'Errore')
      } as ApiResponse)),
      catchError(err => of({
        status: response.status,
        data: undefined,
        error: err.message || 'Impossibile analizzare la risposta'
      } as ApiResponse))
    )
  ),
  share() // Gestisci 2 subscription da partition
);

// Dividi in successo (200s) e fallimento (altri)
const [success$, failure$] = partition(
  responses$,
  (response: ApiResponse) => response.status >= 200 && response.status < 300
);

// Gestisci risposte di successo
success$.subscribe((response) => {
  console.log('✅ Successo:', response.data);
  // Visualizza dati successo nella UI
});

// Gestisci risposte di fallimento
failure$.subscribe((response) => {
  console.error('❌ Fallimento:', response.error);
  // Visualizza messaggio di errore
});

Confronto con filter

Differenze base

MetodoDescrizioneOutputCaso d'Uso
partitionDividi una sorgente in due stream2 ObservableQuando vuoi usare entrambi gli stream simultaneamente
filterPassa solo valori che soddisfano la condizione1 ObservableQuando serve solo uno stream

Esempi di utilizzo

Usa partition per elaborare entrambi gli stream simultaneamente

ts
import { partition, interval } from 'rxjs';
import { map, take } from 'rxjs';

const output = document.createElement('div');
document.body.appendChild(output);

const successArea = document.createElement('div');
successArea.innerHTML = '<h4 style="color: green;">✅ Successo</h4><ul id="success-list"></ul>';
successArea.style.float = 'left';
successArea.style.width = '45%';
output.appendChild(successArea);

const failureArea = document.createElement('div');
failureArea.innerHTML = '<h4 style="color: red;">❌ Fallimento</h4><ul id="failure-list"></ul>';
failureArea.style.float = 'right';
failureArea.style.width = '45%';
output.appendChild(failureArea);

// Stream successo/fallimento casuale
const tasks$ = interval(1000).pipe(
  take(10),
  map(i => ({
    id: i + 1,
    success: Math.random() > 0.5,
    message: `Task ${i + 1}`
  }))
);

// ✅ partition - gestisci successo e fallimento simultaneamente
const [success$, failure$] = partition(tasks$, task => task.success);

success$.subscribe(task => {
  const successList = document.getElementById('success-list')!;
  const li = document.createElement('li');
  li.textContent = task.message;
  successList.appendChild(li);
});

failure$.subscribe(task => {
  const failureList = document.getElementById('failure-list')!;
  const li = document.createElement('li');
  li.textContent = task.message;
  failureList.appendChild(li);
});

Usa filter se serve solo uno stream

ts
import { interval } from 'rxjs';
import { map, take, filter } from 'rxjs';

const output = document.createElement('div');
document.body.appendChild(output);

const successArea = document.createElement('div');
successArea.innerHTML = '<h4 style="color: green;">✅ Visualizza solo successo</h4><ul id="success-only"></ul>';
output.appendChild(successArea);

const tasks$ = interval(1000).pipe(
  take(10),
  map(i => ({
    id: i + 1,
    success: Math.random() > 0.5,
    message: `Task ${i + 1}`
  }))
);

// ✅ filter - elabora solo successo (ignora fallimenti)
tasks$
  .pipe(filter(task => task.success))
  .subscribe(task => {
    const successList = document.getElementById('success-only')!;
    const li = document.createElement('li');
    li.textContent = task.message;
    successList.appendChild(li);
  });

Usare filter due volte vs. partition

ts
import { of } from 'rxjs';
import { filter } from 'rxjs';
import { partition } from 'rxjs';

const numbers$ = of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// ❌ Usa filter due volte - la sorgente potrebbe essere eseguita due volte
const evens1$ = numbers$.pipe(filter(n => n % 2 === 0));
const odds1$ = numbers$.pipe(filter(n => n % 2 !== 0));

evens1$.subscribe(n => console.log('Pari:', n));
odds1$.subscribe(n => console.log('Dispari:', n));
// Problema: se numbers$ è un cold observable, verrà eseguito due volte

// ✅ Usa partition - crea entrambi gli stream in una singola esecuzione
const [evens2$, odds2$] = partition(numbers$, n => n % 2 === 0);

evens2$.subscribe(n => console.log('Pari:', n));
odds2$.subscribe(n => console.log('Dispari:', n));
// Vantaggio: crea efficientemente due stream da una sorgente

Usa filter se vuoi ramificare nella pipeline

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

interface User {
  id: number;
  name: string;
  age: number;
  isActive: boolean;
}

const users$ = from([
  { id: 1, name: 'Alice', age: 25, isActive: true },
  { id: 2, name: 'Bob', age: 30, isActive: false },
  { id: 3, name: 'Carol', age: 35, isActive: true }
]);

// ❌ partition è una Funzione di Creazione, quindi non può essere usato in una pipeline
// users$.pipe(
//   map(user => user.name),
//   partition(name => name.startsWith('A')) // Errore
// );

// ✅ Usa filter - disponibile nella pipeline
users$
  .pipe(
    filter(user => user.isActive),  // Solo utenti attivi
    map(user => user.name)           // Estrai nome
  )
  .subscribe(console.log);
// Output: Alice, Carol
SituazioneMetodo RaccomandatoMotivo
Vuoi elaborare entrambi successo e fallimentopartitionPuò creare due stream in una singola esecuzione della sorgente
Vuoi elaborare solo successofilterSemplice e chiaro
Vuoi ramificare condizioni nella pipelinefilterpartition non può essere usato perché è una Funzione di Creazione
Vuoi ramificare in 3 o più con condizioni complessegroupByPuò dividere in gruppi multipli

Note

1. Sottoscrivi entrambi gli stream

I due Observable creati in una partition condividono la sorgente originale. Se non ti sottoscrivi a entrambi, lo stream originale potrebbe non essere elaborato completamente.

ts
const [success$, failure$] = partition(source$, predicate);

// Sottoscrivi entrambi
success$.subscribe(handleSuccess);
failure$.subscribe(handleFailure);

2. La sorgente viene eseguita due volte

partition internamente si sottoscrive alla sorgente originale due volte. Fai attenzione agli effetti collaterali.

ts
let callCount = 0;
const source$ = new Observable(observer => {
  callCount++;
  console.log(`Conteggio subscription: ${callCount}`);
  observer.next(1);
  observer.complete();
});

const [a$, b$] = partition(source$, n => n > 0);
a$.subscribe(); // Conteggio subscription: 1
b$.subscribe(); // Conteggio subscription: 2

Per evitare effetti collaterali, usa share().

ts
import { share } from 'rxjs';

const shared$ = source$.pipe(share());
const [a$, b$] = partition(shared$, n => n > 0);

3. Non fornito come Pipeable Operator

Da RxJS 7, partition è fornito solo come Funzione di Creazione. Non può essere usato all'interno di una pipeline.

ts
// ❌ Non possibile
source$.pipe(
  partition(n => n % 2 === 0) // Errore
);

// ✅ Utilizzo corretto
const [evens$, odds$] = partition(source$, n => n % 2 === 0);

Pattern Alternativi

Se vuoi ramificare all'interno di una pipeline, usa filter.

ts
const source$ = of(1, 2, 3, 4, 5, 6);

const evens$ = source$.pipe(filter(n => n % 2 === 0));
const odds$ = source$.pipe(filter(n => n % 2 !== 0));

// Oppure condividi la sorgente con share
const shared$ = source$.pipe(share());
const evens$ = shared$.pipe(filter(n => n % 2 === 0));
const odds$ = shared$.pipe(filter(n => n % 2 !== 0));

Operatori correlati

  • filter - passa solo valori che soddisfano una condizione
  • groupBy - Dividi in gruppi multipli
  • share - Condividi una sorgente

Riepilogo

partition è uno strumento potente per dividere un Observable in due basandosi su una condizione.

  • ✅ Ideale per elaborazione separata successo/fallimento
  • ✅ Crea due stream indipendenti
  • ⚠️ Le sorgenti vengono sottoscritte due volte (nota gli effetti collaterali)
  • ⚠️ Non offerto come Pipeable Operator

Pubblicato sotto licenza CC-BY-4.0.