last#
The last()
operator is used to get only the very last value emitted by a source Observable that satisfies an optional condition, but only after the source Observable completes.
It operates similarly to first()
but focuses on the end of the stream:
last()
(no arguments): Waits for the source Observable to complete. Once completed, it emits the single, last value that the source emitted. If the source completes without emitting any values,last()
will emit anEmptyError
.last(predicateFn)
: Waits for the source Observable to complete. Once completed, it looks at all the values the source emitted and finds the last one for which thepredicateFn
returnedtrue
. It emits that single value. If no value satisfied the predicate before completion,last()
emits anEmptyError
.last(predicateFn, defaultValue)
: Same as above, but if no value satisfied the predicate before completion, it emits thedefaultValue
instead of erroring.
Key Characteristics#
- Waits for Completion: Critically depends on the source Observable sending a
complete
notification before it can emit a value. It won't work on streams that never complete (like a rawinterval
). - Selects Last Value: Emits only one value – the last that qualifies based on the arguments, determined after the source finishes.
- Completes Stream: Completes itself immediately after emitting the single value (or default value/error).
- Potential Error: Can throw
EmptyError
if the source completes without emitting suitable values (unless adefaultValue
is supplied).
Real-World Example Scenario#
Imagine you have an Observable representing a sequence of operations that must finish, like processing steps in a batch job. The stream might emit intermediate status updates, but you only care about the final result or status message that is emitted just before the entire process completes.
Scenario: Let's simulate a stream emitting scores achieved during different phases of a game round. We only want to get the final score achieved at the end of the round.
Code Snippet#
import { Component, OnInit } from "@angular/core";
import { of, last, catchError, EMPTY, throwError } from "rxjs";
import { EmptyError } from "rxjs";
@Component({
selector: "app-last-demo",
standalone: true,
imports: [],
template: `
<h4>Last Operator Demo</h4>
<p>Getting the final score from a completed round. Check console.</p>
<p>Result: {{ finalScoreStatus }}</p>
`,
})
export class LastDemoComponent implements OnInit {
finalScoreStatus = "Waiting for round to complete...";
ngOnInit(): void {
const roundScores$ = of(10, 50, 20, 100); // Finite, completes after 100
console.log(
`[${new Date().toLocaleTimeString()}] Subscribing to get final score...`
);
roundScores$
.pipe(
last(), // No predicate, just get the very last value
catchError((error) => {
if (error instanceof EmptyError) {
console.warn(
`[${new Date().toLocaleTimeString()}] Source completed without emitting values.`
);
this.finalScoreStatus = "Round completed with no scores.";
return EMPTY;
} else {
console.error(
`[${new Date().toLocaleTimeString()}] Stream error:`,
error
);
this.finalScoreStatus = `Error: ${error.message}`;
return throwError(() => error);
}
})
)
.subscribe({
next: (finalScore) => {
console.log(
`[${new Date().toLocaleTimeString()}] Final score received: ${finalScore}`
);
this.finalScoreStatus = `Final Score: ${finalScore}`;
},
complete: () => {
console.log(
`[${new Date().toLocaleTimeString()}] Stream completed by last().`
);
},
});
}
}
Explanation:
roundScores$ = of(10, 50, 20, 100)
: We create a finite Observable usingof()
. This stream emits 10, 50, 20, 100 and then immediately completes.last()
: This operator subscribes toroundScores$
. It internally keeps track of the most recently emitted value. BecauseroundScores$
completes right after emitting 100,last()
knows the stream is finished.- Emission: Once
roundScores$
completes,last()
emits the very last value it saw, which is100
. catchError(...)
: Handles theEmptyError
case if the source (of()
) had been empty (e.g.,of()
).subscribe({...})
:- The
next
handler receives the single value100
. - The
complete
handler is called right afternext
, confirming the stream finished.
- The
Summary#
last()
is used when you need the final value emitted by a completing Observable stream (optionally matching a condition). It inherently requires waiting for the source to finish before it can determine and emit the last value. Remember it won't work if the source stream never completes.