Search code examples
angulartypescriptangular-pipe

Using infer to grab the boxed Observable value


I've created a pipe to convert any source to an Observable, as follows:

// If T is like an Observable it infers the inner value, otherwise it returns T
type Unobservable<T> = T extends Observable<infer R> ? R : T;

@Pipe({
  name: 'toObservable'
})
export class ToObservablePipe implements PipeTransform {
  transform<T>(value: T): Observable<Unobservable<T>> {
    return isObservable(value) ? value : just(value);
  }
}

The functionality itself works, however I'm facing a problem with the return type. Currently it shows the following error:

Type '(T & Observable) | Observable' is not assignable to type 'Observable>'. Type 'T & Observable' is not assignable to type 'Observable>'. Type 'unknown' is not assignable to type 'Unobservable'.

Just to context you, the use of the pipe can be something like this:

<h4>Array source</h4>

<pre>{{ array | toObservable | async | json }}</pre>

How can I make compiler "happy"? :)

DEMO


Solution

  • You can use an overload instead, as the @angular/AsyncPipe:

    import { Pipe, PipeTransform } from '@angular/core';
    import { isObservable, Observable, of as just } from 'rxjs';
    
    @Pipe({
      name: 'toObservable'
    })
    export class ToObservablePipe implements PipeTransform {
      transform<T>(value: null): null;
      transform<T>(value: undefined): undefined;
      transform<T>(value: Observable<T> | null | undefined): Observable<T> | null | undefined;
      transform<T>(value: T): Observable<T> | null | undefined;
      transform(value: Observable<any> | null | undefined): any {
        // can be also if (value === null || value === undefined) ...
        if (value == null) return value;
    
        return isObservable(value) ? value : just(value);
      }
    }
    

    WORKING DEMO


    Some test cases:

    const obj = { label: 1 };
    
    // Error!
    new ToObservablePipe().transform(null).subscribe(response => console.log(response));
    
    // Error!
    new ToObservablePipe().transform(undefined).subscribe(response => console.log(response));
    
    // Inferred as string
    new ToObservablePipe().transform('').subscribe(response => console.log(response));
    
    // Inferred as number
    new ToObservablePipe().transform(0).subscribe(response => console.log(response));
    
    // Inferred as { label: number }
    new ToObservablePipe().transform(just(obj)).subscribe(response => console.log(response));
    
    // Inferred as { label: number }[]
    new ToObservablePipe().transform([obj]).subscribe(response => console.log(response));
    
    // Inferred as { label: number }
    new ToObservablePipe().transform(obj).subscribe(response => console.log(response));