concat
Creates an output Observable which sequentially emits all values from the first given Observable and then moves on to the next.
Import
import { concat } from 'rxjs' ;
Type Signature
function concat < T extends readonly unknown []>(
... inputs : [ ... ObservableInputTuple < T >]
) : Observable < T [ number ]>;
Parameters
inputs
...ObservableInput[]
required
Multiple Observables to concatenate sequentially.
Returns
An Observable that emits values from all input Observables in sequence. Values from the next Observable only begin emitting after the previous one completes.
Description
concat joins multiple Observables together by subscribing to them one at a time. It:
Subscribes to the first Observable
Emits all its values until it completes
Then subscribes to the next Observable
Repeats until all Observables complete
Key characteristics:
Subscribes to Observables sequentially , not concurrently
Only one Observable is active at a time
Waits for each to complete before moving to the next
If any Observable errors, concat errors immediately
If any Observable never completes, subsequent ones never start
Examples
Basic Concatenation
import { interval , take , concat } from 'rxjs' ;
const timer1 = interval ( 1000 ). pipe ( take ( 4 )); // 0, 1, 2, 3
const timer2 = interval ( 500 ). pipe ( take ( 3 )); // 0, 1, 2
const result = concat ( timer1 , timer2 );
result . subscribe ( x => console . log ( x ));
// Output (with timing):
// 0 (at 1s)
// 1 (at 2s)
// 2 (at 3s)
// 3 (at 4s)
// 0 (at 4.5s)
// 1 (at 5s)
// 2 (at 5.5s)
Timer and Sequence
import { interval , take , range , concat } from 'rxjs' ;
const timer = interval ( 1000 ). pipe ( take ( 4 )); // Takes 4 seconds
const sequence = range ( 1 , 10 ); // Synchronous
const result = concat ( timer , sequence );
result . subscribe ( x => console . log ( x ));
// Output:
// 0 (after 1s)
// 1 (after 2s)
// 2 (after 3s)
// 3 (after 4s)
// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 (immediately after)
Repeat an Observable
import { interval , take , concat } from 'rxjs' ;
const timer = interval ( 1000 ). pipe ( take ( 2 )); // 0, 1
concat ( timer , timer , timer ). subscribe ({
next : value => console . log ( value ),
complete : () => console . log ( 'Done!' )
});
// Output:
// 0 (at 1s)
// 1 (at 2s)
// 0 (at 3s)
// 1 (at 4s)
// 0 (at 5s)
// 1 (at 6s)
// Done!
Common Use Cases
Sequential HTTP Requests
import { concat } from 'rxjs' ;
import { ajax } from 'rxjs/ajax' ;
import { map } from 'rxjs/operators' ;
// Perform requests one after another
const createUser$ = ajax . post ( '/api/users' , { name: 'John' });
const assignRole$ = ajax . post ( '/api/users/1/roles' , { role: 'admin' });
const sendEmail$ = ajax . post ( '/api/emails' , { to: 'john@example.com' });
const setup$ = concat ( createUser$ , assignRole$ , sendEmail$ );
setup$ . subscribe ({
next : response => console . log ( 'Step completed:' , response . status ),
complete : () => console . log ( 'All steps completed!' ),
error : err => console . error ( 'Setup failed:' , err )
});
Animation Sequence
import { concat } from 'rxjs' ;
import { timer } from 'rxjs' ;
import { tap , ignoreElements } from 'rxjs/operators' ;
const fadeIn$ = timer ( 1000 ). pipe (
tap (() => element . style . opacity = '1' ),
ignoreElements ()
);
const show$ = timer ( 3000 ). pipe (
tap (() => element . style . display = 'block' ),
ignoreElements ()
);
const fadeOut$ = timer ( 1000 ). pipe (
tap (() => element . style . opacity = '0' ),
ignoreElements ()
);
const animation$ = concat ( fadeIn$ , show$ , fadeOut$ );
animation$ . subscribe ({
complete : () => console . log ( 'Animation complete' )
});
Sequential Data Processing
import { concat , from } from 'rxjs' ;
import { concatMap , tap } from 'rxjs/operators' ;
const batches = [
[ 1 , 2 , 3 ],
[ 4 , 5 , 6 ],
[ 7 , 8 , 9 ]
];
// Process each batch completely before moving to next
const processBatch = ( batch : number []) => {
return from ( batch ). pipe (
tap ( item => console . log ( `Processing item ${ item } ` ))
);
};
const processAll$ = concat (
... batches . map ( batch => processBatch ( batch ))
);
processAll$ . subscribe ({
complete : () => console . log ( 'All batches processed' )
});
Multi-Step Wizard
import { concat , Subject } from 'rxjs' ;
import { take , tap } from 'rxjs/operators' ;
const step1Complete$ = new Subject ();
const step2Complete$ = new Subject ();
const step3Complete$ = new Subject ();
const wizard$ = concat (
step1Complete$ . pipe (
take ( 1 ),
tap (() => console . log ( 'Step 1 complete, showing step 2' ))
),
step2Complete$ . pipe (
take ( 1 ),
tap (() => console . log ( 'Step 2 complete, showing step 3' ))
),
step3Complete$ . pipe (
take ( 1 ),
tap (() => console . log ( 'Step 3 complete, wizard done!' ))
)
);
wizard$ . subscribe ({
complete : () => console . log ( 'Wizard completed!' )
});
// Trigger steps as user completes them
step1Complete$ . next ();
// Later...
step2Complete$ . next ();
// Finally...
step3Complete$ . next ();
Behavior with Errors
import { concat , of , throwError } from 'rxjs' ;
import { catchError } from 'rxjs/operators' ;
const first$ = of ( 1 , 2 , 3 );
const error$ = throwError (() => new Error ( 'Oops!' ));
const third$ = of ( 4 , 5 , 6 );
// Without error handling - stops at error
concat ( first$ , error$ , third$ ). subscribe ({
next : x => console . log ( x ),
error : err => console . error ( err . message )
});
// Output: 1, 2, 3, "Oops!"
// third$ never runs
// With error handling - continues
concat (
first$ ,
error$ . pipe ( catchError (() => of ( 'recovered' ))),
third$
). subscribe ( x => console . log ( x ));
// Output: 1, 2, 3, "recovered", 4, 5, 6
Comparison with Merge
concat (Sequential)
merge (Concurrent)
import { concat , interval , take } from 'rxjs' ;
import { map } from 'rxjs/operators' ;
const first$ = interval ( 1000 ). pipe (
take ( 3 ),
map ( x => `First: ${ x } ` )
);
const second$ = interval ( 500 ). pipe (
take ( 3 ),
map ( x => `Second: ${ x } ` )
);
concat ( first$ , second$ ). subscribe ( console . log );
// Output (sequential):
// First: 0 (at 1s)
// First: 1 (at 2s)
// First: 2 (at 3s)
// Second: 0 (at 3.5s)
// Second: 1 (at 4s)
// Second: 2 (at 4.5s)
Important Notes
If any Observable in the sequence never completes, subsequent Observables will never be subscribed to. Use take, takeUntil, or timeout to ensure completion.
concat is equivalent to merge with concurrency set to 1: merge(...sources, 1)
For the operator version that works on a source Observable, use concatWith or the pipe operator concatAll.
merge - Combine Observables concurrently
zip - Combine by index
forkJoin - Wait for all to complete
concatAll - Flatten higher-order Observable sequentially
concatMap - Map and concat
startWith - Prepend values
endWith - Append values
See Also