angular - Como posso convencer o TypeScript de que é ok chamar operadores de Observable em um Subject?
Exemplo de código, 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();
}
}
Quando tento compilar este código, o TypeScript reclama sobre a última linha:
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'.
Meu sentimento é que isso está dizendo que first() é um método de Observable, não de Subject, mas meu entendimento é que um Subject também é um Observable, então isso não deveria ser um problema. E, de fato, se eu ignorar o erro, o código compila e roda bem.
Coisas que tentei:
- Várias maneiras de importar explicitamente o operador
first, por exemplo,import 'rxjs/add/operator/first'. Isso não altera o erro. - Convertendo o Subject para um Observable antes de chamar
first(), por exemplo,return (this.response$ as Observable<boolean>).first();. Isso resulta em um erro diferente, mas semelhante, que também me parece incorreto:Type 'Observable<boolean>' não é atribuível ao tipo 'Subject<boolean>'. A propriedade 'observers' está ausente no tipo 'Observable<boolean>'.
Como posso convencer o TypeScript de que é válido chamar first() em um Subject?
Para esclarecer em resposta à discussão sobre os tipos de retorno abaixo: o método também pode ter tido um tipo de retorno incorreto (deveria ser Observable em vez de Subject), mas isso parece ser um problema separado; mudar o tipo de retorno não resolve o erro descrito acima. Um detalhe adicional que confirma isso é que, quando o vscode destaca o erro, ele apenas destaca this.response$, não toda a linha de retorno, sugerindo que o problema está entre this.response$ e first(), e não entre first() e a assinatura da função:
Resposta #1
Seu entendimento de que um Subject também é um Observable está correto, mas até o ponto de que um Subject também pode ser usado como um Observable. Mas isso não faz de um Subject um Observable e vice-versa.
Como sugerido por ggradnig, você deve mudar o tipo de retorno da sua função confirm para Observable<boolean> em vez de Subject<boolean>, já que o método first no Subject<boolean> retornaria um 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();
}
}
Resposta #2
Resumo: Verificações de tipo mais rigorosas introduzidas no TypeScript 2.4 não são compatíveis com RxJS < 5.4.2 (devido a um bug). Seu IDE provavelmente está usando TypeScript >= 2.4 para verificações de tipo. Você pode atualizar o RxJS (recomendado), fazer downgrade do TypeScript ou usar a solução programática Subject.asObservable juntamente com um tipo de retorno Observable<boolean>.
Explicação:
Após a discussão, eu olhei no código-fonte do RxJS 5.3.0 e descobri que as assinaturas de lift diferem entre Subject e Observable. Especificamente, podemos ver a seguinte diferença:
Subject.ts:
lift<R>(operator: Operator<T, R>): Observable<T>
Observable.ts:
lift<R>(operator: Operator<T, R>): Observable<R>
O tipo genérico do Observable retornado é T em Subject, mas R em Observable.
O operador first() requer um Observable para seu contexto this, que não pode ser correspondido por Subject devido às assinaturas incompatíveis de lift. Isso é muito estranho para mim, pois Subject estende Observable e, portanto, deveria ter assinaturas correspondentes para todas as suas propriedades.
Agora aqui está a solução: o TypeScript introduziu verificações de tipo mais rigorosas a partir da versão 2.4 e, de fato, o RxJS precisou se atualizar com essa mudança alterando a assinatura do método lift. Essa mudança pode ser vista no changelog na versão 5.4.2.
Assunto: assinatura do lift agora é apropriada para verificações mais rigorosas do TypeScript 2.4
Aqui está o problema correspondente. Parece ter sido um bug.
Você está usando RxJS 5.3, então é por isso que você recebe o erro.
Existem duas opções. Ou atualizar o RxJS (recomendado) ou usar Subject.asObservable para mudar o tipo de Subject para Observable.
Como mencionado no meu comentário, o tipo de retorno da sua função também precisa ser alterado para Observable<boolean>.
Aplicado no seu trecho, isso deveria ficar assim:
public confirm(prompt: string): Observable<boolean> {
// ...set up a confirmation dialog...
this.response$ = new Subject<boolean>();
return this.response$.asObservable().first();
}
Vejo que sua versão do TypeScript é 2.2, mas pode muito bem ser que seu IDE use outra versão do TypeScript para verificação de tipos.
Alternativa
Como mencionado por @cartant, Subject.lift é implementado para retornar um Subject, mas isso não pode ser expresso com os tipos do TypeScript (não há sobrecarga de tipo de retorno, nem mesmo tenho certeza se existe uma linguagem que suporte isso).
Então, outra solução é ignorar as verificações de tipo fazendo um cast para <any>. Dessa forma, você também pode retornar Subject.
