Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type is not inferred correctly using filter operator #3125

Closed
rafaelss95 opened this issue Nov 19, 2017 · 14 comments
Closed

Type is not inferred correctly using filter operator #3125

rafaelss95 opened this issue Nov 19, 2017 · 14 comments

Comments

@rafaelss95
Copy link

RxJS version:
5.5.2

Code to reproduce:
Consider this piece of code:

constructor(private router: Router) { }

ngOnInit() {
  this.router.events.pipe(
    filter(e => e instanceof NavigationEnd)
  ).subscribe(
    // e is inferred as RouterEvent | RouteConfigLoadStart | RouteConfigLoadEnd | ChildActivationStart | ChildActivationEnd | ActivationStart | ActivationEnd
    e => console.log(e)
  );

Expected behavior:
I'd expect e to be inferred as NavigationEnd.

Actual behavior:
I'm using Angular 5 and in code above, I'm using filter operator to "listen" only events referent to NavigationEnd, however the type isn't being inferred correctly. Instead of getting e as NavigationEnd, I'm getting e as RouterEvent | RouteConfigLoadStart | RouteConfigLoadEnd | ChildActivationStart | ChildActivationEnd | ActivationStart | ActivationEnd.

Additional information:
DEMO

@cartant
Copy link
Collaborator

cartant commented Nov 19, 2017

The problem is that the function you are passing to filter is not a type guard, so the overload signature that accepts a type guard is not matched and no narrowing is performed. You need to indicate to TypeScript that the function is a type guard:

filter((e: Event): e is NavigationEnd => e instanceof NavigationEnd)

@rafaelss95
Copy link
Author

rafaelss95 commented Nov 19, 2017

Thanks for your comment, but... how the function that i'm passing isn't a type guard? It works well if I use it with an if (see the last solution - in the end -).

I tested your proposed solution:

filter((e: Event): e is NavigationEnd => e instanceof NavigationEnd) doesn't compile:

Type 'RouterEvent' is not assignable to type 'Event'.

So, I removed the (e: Event) part:

filter((e): e is NavigationEnd => e instanceof NavigationEnd)

... and it does change nothing at all, the type is still incorrectly inferred.


Btw, the solutions I came up are:

1 - Cast it:

this.router.events.pipe(
    filter<NavigationEnd>(e => e instanceof NavigationEnd)
  ).subscribe(
    // e is inferred as NaviagtionEnd
    e => console.log(e)
  );

2 - Crazy option -> Don't use filter:

this.router.events.subscribe(
  e => {
    if (e instanceof NavigationEnd) {
      // e is inferred as NavigationEnd
      console.log(e);
    }
  }
);


@cartant
Copy link
Collaborator

cartant commented Nov 19, 2017

I briefly looked at the Angular docs and those that I saw suggested the events observable in the Router emitted Event instances. If that's wrong or out-of-date, just use the appropriate type:

filter((e: RouterEvent): e is NavigationEnd => e instanceof NavigationEnd)

The key point is that this is not an RxJS issue; it's a TypeScript issue. The function has to be explicitly made a type guard, it won't be inferred as one just because it happens to return the result of an instanceof expression.

@rafaelss95
Copy link
Author

rafaelss95 commented Nov 19, 2017

Strange. How is it a typescript issue if I can get the correct type with the following code (using type guard also)?

this.router.events.subscribe(
  e => {
    if (e instanceof NavigationEnd) {
      // e is inferred as NavigationEnd
      console.log(e);
    }
  }
);

@ptitjes
Copy link

ptitjes commented Nov 19, 2017

@rafaelss95 To complete @cartant's answer, you can read more in TypeScript's documentation about the so-called Type Guards. Note the use of the special return type for the function passed to filter: aParam is AType, that is what helps TypeScript's type inference!

@cartant
Copy link
Collaborator

cartant commented Nov 20, 2017

Because this:

if (e instanceof NavigationEnd) {

is a type guard, but this:

e => e instanceof NavigationEnd

is not a user-defined type guard. However, this:

(e: RouterEvent): e is NavigationEnd => e instanceof NavigationEnd

is a user-defined type guard.

@rafaelss95
Copy link
Author

Ok, I got it. Now the question is: does it works as intended or its a bug?

@ptitjes
Copy link

ptitjes commented Nov 20, 2017

I think rxjs can't do more than what TypeScript is capable. So you can consider that it is as intended. :)

@rafaelss95
Copy link
Author

rafaelss95 commented Nov 30, 2017

I just enabled --strict on tsconfig.json and the proposed solution doesn't work.

this.router.events.pipe(
  // doesn't compile... e is not assignable...
  filter((e: RouterEvent): e is NavigationEnd => e instanceof NavigationEnd)
).subscribe(
   // ... 
);

Workaround:

this.router.events.pipe(
  filter(e => e instanceof NavigationEnd),
  map(e => e as NavigationEnd)
).subscribe(
   // ... 
);

@sandangel
Copy link

not work for me too.

dec-03-2017 20-10-18

@benschulz
Copy link

benschulz commented Dec 14, 2017

This is not an rxjs issue. Related upstream issues are microsoft/TypeScript#5101 and microsoft/TypeScript#10734.

@kwonoj
Copy link
Member

kwonoj commented Dec 14, 2017

I'm going to close issue as it's sufficiently explained for reason, and it's upstream issue that we can't make action at this moment until we get compiler support.

@Hotell
Copy link

Hotell commented Dec 24, 2017

any idea how to hotfix this for ofType lettable operator ? -> redux-observable/redux-observable#382

cc @cartant

@lock
Copy link

lock bot commented Jun 6, 2018

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@lock lock bot locked as resolved and limited conversation to collaborators Jun 6, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants