Rxjs: .withLatestFrom doesn't seem to work as an add operator

Created on 17 Aug 2016  ·  26Comments  ·  Source: ReactiveX/rxjs

RxJS version:
5, beta11
Code to reproduce:

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/merge';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/scan';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/operator/withLatestFrom';

Expected behavior:
Lightweight import of Observable with operators added.
Actual behavior:
Merge, map etc all work. withLatestFrom import apparently occurs (browserify builds without error, versus say trying import 'rxjs/observable/withLatestFrom' like required for merge) but does not attach the function to Observable (index.js:49 Uncaught TypeError: filterGetQuery$.withLatestFrom is not a function.

Additional information:

Most helpful comment

Assuming this is node on a server. This is basically how node_modules work. require will look for the nearest node_modules/rxjs folder that it can.

In the case of npm link and modules, they will all have different identities, and then in fact completely different Observables.

Are you using TypeScript, JavaScript, ES6 Syntax / Babel?

Is function bind (::) an option for you? You can use :: that is probably a more straightforward solution.

All 26 comments

missing add in path of import 'rxjs/operator/withLatestFrom'; is just copy-paste incorrection or actual code missing those as well?

fixed, just wrong paste of trying different things sorry :P Yes it doesn't work with the correct path. I have about 15 of these operators imported in various files and it's just withLatestFrom that doesn't work... EDIT and apparently, sample.

interesting. I just did try bare minimum like below

var Observable = require('rxjs/Observable').Observable;
require('rxjs/add/observable/of');
require('rxjs/add/operator/withLatestFrom');

const a = Observable.of(1);
const b = Observable.of('b');

a.withLatestFrom(b, (a,b) => String(a) + String(b)).subscribe(console.log.bind(console));

and could see operator's correctly patched. Would you mind share small repo that can reproduce issues to take a look into? (and does above snippet works for you?)

Interesting, I have figured it out. Maybe kinda obvious, maybe not...
If I have an Observable starting in one external npm module, that flows through to end users in other modules, all the operators need to be added to augment that observable before it is declared...Which means that the source module containing the observable would need to proactively predict all the operators that users of that module will want access to.
eg:

import stream from 'external-module'
import 'rxjs/add/operator/withLatestFrom'

stream
  .withLatestFrom // won't work. 

Here, .withLatestFrom would have to be added in the external module, yet you couldn't predict in that module all the ways the end users will be then manipulating those streams (and I have found I really need to trim down the operators in this way - importing rxjs whole causes very noticeable delays in import refresh times).

Is there any way selectively upgrade Observables without having to make everything available from the start?

So external-module internally imports Observable with operators selectively while that module is separate npm package, and you'd like to patch that Observable's operator instead of what you loaded in your code's scope, is my understanding correct?

Yes. The goal is to keep the observable source as lightweight as possible,
then incrementally augment it only as required as it flows through a series
of modules.

It seems then that the add import command is augmenting the wrong
observable, which means that import {Observable} from 'rxjs/observable'
in multiple locations is not importing a singleton as hoped (or Browserify
if is not recognizing the reoccurrence as such). Either way, this could
potentially mean that every add operator in every new module would be
further duplicating the code.

Whilst I realise that import does not guarantee a singleton, I had hoped
there would be some way to achieve the above. I'm not sure now if this is
something more for Stack Overflow now, but it would seem to me that these
add operators should work in this fashion otherwise imported observables
will always be locked into their lowest form (unless you imported, created
a new observable then flatmapped the imported one every time...)

I see, it's interesting question.

For cases of in multiple locations is not importing a singleton as hoped, Whilst I realise that import does not guarantee a singleton, it is not singleton indeed. Each external module will have different scope to module internally imported compare to what you have imported in your user code in general. (depends on tools, compilers, etcs though)

I haven't experiment this by myself yet, so bit hard to give any answers unfortunately. Personally I'm seeing this is more about general questions of package / dependency management question instead of implementation of Rx itself. (or maybe bit arguable since Rx uses module augmentation).

I'd like to suggest open up thread for stackoverflow / or similar to look for generalized form of solutions, but leaving this opened for couple of day if anyone already experienced similar can tip here as well.

/cc @blesh , @jayphelps , @david-driscoll for asking out expertise.

Yes, I'm actually running into this everywhere now that I'm attempting to link together all my differently scoped modules together. Consider for each Module A with stream a$:

  • Module A uses a$.zip only
  • Module B takes a$ and wants to a$.flatMap it - importing it won't work
  • Module B also re-imports import {Observable} from 'rxjs/Observable' to create a new Observable b$ to export
  • Module C can't use either .zip or .flatMap on any incoming Observables, despite them now being present but having augmented the wrong versions

...and so on...the situation compounds the more modules pass through streams. In this way I need to 'chase back' through the modules to find the source of the stream every time I want to do something new to it, which means editing unrelated modules and adding a bloat to the original one which doesn't use and shouldn't care about that particular augmentation.

My only current solution is doing something like having a single Observable entry point and have to dependency inject everything back through to that source with dynamic require or something (ugh), which would mean always writing namespace.Observable, every independent module having to know about / be tied to namespace, and generally breaking the ability to just write import {Observable} from 'rxjs/Observable' as required which is much cleaner.

I think perhaps goes beyond a support question as it is not just a question of package management...it seems a reasonable use case for how operators could/should be dealt with in a whole app, so would welcome some input from the experts :) Thanks!

