or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

application-management.mdchange-detection.mdcomponent-system.mddependency-injection.mdindex.mdmodern-authoring.mdresource-api.mdrxjs-interop.mdtesting.mdutilities-helpers.md
tile.json

rxjs-interop.mddocs/

RxJS Interoperability

Angular's RxJS interoperability APIs provide seamless integration between Angular's signal-based reactivity system and RxJS observables, enabling conversion between signals and observables in both directions.

Capabilities

Signal to Observable Conversion

Convert signals to observables for integration with RxJS-based code and reactive programming patterns.

/**
 * Converts a signal to an Observable
 * @param source - Signal to convert to Observable
 * @param options - Optional configuration
 * @returns Observable that emits signal values
 */
function toObservable<T>(
  source: Signal<T>,
  options?: {
    injector?: Injector;
  }
): Observable<T>;

Observable to Signal Conversion

Convert observables to signals for integration with Angular's signal-based reactivity system.

/**
 * Converts an Observable to a Signal
 * @param source - Observable to convert to Signal
 * @param options - Optional configuration
 * @returns Signal that reflects Observable values
 */
function toSignal<T>(
  source: Observable<T>,
  options: {
    initialValue?: undefined;
    injector?: Injector;
    manualCleanup?: boolean;
    rejectErrors?: boolean;
  } & ({requireSync: true} | {initialValue: T})
): Signal<T>;

/**
 * Converts an Observable to a Signal with initial value
 * @param source - Observable to convert to Signal
 * @param options - Configuration with initial value
 * @returns Signal that reflects Observable values
 */
function toSignal<T>(
  source: Observable<T>,
  options: {
    initialValue: T;
    injector?: Injector;
    manualCleanup?: boolean;
    rejectErrors?: boolean;
  }
): Signal<T>;

/**
 * Converts an Observable to a Signal without initial value
 * @param source - Observable to convert to Signal
 * @param options - Optional configuration
 * @returns Signal that may be undefined initially
 */
function toSignal<T>(
  source: Observable<T>,
  options?: {
    initialValue?: undefined;
    injector?: Injector;
    manualCleanup?: boolean;
    rejectErrors?: boolean;
  }
): Signal<T | undefined>;

Lifecycle Integration

RxJS operators and utilities for integration with Angular component lifecycle.

/**
 * RxJS operator that completes the stream when component is destroyed
 * @param destroyRef - Optional DestroyRef, will be injected if not provided
 * @returns MonoTypeOperatorFunction that completes on destroy
 */
function takeUntilDestroyed<T>(destroyRef?: DestroyRef): MonoTypeOperatorFunction<T>;

Output Integration

Create Angular outputs from observables and convert outputs to observables.

/**
 * Creates an Angular output from an Observable
 * @param source - Observable to convert to output
 * @param options - Optional configuration
 * @returns OutputRef for the observable
 */
function outputFromObservable<T>(
  source: Observable<T>,
  options?: {
    injector?: Injector;
  }
): OutputRef<T>;

/**
 * Converts an Angular output to an Observable
 * @param output - Output to convert to Observable
 * @param options - Optional configuration
 * @returns Observable of output events
 */
function outputToObservable<T>(
  output: OutputRef<T>,
  options?: {
    injector?: Injector;
  }
): Observable<T>;

Resource Integration

Create resources from observables for server-side rendering and data fetching patterns.

/**
 * Creates a resource from an Observable
 * @param source - Observable to create resource from
 * @param options - Optional configuration
 * @returns Resource wrapping the observable
 */
function rxResource<T>(
  source: Observable<T>,
  options?: {
    injector?: Injector;
  }
): Resource<T>;

Event Handling

Utilities for handling events in reactive contexts.

/**
 * Creates a pending state that resolves when specified event occurs
 * @param target - Event target (element, component, etc.)
 * @param eventName - Name of event to wait for
 * @param options - Optional configuration
 * @returns Promise that resolves when event occurs
 */
function pendingUntilEvent<T = Event>(
  target: EventTarget,
  eventName: string,
  options?: {
    injector?: Injector;
  }
): Promise<T>;

Usage Examples

