Overview
BehaviorSubject is a variant of Subject that requires an initial value and emits its current value to new subscribers immediately upon subscription. It always stores the latest value emitted and provides access to it via the value property.
Key Feature : BehaviorSubject requires an initial value and always has a “current value” that new subscribers receive immediately.
Class Signature
class BehaviorSubject < T > extends Subject < T > {
constructor ( initialValue : T );
get value () : T ;
getValue () : T ;
next ( value : T ) : void ;
}
Constructor
const subject = new BehaviorSubject < T >( initialValue : T );
The initial value that will be emitted to the first subscriber and returned by getValue() before any other values are emitted.
Properties
Get the current value. Throws an error if the Subject has errored.
Methods
Returns the current value. Throws the error if the Subject has errored.
Emit a new value and update the current value.
Usage Examples
Basic Usage
Immediate emission to new subscribers:
Current value
getValue vs value
import { BehaviorSubject } from 'rxjs' ;
const subject = new BehaviorSubject < number >( 0 );
console . log ( 'Initial value:' , subject . value ); // 0
subject . subscribe ({
next : ( v ) => console . log ( `Observer A: ${ v } ` )
});
// Observer A: 0 (receives initial value immediately)
subject . next ( 1 );
subject . next ( 2 );
// Late subscriber gets current value
subject . subscribe ({
next : ( v ) => console . log ( `Observer B: ${ v } ` )
});
// Observer B: 2 (receives latest value immediately)
subject . next ( 3 );
// Output:
// Initial value: 0
// Observer A: 0
// Observer A: 1
// Observer A: 2
// Observer B: 2
// Observer A: 3
// Observer B: 3
Application State
Manage application state with BehaviorSubject:
import { BehaviorSubject } from 'rxjs' ;
import { map } from 'rxjs/operators' ;
interface AppState {
user : { name : string ; email : string } | null ;
isLoading : boolean ;
error : string | null ;
}
const initialState : AppState = {
user: null ,
isLoading: false ,
error: null
};
class StateManager {
private state = new BehaviorSubject < AppState >( initialState );
state$ = this . state . asObservable ();
user$ = this . state$ . pipe ( map ( state => state . user ));
isLoading$ = this . state$ . pipe ( map ( state => state . isLoading ));
setUser ( user : AppState [ 'user' ]) : void {
this . state . next ({
... this . state . value ,
user
});
}
setLoading ( isLoading : boolean ) : void {
this . state . next ({
... this . state . value ,
isLoading
});
}
setError ( error : string | null ) : void {
this . state . next ({
... this . state . value ,
error
});
}
}
const stateManager = new StateManager ();
// Components can subscribe and get current state immediately
stateManager . user$ . subscribe ( user => {
if ( user ) {
console . log ( 'Logged in as:' , user . name );
} else {
console . log ( 'Not logged in' );
}
});
stateManager . setUser ({ name: 'Alice' , email: 'alice@example.com' });
Theme Manager
Manage application theme:
import { BehaviorSubject } from 'rxjs' ;
type Theme = 'light' | 'dark' ;
class ThemeManager {
private theme = new BehaviorSubject < Theme >( 'light' );
theme$ = this . theme . asObservable ();
getCurrentTheme () : Theme {
return this . theme . value ;
}
setTheme ( theme : Theme ) : void {
this . theme . next ( theme );
document . body . className = theme ;
}
toggleTheme () : void {
const newTheme = this . theme . value === 'light' ? 'dark' : 'light' ;
this . setTheme ( newTheme );
}
}
const themeManager = new ThemeManager ();
// UI components subscribe and get current theme immediately
themeManager . theme$ . subscribe ( theme => {
console . log ( 'Current theme:' , theme );
applyThemeStyles ( theme );
});
// Toggle theme
themeManager . toggleTheme ();
Track form input values:
import { BehaviorSubject } from 'rxjs' ;
import { debounceTime , distinctUntilChanged } from 'rxjs/operators' ;
class FormField {
private valueSubject = new BehaviorSubject < string >( '' );
value$ = this . valueSubject . asObservable ();
debouncedValue$ = this . value$ . pipe (
debounceTime ( 300 ),
distinctUntilChanged ()
);
setValue ( value : string ) : void {
this . valueSubject . next ( value );
}
getValue () : string {
return this . valueSubject . value ;
}
reset () : void {
this . valueSubject . next ( '' );
}
}
const searchField = new FormField ();
// Subscribe to debounced values for API calls
searchField . debouncedValue$ . subscribe ( query => {
if ( query . length > 2 ) {
searchAPI ( query );
}
});
// Subscribe to immediate values for UI updates
searchField . value$ . subscribe ( value => {
updateCharacterCount ( value . length );
});
// Simulate user typing
searchField . setValue ( 'h' );
searchField . setValue ( 'he' );
searchField . setValue ( 'hel' );
searchField . setValue ( 'hello' );
Loading State
Manage loading indicators:
import { BehaviorSubject , finalize } from 'rxjs' ;
import { ajax } from 'rxjs/ajax' ;
class DataService {
private loading = new BehaviorSubject < boolean >( false );
isLoading$ = this . loading . asObservable ();
fetchData ( url : string ) {
this . loading . next ( true );
return ajax . getJSON ( url ). pipe (
finalize (() => this . loading . next ( false ))
);
}
}
const service = new DataService ();
// UI subscribes to loading state
service . isLoading$ . subscribe ( isLoading => {
if ( isLoading ) {
showSpinner ();
} else {
hideSpinner ();
}
});
// Fetch data
service . fetchData ( '/api/data' ). subscribe ( data => {
console . log ( 'Data loaded:' , data );
});
Manage shopping cart state:
import { BehaviorSubject } from 'rxjs' ;
import { map } from 'rxjs/operators' ;
interface CartItem {
id : number ;
name : string ;
price : number ;
quantity : number ;
}
class ShoppingCart {
private items = new BehaviorSubject < CartItem []>([]);
items$ = this . items . asObservable ();
itemCount$ = this . items$ . pipe (
map ( items => items . reduce (( sum , item ) => sum + item . quantity , 0 ))
);
total$ = this . items$ . pipe (
map ( items => items . reduce (( sum , item ) => sum + ( item . price * item . quantity ), 0 ))
);
addItem ( item : Omit < CartItem , 'quantity' >) : void {
const currentItems = this . items . value ;
const existingItem = currentItems . find ( i => i . id === item . id );
if ( existingItem ) {
this . items . next (
currentItems . map ( i =>
i . id === item . id
? { ... i , quantity: i . quantity + 1 }
: i
)
);
} else {
this . items . next ([ ... currentItems , { ... item , quantity: 1 }]);
}
}
removeItem ( id : number ) : void {
this . items . next (
this . items . value . filter ( item => item . id !== id )
);
}
clear () : void {
this . items . next ([]);
}
}
const cart = new ShoppingCart ();
cart . itemCount$ . subscribe ( count => {
updateCartBadge ( count );
});
cart . total$ . subscribe ( total => {
updateTotalDisplay ( total );
});
cart . addItem ({ id: 1 , name: 'Widget' , price: 10 });
cart . addItem ({ id: 2 , name: 'Gadget' , price: 20 });
cart . addItem ({ id: 1 , name: 'Widget' , price: 10 }); // Increments quantity
Key Differences from Subject
Subject vs BehaviorSubject:
Subject: No initial value, late subscribers miss past emissions
BehaviorSubject: Requires initial value, late subscribers get current value
import { Subject , BehaviorSubject } from 'rxjs' ;
// Regular Subject
const subject = new Subject < number >();
subject . next ( 1 );
subject . subscribe ( x => console . log ( 'Subject:' , x ));
subject . next ( 2 );
// Output: Subject: 2 (missed 1)
// BehaviorSubject
const behaviorSubject = new BehaviorSubject < number >( 0 );
behaviorSubject . next ( 1 );
behaviorSubject . subscribe ( x => console . log ( 'BehaviorSubject:' , x ));
behaviorSubject . next ( 2 );
// Output:
// BehaviorSubject: 1 (current value)
// BehaviorSubject: 2
Error Handling
Once a BehaviorSubject errors, getValue() and the value getter will throw that error instead of returning a value.
import { BehaviorSubject } from 'rxjs' ;
const subject = new BehaviorSubject < number >( 0 );
subject . next ( 1 );
console . log ( subject . value ); // 1
subject . error ( new Error ( 'Something went wrong' ));
try {
console . log ( subject . value ); // Throws error
} catch ( err ) {
console . error ( 'Error accessing value:' , err . message );
}
Common Patterns
Derived State
import { BehaviorSubject , combineLatest } from 'rxjs' ;
import { map } from 'rxjs/operators' ;
const firstName = new BehaviorSubject < string >( 'John' );
const lastName = new BehaviorSubject < string >( 'Doe' );
const fullName$ = combineLatest ([ firstName , lastName ]). pipe (
map (([ first , last ]) => ` ${ first } ${ last } ` )
);
fullName$ . subscribe ( name => console . log ( 'Full name:' , name ));
// Full name: John Doe
firstName . next ( 'Jane' );
// Full name: Jane Doe
Snapshot Values
import { BehaviorSubject } from 'rxjs' ;
const counter = new BehaviorSubject < number >( 0 );
function increment () : void {
counter . next ( counter . value + 1 );
}
function decrement () : void {
counter . next ( counter . value - 1 );
}
increment (); // 1
increment (); // 2
decrement (); // 1
console . log ( 'Current count:' , counter . value ); // 1
When to Use BehaviorSubject
Current State : Need to access current value synchronously
Initial Value : Want new subscribers to receive latest value
State Management : Managing application or component state
Configuration : Storing current settings/preferences
Form State : Tracking form field values
UI State : Managing UI state (theme, language, etc.)
Slightly more memory than Subject (stores current value)
getValue() is synchronous - no subscription needed
New subscribers immediately receive current value
Good for state that changes infrequently
See Also