How to share a single Subject source - rxjs

I'm trying to share a Subject source across multiple functions that filter their actions and do appropriate tasks, the ones that are not filtered should fall trough without modifications.
I've tried merging same source but it doesn't really work the way I need it to...
const source = new Subject()
source.next({ type: 'some type', action: {} })
merge(
source,
source.pipe(filter(...), do something),
source.pipe(filter(...), do something),
source.pipe(filter(...), do something),
source.pipe(filter(...), do something),
).subscribe(...)
In this case I get original source + filtered ones.
I'm expecting to be able provide same source to multiple functions that can filter on types and do async behaviours, rest of the types that were not filtered should fall trough. Hope this is clear enough, or otherwise will try to make a better example. Thanks!
example here

Basically you want one source with actions. Subject is fine way to do this.
Then you want to do some processing on each type of action. You can filter and subscribe to each substream.
const add$ = source.pipe(filter(a => a.type === "add")).subscribe(function onAddAction(a) {});
const remove$ = source.pipe(filter(a => a.type === "remove")).subscribe(function onRemove(a) {});
Or you can prepare substreams and then merge to all processed actions again.
const add$ = source.pipe(filter(a => a.type === "add"), tap(onAdd));
const remove$ = source.pipe(filter(a => a.type === "remove"), tap(onRemove));
const processedAction$ = merge(add$, remove$);
processedAction$.subscribe(logAction);
If you need to do some preprocessing on all actions you can use share or shareReplay. toAction will be called only once per each item.
const subject = new Subject();
const action$ = subject.pipe(map(toAction), share());
const add$ = action$.pipe(filter(isAdd));
...
merge(add$, remove$).subscribe(logAction);
And if you have problems splitting:
function not(predicate) {
return function(item, ...args) {
return !predicate(item, ...args);
}
}
function any(...predicates) {
return function(item, ...args) {
return predicates.some(p => p(item, ...args));
}
}
const a = source.pipe(filter(fa), map(doA));
const b = source.pipe(filter(fb), map(doB));
const c = source.pipe(filter(fc), map(doC));
const rest = source.pipe(filter(not(any(fa, fb, fc)));
merge(a, b, c, rest).subscribe(logAction);

Related

rxjs why does behaviour subject when piped triggered with next()

I have read the tutorial from this link https://magarcia.io/2019/02/18/bloc-pattern-with-react-hooks
and i just dont understand how the search query to the API is triggered when _query.next is called with new search terms
see below code.
export class SearchBloc {
private _results$: Observable<string[]>;
private _query$ = new BehaviorSubject<string>('');
constructor(private api: API) {
**this._results$ = this._query$.pipe(
switchMap((query) => {
return observableFrom(this.api.search(query));
})
);**
get results$(): Observable<string[]> {
return this._results$;
}
}
const SearchInput = () => {
const searchBloc = useContext(SearchContext);
const [query, setQuery] = useState('');
useEffect(() => {
searchBloc.query.next(query);
}, [searchBloc, query]);
return (
<input
type="text"
name="Search"
value={query}
onChange={({ target }) => setQuery(target.value)}
/>
);
};
Assuming that searchblock was put in the context, and during input change the query which is a behaviour subject is assigned a new value with next();
how or why does the api query executes?
I guess I did not understand the line with
this._results$ = this._query$.pipe(
switchMap((query) => {
so maybe the question is, how did the pipe worked? did it create a method callback that will execute when next is called? and what is the assignment to result mean?
anyone that can help me make sense of it is greatly appreaciated.
Consider the following code:
It creates a stream of 5 numbers. Then it creates a second stream which is defined as a stream that has all the same numbers as the first one, only each number is incremented.
const numberStream$ = of(1,2,3,4,5);
const numbersPlus1$ = numberStream$.pipe(
map(v => v + 1)
);
numbersPlus1$.subscribe(console.log);
If you subscribe to numberStream$ you should expect to get 1,2,3,4,5.
If you subscribe to numbersPlus1$ you should expect to get 2,3,4,5,6.
Here we do the same thing with a Subject. Of course, unlike of(1,2,3,4,5), a subject lets you create a stream imperatively. Whenever I call .next on a subject, I'm saying "Make this value the next emission in this subject's stream."
const numberSubject$ = new Subject<number>();
const numbersPlus1$ = numberSubject$.pipe(
map(v => v + 1)
);
numbersPlus1$.subscribe(console.log);
numberSubject$.next(1);
numberSubject$.next(2);
numberSubject$.next(3);
numberSubject$.next(4);
numberSubject$.next(5);

Redux-toolkit deeply nested flatmapped data not updating state

I have deeply nested data, and need to update some deeply nested child. Currently I do it by flatmapping the two upper level lists, then searching in all the possible tasks, and then mutating the task by calling the init function.
const tasks = state.data.flatMap((p) => p.hierarchyLines).flatMap((h) => h?.tasks);
const task = tasks.find((t) => t?.id === payload.id);
task?.init(payload);
task.init(data: any):
this.id = _data["id"];
this.start = _data["start"] ? new Date(_data["start"].toString()) : <any>undefined;
this.deadline = _data["deadline"] ? new Date(_data["deadline"].toString()) : <any>undefined;
...
This does not work, any advice on why it is not updating the state?
If anyone should ever run into a similiar issue. The problem was that immer does not handle classes well.
https://immerjs.github.io/immer/complex-objects
For me the solution was copying the state first, then mutating it, and then returning the copy as described here: https://redux-toolkit.js.org/usage/immer-reducers#immer-usage-patterns
builder.addCase(fetch.fulfilled, (state, { payload }) => {
const stateCopy = _.cloneDeep(state);
stateCopy.loading = false;
const tasks = stateCopy.data.flatMap((p) => p.hierarchyLines).flatMap((h) => h?.tasks);
const task = tasks.find((t) => t?.id === payload.id);
task?.init(payload);
return stateCopy;
});

What is the best way to patch fetched objects in RxJS?

I have a code that fetches book by its id
const fetchBook = (bookId: number) => {
const title = 'Book' + bookId;
// mimic http request
return timer(200).pipe(mapTo({ bookId, title }));
}
const bookId$ = new Subject<number>();
const book$ = bookId$.pipe(
switchMap(bookId => fetchBook(bookId)),
shareReplay(1)
);
book$.subscribe(book => console.log('book title: ', book.title))
bookId$.next(1);
I have an API method that patches values and returns the updated object:
const patchBook = (bookId: number, newTitle: string) => {
return timer(200).pipe(mapTo({ bookId, title: newTitle }));
}
What should I do to get book$ to emit the new value after I call patchBook(1, 'New Book Title')?
I can declare book$ as Subject explicitly and update it manually. But it will be imperative, not reactive approach.
Upd: The patch is called as a result of user action at any time (or never)
Upd2: Actually book$ can be also changed on server side and my real code looks like this:
const book$ = combineLatest([bookId$, currentBookChangedServerSignal$]).pipe...
The same thing you did to transform a bookId into a Book, you can use to transform a Book into a patchBook.
const book$ = bookId$.pipe(
switchMap(bookId => fetchBook(bookId)),
mergeMap(({bookId, title}) => patchBook(bookId, title)),
shareReplay(1)
);
Update:
patch is not always called
There are many ways this could be done and the "best" way really depends on how you've architected your system.
Lets say you dynamically create a button that the user clicks and this triggers an update event.
const patchBtn = document.createElement("button");
const patchBook$ = fromEvent(patchBtn, 'click').pipe(
switchMap(_ => patchBook(bookId, title))
);
const basicBook$ = bookId$.pipe(
switchMap(bookId => fetchBook(bookId))
);
const book$ = merge(patchBook$, basicBook$).pipe(
shareReplay(1)
);
You probably want your fromEvent events to emit some data rather then hard-coding (bookId, title) into the stream from a click, but you get the idea. That's just one of many ways to get the job done.
And of course, it should almost always be possible (and desirable) to remove bookId$, and replace it with a more reactive-style mechanism that hooks declarativly into whatever/wherever the ID's come from in the first place.
You can declare a fetchBook$ observable, and a patchBook$ subject. Then your book$ observable can be a merge of the two.
const patchBook = (bookId: number, newTitle: string) => {
return timer(200).pipe(
mapTo({ bookId, title: newTitle }),
tap(newBook=>this.patchBook$.next(newBook))
);
}
const bookId$ = new Subject<number>();
const fetchBook$ = bookId$.pipe(
switchMap(bookId => fetchBook(bookId)),
shareReplay(1)
);
const patchBook$ = Subject<{ bookId: number, newTitle: string}>();
const book$ = merge(fetchBook$, patchBook$);
book$.subscribe(book => console.log('book title: ', book.title))
bookId$.next(1);
patchBook(2, 'Moby Dick');

How to get multiple properties from objects in JXA?

Is there a way in JXA to get multiple properties from multiple objects with a single call?
For example, I want to get name and enabled property from menu items which can be done for each individual property as follows:
Application("System Events").processes.byName('Finder').menuBars[0].menuBarItems.name()
Application("System Events").processes.byName('Finder').menuBars[0].menuBarItems.enabled()
but is it possible to get them with a single function call? Something like:
Application("System Events").processes.byName('Finder').menuBars[0].menuBarItems.select('name', 'enabled')
I know, that I can iterate through the menuBarItems and collect properties from .properties() method, but this approach is too slow, that's why I'm looking for other options.
UPDATE
I'm looking for better performance, not for nicer syntax, i.e. I want properties to be retrieved in a single call to System Events.
I'd probably do it like this:
sys = Application('com.apple.systemevents');
FinderProc = sys.processes['Finder'];
FinderMenuBarItems = FinderProc.menuBars[0].menuBarItems();
Array.from(FinderMenuBarItems,x=>[x.name(),x.enabled()]);
By first converting the object to an array, this allows one to map each element and retrieve the desired properties for all in one go. The code is split over several lines for ease of reading.
EDIT: added on 2019-07-27
Following on from your comment regarding Objective-C implementation, I had a bit of time today to write a JSObjc script. It does the same thing as the vanilla JXA version above, and, yes, it clearly makes multiple function calls, which is necessary. But it's performing these functions at a lower level than System Events (which isn't involved at all here), so hopefully you'll find it more performant.
ObjC.import('ApplicationServices');
ObjC.import('CoreFoundation');
ObjC.import('Foundation');
ObjC.import('AppKit');
var err = {
'-25211':'APIDisabled',
'-25206':'ActionUnsupported',
'-25205':'AttributeUnsupported',
'-25204':'CannotComplete',
'-25200':'Failure',
'-25201':'IllegalArgument',
'-25202':'InvalidUIElement',
'-25203':'InvalidUIElementObserver',
'-25212':'NoValue',
'-25214':'NotEnoughPrecision',
'-25208':'NotImplemented',
'-25209':'NotificationAlreadyRegistered',
'-25210':'NotificationNotRegistered',
'-25207':'NotificationUnsupported',
'-25213':'ParameterizedAttributeUnsupported',
'0':'Success'
};
var unwrap = ObjC.deepUnwrap.bind(ObjC);
var bind = ObjC.bindFunction.bind(ObjC);
bind('CFMakeCollectable', [ 'id', [ 'void *' ] ]);
Ref.prototype.nsObject = function() {
return unwrap($.CFMakeCollectable(this[0]));
}
function getAttrValue(AXUIElement, AXAttrName) {
var e;
var _AXAttrValue = Ref();
e = $.AXUIElementCopyAttributeValue(AXUIElement,
AXAttrName,
_AXAttrValue);
if (err[e]!='Success') return err[e];
return _AXAttrValue.nsObject();
}
function getAttrValues(AXUIElement, AXAttrNames){
var e;
var _AXAttrValues = Ref();
e = $.AXUIElementCopyMultipleAttributeValues(AXUIElement,
AXAttrNames,
0,
_AXAttrValues);
if (err[e]!='Success') return err[e];
return _AXAttrValues.nsObject();
}
function getAttrNames(AXUIElement) {
var e;
var _AXAttrNames = Ref();
e = $.AXUIElementCopyAttributeNames(AXUIElement, _AXAttrNames);
if (err[e]!='Success') return err[e];
return _AXAttrNames.nsObject();
}
(() => {
const pid_1 = $.NSWorkspace.sharedWorkspace
.frontmostApplication
.processIdentifier;
const appElement = $.AXUIElementCreateApplication(pid_1);
const menuBar = getAttrValue(appElement,"AXMenuBar");
const menuBarItems = getAttrValue(menuBar, "AXChildren");
return menuBarItems.map(x => {
return getAttrValues(x, ["AXTitle", "AXEnabled"]);
});
})();

How to combine two list of object in one list with Rxjs

I have 2 differents list of objects and i want to combine them together for exemple:
listObj1 = [{name:'bob 1'}, {name:'bob 2'}]
listObj2 = [{pseudo:'Bob Razowski'}, {pseudo:'sponge bob'}]
result = [
{name:'bob 1', pseudo:'Bob Razowski}
{name:'bob 2', pseudo:'sponge bob'}
]
Can i do that with rxjs and how or if you have a better solution let me know
const characters = [];
const name$ = Observable.from(this.nameList)
.map(item => {
return {'name': item};
})
const pseudo$ = Observable.from(this.pseudoList)
.map(item => {
return {'pseudo': item};
})
Observable.zip(name$, pseudo$).subscribe(result => {
let char= {};
if(result.length > 1) {
char['name'] = result[0];
char['pseudo'] = result[1];
characters.push(char)
}
});
I started something like that but when i see the result, i can do it without rxjs. My question is more if it exists an other operator to do that.
thank
Well, you can do it with RxJS, but there is no obvious reason to do so, looking at your code snippet. One reason to do it reactively would be that you had really long lists and wanted to let the combination happen on (hypothetical) multiple threads. But in JavaScript that's not really practical, so for...of or Array.map are the right choices for this kind of task.
Anyhow, the RxJS solution would look like this:
zip(
from(listOb1),
from(listObj2)
).pipe(
map(([one, two]) => Object.assign({}, one, two)),
toArray()
)
Convert both lists into Observable streams with from, then zip them together and map each pair onto a new object using Object.assign. Collect the objects with toArray and done.

Resources