Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
feat(core): implement takeUntilDestroyed in rxjs-interop
This commit implements an RxJS operator `takeUntilDestroyed` which
terminates an Observable when the current context (component, directive,
etc) is destroyed. `takeUntilDestroyed` will inject the current `DestroyRef`
if none is provided, or use one provided as an argument.
  • Loading branch information
alxhub committed Mar 30, 2023
commit 877d1005065d08a593c29c35cd3be283baa46539
5 changes: 5 additions & 0 deletions goldens/public-api/core/rxjs-interop/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

```ts

import { DestroyRef } from '@angular/core';
import { Injector } from '@angular/core';
import { MonoTypeOperatorFunction } from 'rxjs';
import { Observable } from 'rxjs';
import { Signal } from '@angular/core';

Expand All @@ -22,6 +24,9 @@ export interface FromSignalOptions {
injector?: Injector;
}

// @public
export function takeUntilDestroyed<T>(destroyRef?: DestroyRef): MonoTypeOperatorFunction<T>;

// (No @packageDocumentation comment for this package)

```
1 change: 1 addition & 0 deletions packages/core/rxjs-interop/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@

export {fromObservable} from './from_observable';
export {fromSignal, FromSignalOptions} from './from_signal';
export {takeUntilDestroyed} from './take_until_destroyed';
36 changes: 36 additions & 0 deletions packages/core/rxjs-interop/src/take_until_destroyed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {assertInInjectionContext, DestroyRef, inject} from '@angular/core';
import {MonoTypeOperatorFunction, Observable} 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. Otherwise, the
* current `DestroyRef` is injected.
*
* @developerPreview
*/
export function takeUntilDestroyed<T>(destroyRef?: DestroyRef): MonoTypeOperatorFunction<T> {
Comment thread
alxhub marked this conversation as resolved.
Outdated
if (!destroyRef) {
assertInInjectionContext(takeUntilDestroyed);
destroyRef = inject(DestroyRef);
}

const destroyed$ = new Observable<void>(observer => {
destroyRef!.onDestroy(observer.next.bind(observer));
});

return <T>(source: Observable<T>) => {
return source.pipe(takeUntil(destroyed$));
};
}
66 changes: 66 additions & 0 deletions packages/core/rxjs-interop/test/take_until_destroyed_spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {DestroyRef, EnvironmentInjector, Injector, runInInjectionContext} from '@angular/core';
import {BehaviorSubject} from 'rxjs';

import {takeUntilDestroyed} from '../src/take_until_destroyed';

describe('takeUntilDestroyed', () => {
it('should complete an observable when the current context is destroyed', () => {
const injector = Injector.create({providers: []}) as EnvironmentInjector;
const source$ = new BehaviorSubject(0);
const tied$ = runInInjectionContext(injector, () => source$.pipe(takeUntilDestroyed()));

let completed = false;
let last = 0;

tied$.subscribe({
next(value) {
last = value;
},
complete() {
completed = true;
}
});

source$.next(1);
expect(last).toBe(1);

injector.destroy();
expect(completed).toBeTrue();
source$.next(2);
expect(last).toBe(1);
});

it('should allow a manual DestroyRef to be passed', () => {
const injector = Injector.create({providers: []}) as EnvironmentInjector;
const source$ = new BehaviorSubject(0);
const tied$ = source$.pipe(takeUntilDestroyed(injector.get(DestroyRef)));

let completed = false;
let last = 0;

tied$.subscribe({
next(value) {
last = value;
},
complete() {
completed = true;
}
});

source$.next(1);
expect(last).toBe(1);

injector.destroy();
expect(completed).toBeTrue();
source$.next(2);
expect(last).toBe(1);
});
});