Conditional and Boolean Logic Operators
1. iif for Conditional Observable Selection
| Feature | Syntax | Description | Use Case |
|---|---|---|---|
| iif | iif(condition, trueResult$, falseResult$?) |
Subscribes to one observable based on condition evaluated at subscription time | Conditional stream selection |
| Lazy evaluation | Condition checked when subscribed, not when created | Deferred decision making | Runtime branching |
| Default | If falseResult$ omitted, emits EMPTY | Optional false branch | Conditional execution |
Example: iif for conditional observable selection
import { iif, of, EMPTY, interval } from 'rxjs';
import { mergeMap, map } from 'rxjs/operators';
// Basic iif - simple conditional
const condition = Math.random() > 0.5;
iif(
() => condition,
of('Condition is true'),
of('Condition is false')
).subscribe(console.log);
// Without false branch - emits EMPTY
iif(
() => false,
of('True branch')
// No false branch - completes immediately
).subscribe({
next: val => console.log(val),
complete: () => console.log('Completed (empty)')
});
// Practical: Conditional data source
function getData(useCache: boolean) {
return iif(
() => useCache,
of(cachedData),
ajax.getJSON('/api/data')
);
}
getData(hasCachedData).subscribe(data => displayData(data));
// Practical: Authentication check
const isAuthenticated$ = iif(
() => hasAuthToken(),
ajax.getJSON('/api/user/profile'),
of(null)
);
isAuthenticated$.subscribe(profile => {
if (profile) {
displayProfile(profile);
} else {
redirectToLogin();
}
});
// Practical: Environment-based configuration
const config$ = iif(
() => process.env.NODE_ENV === 'production',
ajax.getJSON('/api/config/production'),
of(developmentConfig)
);
config$.subscribe(config => initializeApp(config));
// Practical: Feature flag
function getFeatureData(featureEnabled: boolean) {
return iif(
() => featureEnabled,
ajax.getJSON('/api/feature/data'),
of({ message: 'Feature disabled' })
);
}
// Practical: Conditional retry
function fetchWithRetry(url: string, shouldRetry: boolean) {
return ajax.getJSON(url).pipe(
catchError(err => iif(
() => shouldRetry,
ajax.getJSON(url).pipe(delay(1000)), // Retry
throwError(() => err) // Don't retry
))
);
}
// Practical: User preference based loading
const theme$ = iif(
() => userPreferences.theme === 'dark',
of('dark-theme.css'),
of('light-theme.css')
).pipe(
mergeMap(themePath => loadStylesheet(themePath))
);
// Practical: A/B testing
const variant$ = iif(
() => Math.random() < 0.5,
of('variant-A'),
of('variant-B')
);
variant$.subscribe(variant => {
trackVariant(variant);
showVariant(variant);
});
// Practical: Conditional polling
const pollInterval$ = iif(
() => isHighPriority,
interval(1000), // Poll every 1s
interval(5000) // Poll every 5s
);
pollInterval$.pipe(
switchMap(() => ajax.getJSON('/api/status'))
).subscribe(status => updateStatus(status));
// Practical: Device-based content loading
const content$ = iif(
() => isMobileDevice(),
ajax.getJSON('/api/content/mobile'),
ajax.getJSON('/api/content/desktop')
);
// Practical: Offline/online handling
const data$ = iif(
() => navigator.onLine,
ajax.getJSON('/api/data'),
of(offlineData)
);
data$.subscribe(data => renderData(data));
// With mergeMap for dynamic conditions
clicks$.pipe(
mergeMap(() => iif(
() => currentUser.isPremium,
getPremiumContent(),
getFreeContent()
))
).subscribe(content => displayContent(content));
Note: iif evaluates condition at subscription time, not creation
time. For in-stream conditionals, use filter or other operators.
2. every for Universal Condition Checking
| Feature | Syntax | Description | Result |
|---|---|---|---|
| every | every(predicate, thisArg?) |
Returns true if all values satisfy predicate, false otherwise | Single boolean emission |
| Short-circuit | Completes with false on first failing value | Doesn't wait for all values | Early termination |
| Empty stream | Returns true for empty observables | Vacuous truth | Logical consistency |
Example: every for universal condition checking
import { of, from, range } from 'rxjs';
import { every, map } from 'rxjs/operators';
// Basic every - all values satisfy condition
of(2, 4, 6, 8).pipe(
every(val => val % 2 === 0)
).subscribe(result => console.log('All even?', result)); // true
// Fails on first non-matching value
of(2, 4, 5, 8).pipe(
every(val => val % 2 === 0)
).subscribe(result => console.log('All even?', result)); // false
// Empty stream returns true
of().pipe(
every(val => val > 100)
).subscribe(result => console.log('Empty stream:', result)); // true
// Practical: Form validation - all fields valid
const formFields$ = from([
{ name: 'email', value: 'user@example.com', valid: true },
{ name: 'password', value: 'secret123', valid: true },
{ name: 'age', value: '25', valid: true }
]);
formFields$.pipe(
every(field => field.valid)
).subscribe(allValid => {
if (allValid) {
enableSubmitButton();
} else {
showValidationErrors();
}
});
// Practical: Permissions check
const requiredPermissions = ['read', 'write', 'delete'];
const userPermissions$ = from(requiredPermissions);
userPermissions$.pipe(
every(permission => hasPermission(permission))
).subscribe(hasAllPermissions => {
if (hasAllPermissions) {
showAdminPanel();
} else {
showAccessDenied();
}
});
// Practical: Data quality check
ajax.getJSON('/api/records').pipe(
mergeMap(response => from(response.records)),
every(record => {
return record.id &&
record.name &&
record.timestamp &&
typeof record.value === 'number';
})
).subscribe(dataValid => {
if (dataValid) {
console.log('All records valid');
processRecords();
} else {
console.error('Invalid data detected');
rejectBatch();
}
});
// Practical: Age verification
const users$ = ajax.getJSON('/api/users');
users$.pipe(
mergeMap(response => from(response.users)),
every(user => user.age >= 18)
).subscribe(allAdults => {
if (allAdults) {
proceedWithContent();
} else {
showAgeRestrictionWarning();
}
});
// Practical: Configuration validation
const configEntries$ = from(Object.entries(config));
configEntries$.pipe(
every(([key, value]) => value !== null && value !== undefined)
).subscribe(configComplete => {
if (configComplete) {
initializeApp();
} else {
showConfigurationError();
}
});
// Practical: File upload validation
const files$ = from(selectedFiles);
files$.pipe(
every(file => {
const validType = ['image/jpeg', 'image/png'].includes(file.type);
const validSize = file.size < 5 * 1024 * 1024; // 5MB
return validType && validSize;
})
).subscribe(allValid => {
if (allValid) {
uploadFiles(selectedFiles);
} else {
showFileValidationError();
}
});
// Practical: Network connectivity check
const endpoints$ = from([
'/api/health',
'/api/status',
'/api/ping'
]);
endpoints$.pipe(
mergeMap(endpoint => ajax.getJSON(endpoint).pipe(
map(() => true),
catchError(() => of(false))
)),
every(success => success)
).subscribe(allReachable => {
if (allReachable) {
setOnlineStatus(true);
} else {
setOnlineStatus(false);
showOfflineWarning();
}
});
// Practical: Dependencies loaded check
const dependencies$ = from([
loadScript('jquery.js'),
loadScript('bootstrap.js'),
loadScript('app.js')
]);
dependencies$.pipe(
every(loaded => loaded === true)
).subscribe(allLoaded => {
if (allLoaded) {
initializeApplication();
} else {
showLoadingError();
}
});
// Practical: Inventory check
const products$ = from(shoppingCart.items);
products$.pipe(
mergeMap(item => checkStock(item.productId)),
every(inStock => inStock)
).subscribe(allAvailable => {
if (allAvailable) {
proceedToCheckout();
} else {
showOutOfStockMessage();
}
});
Note: every short-circuits on first false - efficient for large
streams. Returns single boolean value then completes.
3. find and findIndex for First Match Operations
| Operator | Syntax | Description | Returns |
|---|---|---|---|
| find | find(predicate, thisArg?) |
Emits first value that satisfies predicate, then completes | First matching value or undefined |
| findIndex | findIndex(predicate, thisArg?) |
Emits index of first value that satisfies predicate | Index number or -1 |
| Behavior | Completes after first match or when source completes | Short-circuits on match | Early termination |
Example: find and findIndex for searching
import { from, range } from 'rxjs';
import { find, findIndex, delay } from 'rxjs/operators';
// Basic find - first match
from([1, 2, 3, 4, 5]).pipe(
find(val => val > 3)
).subscribe(result => console.log('Found:', result)); // 4
// No match returns undefined
from([1, 2, 3]).pipe(
find(val => val > 10)
).subscribe(result => console.log('Found:', result)); // undefined
// findIndex - get position
from(['a', 'b', 'c', 'd']).pipe(
findIndex(val => val === 'c')
).subscribe(index => console.log('Index:', index)); // 2
// No match returns -1
from(['a', 'b', 'c']).pipe(
findIndex(val => val === 'z')
).subscribe(index => console.log('Index:', index)); // -1
// Practical: Find user by ID
ajax.getJSON('/api/users').pipe(
mergeMap(response => from(response.users)),
find(user => user.id === targetUserId)
).subscribe(user => {
if (user) {
displayUserProfile(user);
} else {
showUserNotFound();
}
});
// Practical: Find first available slot
const timeSlots$ = from([
{ time: '09:00', available: false },
{ time: '10:00', available: false },
{ time: '11:00', available: true },
{ time: '12:00', available: true }
]);
timeSlots$.pipe(
find(slot => slot.available)
).subscribe(slot => {
if (slot) {
console.log('First available:', slot.time);
bookAppointment(slot);
}
});
// Practical: Find error in logs
const logs$ = ajax.getJSON('/api/logs').pipe(
mergeMap(response => from(response.logs))
);
logs$.pipe(
find(log => log.level === 'ERROR')
).subscribe(errorLog => {
if (errorLog) {
highlightError(errorLog);
console.error('First error:', errorLog);
}
});
// Practical: Find product by name
const products$ = from(productCatalog);
products$.pipe(
find(product => product.name.toLowerCase().includes(searchTerm))
).subscribe(product => {
if (product) {
navigateToProduct(product.id);
} else {
showNoResultsMessage();
}
});
// Practical: Find first invalid field
const formFields$ = from([
{ name: 'email', valid: true },
{ name: 'password', valid: false },
{ name: 'age', valid: true }
]);
formFields$.pipe(
find(field => !field.valid)
).subscribe(invalidField => {
if (invalidField) {
focusField(invalidField.name);
showFieldError(invalidField.name);
}
});
// findIndex for position-based logic
const items$ = from(['apple', 'banana', 'cherry', 'date']);
items$.pipe(
findIndex(item => item.startsWith('c'))
).subscribe(index => {
console.log('Found at position:', index); // 2
highlightItemAtIndex(index);
});
// Practical: Find overdue task
const tasks$ = ajax.getJSON('/api/tasks').pipe(
mergeMap(response => from(response.tasks))
);
tasks$.pipe(
find(task => {
const dueDate = new Date(task.dueDate);
return dueDate < new Date() && task.status !== 'completed';
})
).subscribe(overdueTask => {
if (overdueTask) {
showOverdueNotification(overdueTask);
}
});
// Practical: Find matching route
const routes$ = from([
{ path: '/home', component: 'Home' },
{ path: '/about', component: 'About' },
{ path: '/contact', component: 'Contact' }
]);
routes$.pipe(
find(route => route.path === currentPath)
).subscribe(route => {
if (route) {
loadComponent(route.component);
} else {
show404Page();
}
});
// Practical: Find threshold breach
const sensorReadings$ = interval(1000).pipe(
map(() => Math.random() * 100),
take(100)
);
sensorReadings$.pipe(
find(reading => reading > 95)
).subscribe(highReading => {
if (highReading !== undefined) {
console.warn('Threshold breached:', highReading);
triggerAlert();
}
});
// Practical: Find first cached item
const cacheKeys$ = from(['key1', 'key2', 'key3', 'key4']);
cacheKeys$.pipe(
find(key => cache.has(key))
).subscribe(cachedKey => {
if (cachedKey) {
const data = cache.get(cachedKey);
useCachedData(data);
} else {
fetchFreshData();
}
});
// Combined with other operators
ajax.getJSON('/api/items').pipe(
mergeMap(response => from(response.items)),
find(item => item.featured === true)
).subscribe(featuredItem => {
if (featuredItem) {
displayFeaturedItem(featuredItem);
}
});
4. isEmpty for Empty Stream Detection
| Feature | Syntax | Description | Result |
|---|---|---|---|
| isEmpty | isEmpty() |
Emits true if source completes without emitting, false otherwise | Single boolean emission |
| Timing | Waits for source to complete before emitting | Must complete to know if empty | Deferred evaluation |
Example: isEmpty for empty stream detection
import { EMPTY, of, from } from 'rxjs';
import { isEmpty, filter } from 'rxjs/operators';
// Basic isEmpty - empty stream
EMPTY.pipe(
isEmpty()
).subscribe(result => console.log('Is empty?', result)); // true
// Non-empty stream
of(1, 2, 3).pipe(
isEmpty()
).subscribe(result => console.log('Is empty?', result)); // false
// Practical: Check if search has results
ajax.getJSON(`/api/search?q=${query}`).pipe(
mergeMap(response => from(response.results)),
isEmpty()
).subscribe(noResults => {
if (noResults) {
showNoResultsMessage();
} else {
showResultsFound();
}
});
// Practical: Validate shopping cart
const cartItems$ = from(shoppingCart.items);
cartItems$.pipe(
isEmpty()
).subscribe(cartEmpty => {
if (cartEmpty) {
showEmptyCartMessage();
disableCheckoutButton();
} else {
enableCheckoutButton();
}
});
// Practical: Check for unread messages
ajax.getJSON('/api/messages/unread').pipe(
mergeMap(response => from(response.messages)),
isEmpty()
).subscribe(noUnread => {
if (noUnread) {
hideBadge();
} else {
showNotificationBadge();
}
});
// Practical: Validate filtered results
const data$ = ajax.getJSON('/api/data');
data$.pipe(
mergeMap(response => from(response.items)),
filter(item => item.active && item.inStock),
isEmpty()
).subscribe(noneAvailable => {
if (noneAvailable) {
showOutOfStockMessage();
} else {
displayAvailableItems();
}
});
// Practical: Check pending tasks
ajax.getJSON('/api/tasks/pending').pipe(
mergeMap(response => from(response.tasks)),
isEmpty()
).subscribe(noPending => {
if (noPending) {
showAllCaughtUpMessage();
hideTaskList();
} else {
showTaskList();
}
});
// Practical: Validate form errors
const validationErrors$ = validateForm(formData);
validationErrors$.pipe(
isEmpty()
).subscribe(noErrors => {
if (noErrors) {
submitForm();
} else {
showValidationSummary();
}
});
// Practical: Check notification queue
const notifications$ = from(notificationQueue);
notifications$.pipe(
filter(n => !n.dismissed),
isEmpty()
).subscribe(allDismissed => {
if (allDismissed) {
hideNotificationPanel();
}
});
// Practical: Detect empty file
const fileContent$ = readFileAsObservable(file);
fileContent$.pipe(
isEmpty()
).subscribe(fileEmpty => {
if (fileEmpty) {
showError('File is empty');
} else {
processFile();
}
});
// Practical: Check cached data
const cachedItems$ = from(Array.from(cache.values()));
cachedItems$.pipe(
filter(item => !isExpired(item)),
isEmpty()
).subscribe(cacheEmpty => {
if (cacheEmpty) {
fetchFreshData();
} else {
useCachedData();
}
});
// Practical: Verify authentication tokens
const validTokens$ = from(authTokens).pipe(
filter(token => !isExpired(token))
);
validTokens$.pipe(
isEmpty()
).subscribe(noValidTokens => {
if (noValidTokens) {
redirectToLogin();
} else {
proceedToApp();
}
});
Note: isEmpty must wait for source to complete. Use with finite
streams or apply take/takeUntil to prevent infinite waiting.
5. defaultIfEmpty for Fallback Value Provision
| Feature | Syntax | Description | Behavior |
|---|---|---|---|
| defaultIfEmpty | defaultIfEmpty(defaultValue) |
Emits default value if source completes without emitting | Fallback for empty streams |
| Pass-through | If source emits, default is ignored | Only activates on empty completion | Conditional fallback |
Example: defaultIfEmpty for fallback values
import { EMPTY, of, from } from 'rxjs';
import { defaultIfEmpty, filter } from 'rxjs/operators';
// Basic defaultIfEmpty - provides fallback
EMPTY.pipe(
defaultIfEmpty('No data')
).subscribe(val => console.log(val)); // 'No data'
// Non-empty stream - default ignored
of(1, 2, 3).pipe(
defaultIfEmpty(999)
).subscribe(val => console.log(val)); // 1, 2, 3
// Practical: Search with fallback
ajax.getJSON(`/api/search?q=${query}`).pipe(
mergeMap(response => from(response.results)),
defaultIfEmpty({ message: 'No results found' })
).subscribe(result => displayResult(result));
// Practical: User preferences with defaults
ajax.getJSON('/api/user/preferences').pipe(
mergeMap(response => from(response.preferences)),
filter(pref => pref.enabled),
defaultIfEmpty({
theme: 'light',
language: 'en',
notifications: true
})
).subscribe(preferences => applyPreferences(preferences));
// Practical: Shopping cart with default message
const cartItems$ = from(cart.items);
cartItems$.pipe(
defaultIfEmpty({ type: 'empty-cart', message: 'Your cart is empty' })
).subscribe(item => {
if (item.type === 'empty-cart') {
showEmptyCartUI(item.message);
} else {
displayCartItem(item);
}
});
// Practical: Configuration with fallbacks
ajax.getJSON('/api/config').pipe(
map(config => config.feature),
filter(feature => feature.enabled),
defaultIfEmpty({
enabled: false,
settings: defaultSettings
})
).subscribe(featureConfig => initializeFeature(featureConfig));
// Practical: Filtered list with placeholder
const activeUsers$ = ajax.getJSON('/api/users').pipe(
mergeMap(response => from(response.users)),
filter(user => user.active)
);
activeUsers$.pipe(
defaultIfEmpty({
id: null,
name: 'No active users',
placeholder: true
})
).subscribe(user => {
if (user.placeholder) {
showPlaceholder(user.name);
} else {
displayUser(user);
}
});
// Practical: Latest notification with default
const notifications$ = ajax.getJSON('/api/notifications').pipe(
mergeMap(response => from(response.notifications)),
filter(n => !n.read),
take(1)
);
notifications$.pipe(
defaultIfEmpty({
title: 'All caught up!',
message: 'No new notifications',
type: 'placeholder'
})
).subscribe(notification => displayNotification(notification));
// Practical: Validation errors with success message
const errors$ = validateForm(formData);
errors$.pipe(
defaultIfEmpty({
type: 'success',
message: 'Form is valid'
})
).subscribe(result => {
if (result.type === 'success') {
showSuccess(result.message);
submitForm();
} else {
showError(result);
}
});
// Practical: Product recommendations
ajax.getJSON('/api/recommendations').pipe(
mergeMap(response => from(response.products)),
filter(product => product.inStock),
defaultIfEmpty({
id: null,
name: 'Check back later for recommendations',
placeholder: true
})
).subscribe(product => displayProduct(product));
// Practical: Chat messages with welcome
const messages$ = ajax.getJSON(`/api/chat/${roomId}`).pipe(
mergeMap(response => from(response.messages))
);
messages$.pipe(
defaultIfEmpty({
author: 'System',
text: 'No messages yet. Start the conversation!',
timestamp: Date.now()
})
).subscribe(message => displayMessage(message));
// Practical: Dashboard widgets with default
const widgets$ = from(userDashboard.widgets).pipe(
filter(w => w.visible)
);
widgets$.pipe(
defaultIfEmpty({
type: 'welcome',
content: 'Add widgets to customize your dashboard'
})
).subscribe(widget => renderWidget(widget));
// Multiple defaults example
const data$ = ajax.getJSON('/api/data').pipe(
mergeMap(response => from(response.items)),
filter(item => item.priority === 'high'),
defaultIfEmpty(null)
);
data$.subscribe(item => {
if (item === null) {
console.log('No high priority items');
} else {
processItem(item);
}
});
6. sequenceEqual for Stream Comparison
| Feature | Syntax | Description | Comparison |
|---|---|---|---|
| sequenceEqual | sequenceEqual(compareTo$, comparator?) |
Compares two observables emit same values in same order | Element-wise equality |
| Custom comparator | sequenceEqual(other$, (a, b) => a.id === b.id) |
Define custom equality logic | Flexible comparison |
| Result | Emits single boolean then completes | true if sequences equal, false otherwise | Complete comparison needed |
Example: sequenceEqual for stream comparison
import { of, from } from 'rxjs';
import { sequenceEqual, delay } from 'rxjs/operators';
// Basic sequenceEqual - identical sequences
const a$ = of(1, 2, 3);
const b$ = of(1, 2, 3);
a$.pipe(
sequenceEqual(b$)
).subscribe(equal => console.log('Equal?', equal)); // true
// Different sequences
const c$ = of(1, 2, 3);
const d$ = of(1, 2, 4);
c$.pipe(
sequenceEqual(d$)
).subscribe(equal => console.log('Equal?', equal)); // false
// Different lengths
const e$ = of(1, 2, 3);
const f$ = of(1, 2);
e$.pipe(
sequenceEqual(f$)
).subscribe(equal => console.log('Equal?', equal)); // false
// Timing doesn't matter
const slow$ = of(1, 2, 3).pipe(delay(1000));
const fast$ = of(1, 2, 3);
slow$.pipe(
sequenceEqual(fast$)
).subscribe(equal => console.log('Equal?', equal)); // true
// Custom comparator
const users1$ = of(
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
);
const users2$ = of(
{ id: 1, name: 'Alice Updated' },
{ id: 2, name: 'Bob Updated' }
);
users1$.pipe(
sequenceEqual(users2$, (a, b) => a.id === b.id)
).subscribe(equal => console.log('IDs equal?', equal)); // true
// Practical: Verify data integrity
const original$ = ajax.getJSON('/api/data/original');
const backup$ = ajax.getJSON('/api/data/backup');
original$.pipe(
mergeMap(r => from(r.items)),
sequenceEqual(
backup$.pipe(mergeMap(r => from(r.items)))
)
).subscribe(dataIntact => {
if (dataIntact) {
console.log('Backup verified');
} else {
console.error('Data mismatch detected');
triggerBackupAlert();
}
});
// Practical: Compare form states
const initialFormState$ = of(initialFormData);
const currentFormState$ = of(getCurrentFormData());
initialFormState$.pipe(
sequenceEqual(currentFormState$, deepEqual)
).subscribe(unchanged => {
if (unchanged) {
disableSaveButton();
} else {
enableSaveButton();
}
});
// Practical: Validate sequential processing
const expectedOrder$ = from(['step1', 'step2', 'step3']);
const actualOrder$ = processSteps();
expectedOrder$.pipe(
sequenceEqual(actualOrder$)
).subscribe(correctOrder => {
if (correctOrder) {
console.log('Steps executed in correct order');
markProcessComplete();
} else {
console.error('Step order violation');
abortProcess();
}
});
// Practical: Test API response consistency
const firstCall$ = ajax.getJSON('/api/data');
const secondCall$ = ajax.getJSON('/api/data');
firstCall$.pipe(
mergeMap(r => from(r.items)),
sequenceEqual(
secondCall$.pipe(mergeMap(r => from(r.items)))
)
).subscribe(consistent => {
if (!consistent) {
console.warn('API returned inconsistent data');
}
});
// Practical: Verify replay accuracy
const live$ = dataStream$;
const replayed$ = replayStream$;
live$.pipe(
sequenceEqual(replayed$)
).subscribe(accurate => {
if (accurate) {
console.log('Replay accurate');
} else {
console.error('Replay discrepancy');
}
});
// Practical: Compare user action sequences
const expectedActions$ = from(['login', 'navigate', 'edit', 'save']);
const actualActions$ = from(recordedUserActions);
expectedActions$.pipe(
sequenceEqual(actualActions$)
).subscribe(matchesPattern => {
if (matchesPattern) {
console.log('User followed expected workflow');
} else {
console.log('Workflow deviation detected');
}
});
// Practical: Configuration comparison
const defaultConfig$ = of(defaultConfiguration);
const userConfig$ = of(userConfiguration);
defaultConfig$.pipe(
sequenceEqual(userConfig$, (a, b) => JSON.stringify(a) === JSON.stringify(b))
).subscribe(isDefault => {
if (isDefault) {
hideResetButton();
} else {
showResetButton();
}
});
// Practical: Cache validation
const serverData$ = ajax.getJSON('/api/data').pipe(
mergeMap(r => from(r.items))
);
const cachedData$ = from(cache.getAll());
serverData$.pipe(
sequenceEqual(cachedData$)
).subscribe(cacheValid => {
if (cacheValid) {
useCachedData();
} else {
invalidateCache();
fetchFreshData();
}
});
// Practical: A/B test variant verification
const variantA$ = from(variantAResults);
const variantB$ = from(variantBResults);
variantA$.pipe(
sequenceEqual(variantB$)
).subscribe(identical => {
if (identical) {
console.log('Variants produce identical results');
} else {
console.log('Variants differ - analyze results');
}
});
Note: sequenceEqual requires both observables to complete.
Compares values in order - timing differences ignored.
Section 11 Summary
- iif selects observable at subscription time based on condition - lazy conditional branching
- every returns true if all values satisfy predicate, false on first failure - short-circuits
- find/findIndex emit first matching value/index then complete - efficient searching
- isEmpty emits true if source completes without values - requires completion
- defaultIfEmpty provides fallback value for empty streams - graceful empty handling
- sequenceEqual compares two observables element-wise - supports custom comparators