Converting Signals to Observables

import { Component, signal, inject } from '@angular/core';
import { toObservable } from '@angular/core/rxjs-interop';
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-search',
  template: `
    <div>
      <input 
        [(ngModel)]="searchTerm" 
        (input)="updateSearchTerm($event)"
        placeholder="Search users..."
      >
      <ul>
        <li *ngFor="let user of searchResults()">
          {{user.name}} - {{user.email}}
        </li>
      </ul>
    </div>
  `
})
export class SearchComponent {
  private http = inject(HttpClient);
  
  // Signal for search term
  private searchTermSignal = signal('');
  
  // Convert signal to observable for RxJS operations
  private searchTerm$ = toObservable(this.searchTermSignal);
  
  // Use RxJS operators for debouncing and API calls
  private searchResults$ = this.searchTerm$.pipe(
    debounceTime(300),
    distinctUntilChanged(),
    switchMap(term => 
      term ? this.http.get<User[]>(`/api/users?search=${term}`) : []
    )
  );
  
  // Convert result back to signal
  searchResults = toSignal(this.searchResults$, { initialValue: [] });
  
  updateSearchTerm(event: Event): void {
    const target = event.target as HTMLInputElement;
    this.searchTermSignal.set(target.value);
  }
}

Converting Observables to Signals

import { Component, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { toSignal } from '@angular/core/rxjs-interop';
import { interval, combineLatest } from 'rxjs';
import { map, startWith } from 'rxjs/operators';

interface WeatherData {
  temperature: number;
  humidity: number;
  conditions: string;
}

@Component({
  selector: 'app-weather',
  template: `
    <div>
      <h3>Current Weather</h3>
      <div *ngIf="weather(); else loading">
        <p>Temperature: {{weather()?.temperature}}°F</p>
        <p>Humidity: {{weather()?.humidity}}%</p>
        <p>Conditions: {{weather()?.conditions}}</p>
        <p>Last updated: {{lastUpdate() | date:'medium'}}</p>
      </div>
      <ng-template #loading>
        <p>Loading weather data...</p>
      </ng-template>
    </div>
  `
})
export class WeatherComponent {
  private http = inject(HttpClient);
  
  // Observable that refreshes every 5 minutes
  private refreshTimer$ = interval(5 * 60 * 1000).pipe(startWith(0));
  
  // Observable for weather data
  private weatherData$ = this.refreshTimer$.pipe(
    switchMap(() => this.http.get<WeatherData>('/api/weather'))
  );
  
  // Convert to signals
  weather = toSignal(this.weatherData$);
  lastUpdate = toSignal(
    this.refreshTimer$.pipe(map(() => new Date())),
    { initialValue: new Date() }
  );
}

Lifecycle Integration

import { Component, OnInit, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { interval } from 'rxjs';
import { tap } from 'rxjs/operators';

@Component({
  selector: 'app-timer',
  template: `
    <div>
      <h3>Timer Component</h3>
      <p>Elapsed: {{elapsed}} seconds</p>
      <p>Status: {{status}}</p>
    </div>
  `
})
export class TimerComponent implements OnInit {
  elapsed = 0;
  status = 'Starting...';
  
  ngOnInit(): void {
    // Timer that automatically stops when component is destroyed
    interval(1000)
      .pipe(
        takeUntilDestroyed(), // Automatically unsubscribes on destroy
        tap(count => {
          this.elapsed = count + 1;
          this.status = 'Running';
        })
      )
      .subscribe();
    
    // Manual cleanup example with custom DestroyRef
    const destroyRef = inject(DestroyRef);
    interval(5000)
      .pipe(
        takeUntilDestroyed(destroyRef),
        tap(() => console.log('Periodic log every 5 seconds'))
      )
      .subscribe();
  }
}

Output Integration

import { Component, signal } from '@angular/core';
import { outputFromObservable, outputToObservable } from '@angular/core/rxjs-interop';
import { Subject, merge } from 'rxjs';
import { map, filter } from 'rxjs/operators';

@Component({
  selector: 'app-custom-input',
  template: `
    <div>
      <input 
        #input
        (input)="handleInput($event)"
        (keyup.enter)="handleEnter()"
        (blur)="handleBlur()"
      >
      <p>Current value: {{value()}}</p>
    </div>
  `
})
export class CustomInputComponent {
  private inputSubject = new Subject<string>();
  private enterSubject = new Subject<string>();
  private blurSubject = new Subject<string>();
  
  // Signal for current value
  value = signal('');
  
  // Create outputs from observables
  valueChange = outputFromObservable(
    merge(
      this.inputSubject,
      this.enterSubject,
      this.blurSubject
    )
  );
  
  enterPressed = outputFromObservable(
    this.enterSubject.pipe(
      filter(value => value.length > 0),
      map(() => ({ value: this.value(), timestamp: Date.now() }))
    )
  );
  
  focusLost = outputFromObservable(this.blurSubject);
  
  handleInput(event: Event): void {
    const target = event.target as HTMLInputElement;
    const value = target.value;
    this.value.set(value);
    this.inputSubject.next(value);
  }
  
  handleEnter(): void {
    this.enterSubject.next(this.value());
  }
  
  handleBlur(): void {
    this.blurSubject.next(this.value());
  }
}

Resource Integration

import { Component, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { rxResource } from '@angular/core/rxjs-interop';
import { switchMap, retry, catchError } from 'rxjs/operators';
import { of } from 'rxjs';

interface UserProfile {
  id: number;
  name: string;
  email: string;
  avatar: string;
}

@Component({
  selector: 'app-user-profile',
  template: `
    <div>
      <h3>User Profile</h3>
      <div [ngSwitch]="userResource.status()">
        <div *ngSwitchCase="'loading'">
          <p>Loading user profile...</p>
        </div>
        <div *ngSwitchCase="'error'">
          <p>Error loading profile: {{userResource.error()?.message}}</p>
          <button (click)="refreshProfile()">Retry</button>
        </div>
        <div *ngSwitchCase="'resolved'">
          <div class="profile">
            <img [src]="userResource.value()?.avatar" [alt]="userResource.value()?.name">
            <h4>{{userResource.value()?.name}}</h4>
            <p>{{userResource.value()?.email}}</p>
            <button (click)="refreshProfile()">Refresh</button>
          </div>
        </div>
      </div>
    </div>
  `
})
export class UserProfileComponent {
  private http = inject(HttpClient);
  
  // Create resource from observable with error handling and retry
  userResource = rxResource(
    this.http.get<UserProfile>('/api/user/profile').pipe(
      retry(3),
      catchError(error => {
        console.error('Failed to load user profile:', error);
        throw error;
      })
    )
  );
  
  refreshProfile(): void {
    // Resource will automatically re-execute the observable
    this.userResource.reload();
  }
}

Event Handling

import { Component, ElementRef, viewChild, AfterViewInit } from '@angular/core';
import { pendingUntilEvent } from '@angular/core/rxjs-interop';

@Component({
  selector: 'app-loading-animation',
  template: `
    <div>
      <img 
        #image
        [src]="imageUrl" 
        [class.loaded]="imageLoaded"
        alt="Content image"
      >
      <div *ngIf="!imageLoaded" class="loading-spinner">
        Loading...
      </div>
    </div>
  `,
  styles: [`
    img {
      opacity: 0;
      transition: opacity 0.3s ease;
    }
    img.loaded {
      opacity: 1;
    }
    .loading-spinner {
      display: inline-block;
      /* spinner styles */
    }
  `]
})
export class LoadingAnimationComponent implements AfterViewInit {
  imageRef = viewChild.required<ElementRef<HTMLImageElement>>('image');
  
  imageUrl = 'https://example.com/large-image.jpg';
  imageLoaded = false;
  
  async ngAfterViewInit(): Promise<void> {
    const img = this.imageRef().nativeElement;
    
    try {
      // Wait for image load event
      await pendingUntilEvent(img, 'load');
      this.imageLoaded = true;
      console.log('Image loaded successfully');
    } catch (error) {
      // Handle error event
      await pendingUntilEvent(img, 'error');
      console.error('Image failed to load');
      this.imageLoaded = false;
    }
  }
}