package.fesm2022.rxjs-interop.mjs Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of core Show documentation
Show all versions of core Show documentation
Angular - the core framework
/**
* @license Angular v18.2.6
* (c) 2010-2024 Google LLC. https://angular.io/
* License: MIT
*/
import { assertInInjectionContext, inject, DestroyRef, ɵRuntimeError, ɵgetOutputDestroyRef, Injector, effect, untracked, assertNotInReactiveContext, signal, computed } from '@angular/core';
import { Observable, ReplaySubject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
/**
* Operator which completes the Observable when the calling context (component, directive, service,
* etc) is destroyed.
*
* @param destroyRef optionally, the `DestroyRef` representing the current context. This can be
* passed explicitly to use `takeUntilDestroyed` outside of an [injection
* context](guide/di/dependency-injection-context). Otherwise, the current `DestroyRef` is injected.
*
* @developerPreview
*/
function takeUntilDestroyed(destroyRef) {
if (!destroyRef) {
assertInInjectionContext(takeUntilDestroyed);
destroyRef = inject(DestroyRef);
}
const destroyed$ = new Observable((observer) => {
const unregisterFn = destroyRef.onDestroy(observer.next.bind(observer));
return unregisterFn;
});
return (source) => {
return source.pipe(takeUntil(destroyed$));
};
}
/**
* Implementation of `OutputRef` that emits values from
* an RxJS observable source.
*
* @internal
*/
class OutputFromObservableRef {
constructor(source) {
this.source = source;
this.destroyed = false;
this.destroyRef = inject(DestroyRef);
this.destroyRef.onDestroy(() => {
this.destroyed = true;
});
}
subscribe(callbackFn) {
if (this.destroyed) {
throw new ɵRuntimeError(953 /* ɵRuntimeErrorCode.OUTPUT_REF_DESTROYED */, ngDevMode &&
'Unexpected subscription to destroyed `OutputRef`. ' +
'The owning directive/component is destroyed.');
}
// Stop yielding more values when the directive/component is already destroyed.
const subscription = this.source.pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
next: (value) => callbackFn(value),
});
return {
unsubscribe: () => subscription.unsubscribe(),
};
}
}
/**
* Declares an Angular output that is using an RxJS observable as a source
* for events dispatched to parent subscribers.
*
* The behavior for an observable as source is defined as followed:
* 1. New values are forwarded to the Angular output (next notifications).
* 2. Errors notifications are not handled by Angular. You need to handle these manually.
* For example by using `catchError`.
* 3. Completion notifications stop the output from emitting new values.
*
* @usageNotes
* Initialize an output in your directive by declaring a
* class field and initializing it with the `outputFromObservable()` function.
*
* ```ts
* @Directive({..})
* export class MyDir {
* nameChange$ = ;
* nameChange = outputFromObservable(this.nameChange$);
* }
* ```
*
* @developerPreview
*/
function outputFromObservable(observable, opts) {
ngDevMode && assertInInjectionContext(outputFromObservable);
return new OutputFromObservableRef(observable);
}
/**
* Converts an Angular output declared via `output()` or `outputFromObservable()`
* to an observable.
*
* You can subscribe to the output via `Observable.subscribe` then.
*
* @developerPreview
*/
function outputToObservable(ref) {
const destroyRef = ɵgetOutputDestroyRef(ref);
return new Observable((observer) => {
// Complete the observable upon directive/component destroy.
// Note: May be `undefined` if an `EventEmitter` is declared outside
// of an injection context.
destroyRef?.onDestroy(() => observer.complete());
const subscription = ref.subscribe((v) => observer.next(v));
return () => subscription.unsubscribe();
});
}
/**
* Exposes the value of an Angular `Signal` as an RxJS `Observable`.
*
* The signal's value will be propagated into the `Observable`'s subscribers using an `effect`.
*
* `toObservable` must be called in an injection context unless an injector is provided via options.
*
* @developerPreview
*/
function toObservable(source, options) {
!options?.injector && assertInInjectionContext(toObservable);
const injector = options?.injector ?? inject(Injector);
const subject = new ReplaySubject(1);
const watcher = effect(() => {
let value;
try {
value = source();
}
catch (err) {
untracked(() => subject.error(err));
return;
}
untracked(() => subject.next(value));
}, { injector, manualCleanup: true });
injector.get(DestroyRef).onDestroy(() => {
watcher.destroy();
subject.complete();
});
return subject.asObservable();
}
/**
* Get the current value of an `Observable` as a reactive `Signal`.
*
* `toSignal` returns a `Signal` which provides synchronous reactive access to values produced
* by the given `Observable`, by subscribing to that `Observable`. The returned `Signal` will always
* have the most recent value emitted by the subscription, and will throw an error if the
* `Observable` errors.
*
* With `requireSync` set to `true`, `toSignal` will assert that the `Observable` produces a value
* immediately upon subscription. No `initialValue` is needed in this case, and the returned signal
* does not include an `undefined` type.
*
* By default, the subscription will be automatically cleaned up when the current [injection
* context](guide/di/dependency-injection-context) is destroyed. For example, when `toSignal` is
* called during the construction of a component, the subscription will be cleaned up when the
* component is destroyed. If an injection context is not available, an explicit `Injector` can be
* passed instead.
*
* If the subscription should persist until the `Observable` itself completes, the `manualCleanup`
* option can be specified instead, which disables the automatic subscription teardown. No injection
* context is needed in this configuration as well.
*
* @developerPreview
*/
function toSignal(source, options) {
ngDevMode &&
assertNotInReactiveContext(toSignal, 'Invoking `toSignal` causes new subscriptions every time. ' +
'Consider moving `toSignal` outside of the reactive context and read the signal value where needed.');
const requiresCleanup = !options?.manualCleanup;
requiresCleanup && !options?.injector && assertInInjectionContext(toSignal);
const cleanupRef = requiresCleanup
? (options?.injector?.get(DestroyRef) ?? inject(DestroyRef))
: null;
const equal = makeToSignalEqual(options?.equal);
// Note: T is the Observable value type, and U is the initial value type. They don't have to be
// the same - the returned signal gives values of type `T`.
let state;
if (options?.requireSync) {
// Initially the signal is in a `NoValue` state.
state = signal({ kind: 0 /* StateKind.NoValue */ }, { equal });
}
else {
// If an initial value was passed, use it. Otherwise, use `undefined` as the initial value.
state = signal({ kind: 1 /* StateKind.Value */, value: options?.initialValue }, { equal });
}
// Note: This code cannot run inside a reactive context (see assertion above). If we'd support
// this, we would subscribe to the observable outside of the current reactive context, avoiding
// that side-effect signal reads/writes are attribute to the current consumer. The current
// consumer only needs to be notified when the `state` signal changes through the observable
// subscription. Additional context (related to async pipe):
// https://github.com/angular/angular/pull/50522.
const sub = source.subscribe({
next: (value) => state.set({ kind: 1 /* StateKind.Value */, value }),
error: (error) => {
if (options?.rejectErrors) {
// Kick the error back to RxJS. It will be caught and rethrown in a macrotask, which causes
// the error to end up as an uncaught exception.
throw error;
}
state.set({ kind: 2 /* StateKind.Error */, error });
},
// Completion of the Observable is meaningless to the signal. Signals don't have a concept of
// "complete".
});
if (options?.requireSync && state().kind === 0 /* StateKind.NoValue */) {
throw new ɵRuntimeError(601 /* ɵRuntimeErrorCode.REQUIRE_SYNC_WITHOUT_SYNC_EMIT */, (typeof ngDevMode === 'undefined' || ngDevMode) &&
'`toSignal()` called with `requireSync` but `Observable` did not emit synchronously.');
}
// Unsubscribe when the current context is destroyed, if requested.
cleanupRef?.onDestroy(sub.unsubscribe.bind(sub));
// The actual returned signal is a `computed` of the `State` signal, which maps the various states
// to either values or errors.
return computed(() => {
const current = state();
switch (current.kind) {
case 1 /* StateKind.Value */:
return current.value;
case 2 /* StateKind.Error */:
throw current.error;
case 0 /* StateKind.NoValue */:
// This shouldn't really happen because the error is thrown on creation.
throw new ɵRuntimeError(601 /* ɵRuntimeErrorCode.REQUIRE_SYNC_WITHOUT_SYNC_EMIT */, (typeof ngDevMode === 'undefined' || ngDevMode) &&
'`toSignal()` called with `requireSync` but `Observable` did not emit synchronously.');
}
}, { equal: options?.equal });
}
function makeToSignalEqual(userEquality = Object.is) {
return (a, b) => a.kind === 1 /* StateKind.Value */ && b.kind === 1 /* StateKind.Value */ && userEquality(a.value, b.value);
}
/**
* Generated bundle index. Do not edit.
*/
export { outputFromObservable, outputToObservable, takeUntilDestroyed, toObservable, toSignal };
//# sourceMappingURL=rxjs-interop.mjs.map