angular - ¿Cómo puedo convencer a TypeScript de que está bien llamar a los operadores de Observable en un Subject?
Código de ejemplo, usando Angular 4.1.3, rxjs 5.3.0, TypeScript 2.2.2:
import { Subject } from 'rxjs'
export class Example {
public response$: Subject<boolean>;
public confirm(prompt: string): Subject<boolean> {
// ...set up a confirmation dialog...
this.response$ = new Subject<boolean>();
return this.response$.first();
}
}
Cuando intento compilar este código, TypeScript se queja de la última línea:
The 'this' context of type 'Subject<boolean>' is not assignable to method's 'this' of type 'Observable<boolean>'.
Types of property 'lift' are incompatible.
Type '<R>(operator: Operator<boolean, R>) => Observable<boolean>' is not assignable to type '<R>(operator: Operator<boolean, R>) => Observable<R>'.
Type 'Observable<boolean>' is not assignable to type 'Observable<R>'.
Type 'boolean' is not assignable to type 'R'.
Mi impresión es que esto está diciendo que first() es un método de Observable, no de Subject, pero mi entendimiento es que un Subject también es un Observable, así que esto no debería ser un problema. Y, de hecho, si ignoro el error, el código se compila y se ejecuta bien.
Cosas que he probado:
- Varias formas de importar explícitamente el operador
first, por ejemplo,import 'rxjs/add/operator/first'. Esto no cambia el error. - Convertir el Subject a un Observable antes de llamar a
first(), por ejemplo,return (this.response$ as Observable<boolean>).first();. Esto resulta en un error diferente pero similar, que también me parece incorrecto:Type 'Observable<boolean>' no se puede asignar al tipo 'Subject<boolean>'. Falta la propiedad 'observers' en el tipo 'Observable<boolean>'.
¿Cómo puedo convencer a TypeScript de que es válido llamar a first() en un Subject?
Para aclarar en respuesta a la discusión sobre los tipos de retorno que se menciona a continuación: el método también podría haber tenido un tipo de retorno incorrecto (debería ser Observable en lugar de Subject), pero eso parece ser un problema separado; cambiar el tipo de retorno no resuelve el error descrito anteriormente. Un detalle adicional que confirma esto es que cuando vscode resalta el error, solo resalta this.response$, no toda la línea de retorno, lo que sugiere que el problema está entre this.response$ y first(), no entre first() y la firma de la función:
Respuesta #1
Tu comprensión de que un Subject también es un Observable es correcta, pero hasta el punto de que un Subject también puede ser usado como un Observable. Pero eso no convierte a un Subject en un Observable y viceversa.
Como sugirió ggradnig, deberías cambiar el tipo de retorno de tu función confirm a Observable<boolean> en lugar de Subject<boolean> ya que el método first en el Subject<boolean> devolvería un Observable<boolean>
import { Subject } from 'rxjs'
export class Example {
public response$: Subject < boolean > ;
public confirm(prompt: string): Observable<boolean> {
// ...set up a confirmation dialog...
this.response$ = new Subject < boolean > ();
return this.response$.first();
}
}
Respuesta #2
Resumen: Las verificaciones de tipo más estrictas introducidas en TypeScript 2.4 no son compatibles con RxJS < 5.4.2 (debido a un error). Probablemente tu IDE está usando TypeScript >= 2.4 para las verificaciones de tipo. O bien actualiza RxJS (recomendado), degrada TypeScript, o usa la solución programática Subject.asObservable junto con un tipo de retorno Observable<boolean>.
Explicación:
Después de la discusión, revisé el código fuente de RxJS 5.3.0 y encontré que las firmas de lift difieren entre Subject y Observable. Específicamente, podemos ver la siguiente diferencia:
Subject.ts:
lift<R>(operator: Operator<T, R>): Observable<T>
Observable.ts:
lift<R>(operator: Operator<T, R>): Observable<R>
El tipo genérico del Observable devuelto es T en Subject, pero R en Observable.
El operador first() requiere un Observable para su contexto this, que no puede ser igualado por Subject debido a las firmas incompatibles de lift. Esto me resulta muy extraño, ya que Subject extiende Observable y, por lo tanto, debería tener firmas coincidentes para todas sus propiedades.
Ahora aquí está la solución: TypeScript introdujo verificaciones de tipo más estrictas con la versión 2.4 y, de hecho, RxJS necesitaba ponerse al día con ese cambio modificando la firma del método lift. Este cambio se puede ver en el registro de cambios en la versión 5.4.2.
Asunto: la firma de lift ahora es apropiada para comprobaciones más estrictas de TypeScript 2.4
Aquí está el problema correspondiente. Parece que ha sido un error.
Estás usando RxJS 5.3, por eso es que recibes el error.
Hay dos opciones. O actualizar RxJS (recomendado) o usar Subject.asObservable para cambiar el tipo de Subject a Observable.
Como mencioné en mi comentario, el tipo de retorno de tu función también necesita cambiar a Observable<boolean>.
Aplicado en tu fragmento, esto debería verse así:
public confirm(prompt: string): Observable<boolean> {
// ...set up a confirmation dialog...
this.response$ = new Subject<boolean>();
return this.response$.asObservable().first();
}
Veo que tu versión de TypeScript es 2.2, pero muy bien puede ser que tu IDE use otra versión de TypeScript para las verificaciones de tipos.
Alternativa
Como mencionó @cartant, Subject.lift está implementado para devolver un Subject, pero eso no se puede expresar con los tipos de TypeScript (sin sobrecarga de tipo de retorno, ni siquiera estoy seguro de si existe un lenguaje que lo soporte).
Entonces, otra solución es ignorar las comprobaciones de tipo convirtiendo a <any>. De esa manera también puedes devolver Subject.