This fixes the issue:

import { withLatestFrom } from 'rxjs/operator/withLatestFrom';
// monkey patch incoming stream to augment its available operators
        Object.getPrototypeOf(Object.getPrototypeOf(activeFilters$)).withLatestFrom = withLatestFrom;
const dataWithSelectionsReapplied$ = filteredData$
            .withLatestFrom(activeFilters$)

...but it relies on me knowing the RxJS base proto of activeFilters$ is two levels deep, otherwise I need to do it separately for every incoming stream ref.

EDIT - whoops - this is just putting .withLatestFrom on Object. Not viable.

Assuming this is node on a server. This is basically how node_modules work. require will look for the nearest node_modules/rxjs folder that it can.

In the case of npm link and modules, they will all have different identities, and then in fact completely different Observables.

Are you using TypeScript, JavaScript, ES6 Syntax / Babel?

Is function bind (::) an option for you? You can use :: that is probably a more straightforward solution.

Now this leaves a larger problem, that still needs to be solved. We potentially need some way to "adopt" other observables. Such that we there is an easy way of consuming other instances of RxJS that may or may not have the underlying methods we're expecting.

Would that basically be something like the following?

new Observable().lift(unfriendlyObservable);

This would give you new observable with the locally sourced prototype.

Hi David, thanks for your input!

This is ES6/Babel on frontend. I don't think function bind is going to be an option but I'll look into it.

Interesting about .lift...but if have module A importing flatMap and module B importing startWith, then the lifted new Observable in module B gains startWith but then loses flatMap again :D

Perhaps I'm missing the real issue but it sounds like the issue is that in what ever build env you have, you are actually importing two _different_ versions of RxJS. One from inside external-module which is exporting default stream and another inside your own app when you import 'rxjs/add/operator/withLatestFrom'; So your app's version of Observable is getting the operator added, but since external-module imports a different version, it has it's own Observable that does not share a prototype with yours.

If this is the problem: you should dedup your dependencies. Most bundlers do this automatically if they rely on npm's dedupping, but if the semver of RxJS you depend on doesn't match the semver of RxJS that external-module that depends on, you _will_ have two copies because obviously you and external-module are saying you need incompatible versions of RxJS. This is the nature of semver and is very handy in many cases if you're aware it's happening..but this is not one of them.

If you have two incompatible RxJS versions, your bundler may have an option to override this behavior and only use a single, preferred version for both--but now you're breaking semver so you do so at your own risk. Ideally you would resolve the semver conflict between your project and external-module so that you both depend on RxJS within a matching semver range. But if you _really_ need to...in webpack you use resolve.alias built-in..in browserify, you can add similar behavior with aliasify.

Let me know if I'm confused and not talking about your issue.

@brokenalarms it will gain the functionality of the local observable, and lose the functionality of the external one. If you need that additional method locally, then you can add it in, and then flatMap should just work.

lift may actually be the wrong method, the more I look at it.

It would honestly be easier to just use the entire library by requiring require('rxjs') or switch to the function bind style syntax so you avoid having to patch anything.

@jayphelps and @david-driscoll 's comment reminds me, in most cases if modules are using same version instance being imported probably would be identical one. (yes, I made mistake in my comment)

So bring back to original question @brokenalarms, would you mind share details of how do you orchestrate module import / bundling between user code / module codes? That'll makes easier to narrow down issue here if it's patch operator or module issue.

@jayphelps Ha, that sent me on a somewhat frantic scramble to make sure I hadn't earned myself a facepalm, but no, all the modules are using beta 5.11 and deduping them all didn't make a difference.

@david-driscoll Absolutely would be easier - that's how I started - but with vendor sourcemaps (I need because we use a bunch of es6 external/vendor modules in house that get bundled with rx) caused a near 30-second 100% CPU dead zone following parsing of the vendor file on every reload with the whole library included. Selective importing of rxjs specifically fixed that immediately.

@kwonoj I am local linking npm modules and using browserify and I guess at some stage this duplicating Observable, since I have module import chains like A requires B and Observable, and B requires Observable. As a next step I'm trying to unlink everything and rebuild node_modules just to confirm it's not a package management issue.

I still _very much_ suspect you have two copies of Rx being loaded, even if they're the same version number. What you're trying to do works, if there is a single copy of Rx they both use.

I know I'm stubborn, but triple check for me:

import stream from 'external-module';
import { Observable } 'rxjs/Observable';

console.log(stream instanceof Observable);
// SHOULD BE TRUE!

Indeed...definitely duplicated :P That test fails. I had feared as much in my last mention to @kwonoj, but I can plainly see exports.observable = Observable and exports.observable = Observable1 in my vendor file (though I would still have just expected the latter to replace the former, must be referenced elsewhere).

No idea why and thank you @jayphelps for your initial suggestion uncovering the underlying issue. I have no idea why it's importing twice and I can't trace it back since this is just how browserify spits out the file...but in any case, not one for you guys then I guess. Thanks so much for your help though! 👍

Awesome! Happy to hear the issue is know. Going to go ahead and close

In the case of npm link and modules, they will all have different identities, and then in fact completely different Observables.

I ran into this just now with an npm linked version of a library. Is the best solution still ES7 function bind?

I ended up using the safe global singleton pattern (here,
works great with Symbols) to expose a single Rx instance. Then only a
single one of my npm modules (the base dataplane/framework) has Rx as an
npm dependency, and everything else imports it from there meaning I can
control the version from a single point and link away. Works great.

On Tue, Oct 18, 2016, 9:13 PM Kyle Kelley [email protected] wrote:

In the case of npm link and modules, they will all have different
identities, and then in fact completely different Observables.

I ran into this just now with an npm linked version of a library. Is the
best solution still ES7 function bind?


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/ReactiveX/rxjs/issues/1885#issuecomment-254706021,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AHC8CjjrpwYgr2dEBgA5RZBkzejKgdAIks5q1ZjtgaJpZM4Jmw2g
.

I noticed one way of doing this that lets me use the linked library as well - wrapping observables that aren't patched with the patched Observable using Observable.from(unpatchedObs). Works quite well.

@rgbkrk you intentionally have two different implementations of RxJS v5 Observable in your app? What's your use case?

That's still importing two versions of rxjs though, doubling the file size.

On Tue, Oct 18, 2016, 9:28 PM Kyle Kelley [email protected] wrote:

I noticed one way of doing this that lets me use the linked library as
well - wrapping observables that aren't patched with the patched Observable
using Observable.from(unpatchedObs). Works quite well.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/ReactiveX/rxjs/issues/1885#issuecomment-254707551,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AHC8Cuxqz5a2x6VrVOEyqKgJTokFEOjNks5q1ZyBgaJpZM4Jmw2g
.

you intentionally two different implementations of RxJS v5 Observable in your app?

No, it's all rxjs-5.0.0-rc1. The problem I'm running into is that when linking to a library I'm working on (npm link) with a separate project, the Observables are not patched as referenced by @david-driscoll's comment: "In the case of npm link and modules, they will all have different identities"

That's still importing two versions of rxjs though, doubling the file size.

Not to fret, this isn't for production. I want to be able to use a dummy React app in a different dir while linking to a library that exports a really minimal surface area with Rx.

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

benlesh picture benlesh  ·  3Comments

OliverJAsh picture OliverJAsh  ·  3Comments

jakovljevic-mladen picture jakovljevic-mladen  ·  3Comments

giovannicandido picture giovannicandido  ·  4Comments

cartant picture cartant  ·  3Comments