Cyclejs and trigger events with values - javascript

I m trying to learn cyclejs and reactive programming, and I can't get how to manage events with values.
For example,I need to create four functions that makes some maths operations such as :
addition
substraction
divison
multiplication
Here's the code that I have :
function main ({DOM}) {
const secondNumber$ = DOM
.select('.second-number')
.events('change')
.map(ev => ev.target.value)
.startWith(0)
const firstNumber$ = DOM
.select('.first-number')
.events('change')
.map(ev => ev.target.value)
.startWith(0)
const addClick$ = DOM
.select('.add')
.events('click')
.merge(firstNumber$)
.merge(secondNumber$)
.filter(value => console.log(value))
.scan((nb1, nb2) => nb1 + nb2)
return {
DOM: addClick$.map(result =>
div([
input('.first-number',{type:'text'}),
input('.second-number',{type:'text'}),
button('.add', 'Add'),
h2('Result is : ' + result)
])
)
};
}
It doesn't work at all and I can't figure out in my mind what I m doing wrong out there.
I m looking for a simple explanation how can I make this working ? I feel just like the merging streams of secondNumber$ and firstNumber$ are not correct and I can't find why..
Any idea ?
EDIT : I got that I shouldn't use the operator I was using, but use withLatestFrom.
The fact is that I m using xstream and so I have to map / flatten :
import {
div,
h1,
input,
button
} from '#cycle/dom';
/**
* Counter
* #param {Object} sources Contains the inputs
* #return {Object} The output sinks
*/
function counter(sources) {
const input1$ = sources.DOM
.select('.input1')
.events('input')
.map(ev => ev.target.value)
.startWith(0);
const input2$ = sources.DOM
.select('.input2')
.events('input')
.map(ev => ev.target.value)
.startWith(0);
const add$ = sources.DOM
.select('.add')
.events('click');
const resultAddition$ = add$
.map(ev => input1$
.map(value => input2$
.map(value2 => Number(value) + Number(value2)))
.flatten())
.flatten()
.startWith(0);
return {
DOM: resultAddition$.map(item => {
console.log(item); // triggered each time an input is modified
return div([
h1(`Super new value : ${item}`),
input('.input1', {
attrs: {
type: 'text'
}
}),
input('.input2', {
attrs: {
type: 'text'
}
}),
button('.add', 'Ajouter')
]);
})
};
}
export default counter;
From now, I have got in mind what the code should do, mapping on each click the operation and flatten the two input$ to get my result only when clicking the button
The fact is that the result value is changing on input and not and click. And more important, it changes on input only after the first click on the add button that is not what I want to.
What am I doing wrong this time ?
Thanks for your replies

It seems like you want combineLatest, not merge.
Both combineLatest and merge are "combination operators". They bring multiple Observables together and output one Observable. However, combineLatest is for "AND" combinations, while merge is for "OR" combinations.
You probably need "AND", because you want the value from first-number AND the value from second-number. That said, you want those values only when an add click happens. In that case, there is a variant of combineLatest called withLatestFrom. It allows you to sample the values from first-number AND second-number, but only when the add click happens.
const addClick$ = DOM
.select('.add')
.events('click')
const added$ = addClick$
.withLatestFrom(firstNumber$, secondNumber$,
(click, first, second) => first + second
)
As a side note, you should never do something like .filter(value => console.log(value)). The function for filter is a predicate. It's supposed to be a "condition" function that returns a boolean. If you want to debug, use .do(value => console.log(value)).
PS: I'm assuming you were using RxJS v4.

Related

ngx dropdown list get selected values

I am trying to use ngx dropdown list like this:
<ngx-dropdown-list [items]="categoryItems" id="categoriesofdata" [multiSelection]="true"
[placeHolder]="'Select categories'"></ngx-dropdown-list>
And I am getting all selected values like:
get selectedCategories() {
const items = this.categoryItems.filter((item: any) => item.selected);
return items.length ? JSON.stringify(items.map(item => ({
value: item.value
}))) : '';
}
and output looks like:
[{"value":"Surname"},{"value":"Address"}]
I want to get only for example Surname instead of value and Surname.
[0].value
How Can I do this?
Should I use for loop or is better option?
I think you're almost there, in fact you're doing a little too much. Your map function should just return the value you are interested in rather than creating a new structure.
get selectedCategories() {
const items = this.categoryItems.filter((item: any) => item.selected);
return items.length ? JSON.stringify(items.map(item => item.value)) : '';
}
Edit:
And as a personal preference, I would refactor to something like this:
get selectedCategories() {
if (!this.categoryItems.length) {
return '';
}
const surnames = this.categoryItems
.filter(item => item.selected)
.map(item => item.value);
return JSON.stringify(surnames);
}
I prefer to get out of a function early in the case where no further processing is required. And I would return the result of chained filter and map functions into a new surnames variable. The named variable signals the intention of the code, and keeps the array logic together.
This is just my preference though. Your code was almost there functionally.

How can I map a concatenated value for this state, without it being passed as a string?

I'm new to Javascript.
I'm building this drumpad and I want to be able to switch between different soundpacks.
The sounds are imported from a separate file and the state is written like this:
import * as Sample from '../audiofiles/soundfiles'
const drumpadData = [
{
// other stuff
soundfile: Sample.sound1a
},
If I want to load a different soundpack, I have to change the state so the last letter (a,b,c) gets changed, so instead of Sample.sound1a, it would have to be Sample.sound1b. this is the function i wrote (on App.js):
changeSamples(id) {
let choice = document.getElementById("select-samplepack").value
this.setState(prevState => {
const updatedData = prevState.data.map(item => {
let newSoundfile = "Sample.sound" + item.id + choice
item.soundfile = newSoundfile
return item
})
return {
data: updatedData
}
})
}
It works, as in the value gets changed, but instead of react interpreting that and finding the correct import, the value of soundfile just stays as a string like "Sample.soundb1", so I get a load of media resource errors.
https://aquiab.github.io/drumpad/ heres the website, you can check the console to see the error, you have to load a different soundpack to reproduce the error.
https://github.com/aquiab/drumpad and here are the files:
I've thought of some ways of cheesing it, but I want the code to stay as clean as I can make it be.
Well that's because it is in fact a string. When you do:
"Sample.Sound" + item.id + choice you are doing type coersion. In JavaScript, that means you are converting the value of all data-types so that they share a common one. In this case your output resolves into a string. This will not be effective in finding the right sound in your dictionary.
Instead, what you need is bracket notation: Object[property]
Within the brackets we can define logic to identify the designated key belonging to the Object.
For example: Sample["sound" + item.id + choice] would evaluate to Sample["sound1b"] which is the same as Sample.sound1b
changeSamples(id) {
let choice = document.getElementById("select-samplepack").value
this.setState(prevState => {
const updatedData = prevState.data.map(item => {
item.soundfile = Sample["sound" + item.id + choice]
return item
})
return {
data: updatedData
}
})
}
I can think of two approaches here.
import Sample in App.jsx. and update sound file.
import * as Sample from '../audiofiles/soundfiles'
changeSamples(id) {
let choice = document.getElementById("select-samplepack").value
this.setState(prevState => {
const updatedData = prevState.data.map(item => {
let newSoundfile = Sample[`sound${item.id}${choice}`]
item.soundfile = newSoundfile
return item
})
return {
data: updatedData
}
})
}
You should save mapping of files in other object and update mapping and use mapping in drumpadData soundfile key.
Your problem comes from this line :
let newSoundfile = "Sample.sound" + item.id + choice
Here you are concatening your values item.id and choice with a string, so the result is a string and Sample is not interpreted as your imported object.
What you need is to wright something like
const sampleItem = "sound" + item.id + choice
let newSoundfile = Sample[sampleItem]
When you access an object property with the notation myObject[something], what's inside the bracket get interpreted. So in my example sample (which is a string because I concatenated a string "sound" with the variables) will be replaced with its string value (ex: "sound1a"), and newSoundFile will have as value the result of Sample["sound1a"].
I hope it make sens.

Updating components based on sorted array. Angular (4.3)

Working with an array of data that we want to be able to sort for display in a component, and it doesn't seem to be sorting or updating the DOM, however I have a working code sample that properly demonstrates the concept, and it should be sorting, but in the angular app, it's simply not getting sorted.
The parent component that houses the original data stores the data on an Input parameter object called Batch, and the array we're sorting is on Batch.Invoices.Results. The event from the child component is fine, and the appropriate data is confirmed to bubble to the parent component.
The function that's supposed to sort the array looks like this:
public OnInvoiceSortChange({orderValue, orderAscending}){
console.log(`Invoice Sorting has been called. Value: ${orderValue} . Ascending? ${orderAscending}`);
console.log(`Before:`);
console.log(this.BatchViewModel.Invoices.Results.map(x => x.VendorName));
const sortingArray = [...this.BatchViewModel.Invoices.Results];
if(orderAscending){
const sorted = sortingArray.sort((a, b) => a[orderValue] > b[orderValue] ? 1 : 0);
this.BatchViewModel.Invoices.Results = sorted;
console.log('Sorted');
console.log(sorted.map(x => x.VendorName));
} else {
const sorted = sortingArray.sort((a, b) => a[orderValue] < b[orderValue] ? 1 : 0);
this.BatchViewModel.Invoices.Results = sorted;
console.log(sorted.map(x => x.VendorName));
}
console.log(`After:`);
console.log(this.BatchViewModel.Invoices.Results.map(x => x.VendorName));
}
All the console logs are for debugger visibility, and the output is this:
Where in my testing file (non-angular) looks like this:(where data is a direct copy of the array from the Angular app.
const ascendingData = [...data];
const descendingData = [...data];
const sortedDescending = descendingData.sort((a, b) => a['VendorName'] < b['VendorName']? 0 : 1)
const sortedAscending = ascendingData.sort((a, b) => a['VendorName'] > b['VendorName']? 0 : 1);
const vendorListAscending = sortedAscending.map(x => x.VendorName);
const vendorListDescending = sortedDescending.map(x => x.VendorName);
console.log(vendorListDescending);
console.log(vendorListAscending);
and the output looks like this:
So I see that the sorting should work, but it's just not happening in Angular.
How can I get the array sorted, and update the DOM as well?
The function you pass to sort is wrong. It is supposed to return a negative value for "less", a positive value for "greater" or zero for "equal". If orderValue is numeric then it's easiest to just return a[orderValue] - b[orderValue], if not then just change your 0 to -1.
(By the way, name orderKey could be a bit clearer maybe?)
I don't think angular has anything to do here, but I cannot tell now why you get different results. Anyway, your sort function is invalid (it states that a equals b, but at the same time b is greater than a), I hope fixing this function helps.

RxJS Filter / Search Subject, Observable, or BehaviorSubject

I just started learning RxJS. One thing I have tried to do without much luck is, figuring out how to search/filter a Subject, or create an observed array that I can search on.
I've tried piping and filtering a Subject and BehaviorSubject, but the values in the predicate are RxJS specific.
From what I've read on various posts, the way is to observe an array to use a Subject.
I can easily have two variables, one array and the Subject, and search the array. But I'd like to accomplish this one variable.
In Knockout its possible to search an observed array.
Is this possible in RxJS?
Thanks in advance.
Example:
layers: Rx.Subject<any> = new Rx.Subject<any>();
toggleLayer (layerId, visible) {
//find layer we need to toggle
// How do I search the subject to get the values added in next()?
// tried from(this.layers), pipe does not fire
const source = of(this.layers);
const example = source.pipe(filter((val, index) => {
//val is subject, and only iterates once, even if more than one value in subject
// tslint:disable-next-line:no-debugger
debugger;
return false;
}));
const sub = example.subscribe((val) => {
// tslint:disable-next-line:no-debugger
debugger;
});
}
private addLayer = (layerName, layerObj, layerType) => {
// component class is subscribed to layers subject. Update UI when layer is added
this.layers.next({
layerId: this.layerId,
name: `${layerName}_${this.layerId}`,
layerObj: layerObj,
visible: true,
layerType: layerType
});
}
I'm not 100% clear on the specifics of your ask, but maybe this example will help you.
const filterSubject = new BehaviorSubject<string>('b');
const dataSubject = new BehaviorSubject<string[]>(['foo', 'bar', 'baz', 'bat']);
const dataObservable = combineLatest(filterSubject, dataSubject).pipe(
// given an array of values in the order the observables were presented
map(([filterVal, data]) => data.filter(d => d.indexOf(filterVal) >= 0))
);
dataObservable.subscribe(arr => console.log(arr.join(',')));
// bar, baz, bat
Using combineLatest, you can have the value in dataObservable updated whenever either your filter value or your data array changes.

How to manage state without using Subject or imperative manipulation in a simple RxJS example?

I have been experimenting with RxJS for two weeks now, and although I love it in principle I just cannot seem to find and implement the correct pattern for managing state. All articles and questions appear to agree:
Subject should be avoided where possible in favor of just pushing state through via transformations;
.getValue() should be deprecated entirely; and
.do should perhaps be avoided except for DOM manipulation?
The problem with all such suggestions is that none of the literature appears to directly say what you should be using instead, besides "you'll learn the Rx way and stop using Subject".
But I cannot find a direct example anywhere that specifically indicates the correct way to perform both additions and removals to a single stream/object, as the consequence of multiple other stream inputs, in a stateless and functional manner.
Before I get pointed in the same directions again, problems with uncovered literature are:
The Introduction to Reactive Programming You've been missing: great starting text, but does not specifically address these questions.
The TODO example for RxJS comes with React and involves explicit manipulation of Subjects as proxies for React Stores.
http://blog.edanschwartz.com/2015/09/18/dead-simple-rxjs-todo-list/ : explicitly uses a state object for addition and removal of items.
My perhaps 10th rewrite of the standard TODO follows - My prior iterations covered include:
starting with a mutable 'items' array - bad as state is explicit and imperatively managed
using scan to concatenate new items to an addedItems$ stream, then branching another stream where the removed items were deleted - bad as the addedItems$ stream would grow indefinitely.
discovering BehaviorSubjectand using that - seemed bad since for each new updatedList$.next() emission, it requires the previous value to iterate, meaning that Subject.getValue() is essential.
trying to stream the result of the inputEnter$ addition events into filtered removal events - but then every new stream creates a new list, and then feeding that into the toggleItem$ and toggleAll$ streams means that each new stream is dependent on the previous, and so causing one of the 4 actions (add, remove, toggle item or toggle all) requires the whole chain to be unnecessarily run through again.
Now I have come full circle, where I am back to using both Subject (and just how is it supposed to be successively iterated upon in any way without using getValue()?) and do, as show below. Myself and my colleague agree this is the clearest way, yet it of course seems the least reactive and most imperative. Any clear suggestions on the correct way for this would be much appreciated!
import Rx from 'rxjs/Rx';
import h from 'virtual-dom/h';
import diff from 'virtual-dom/diff';
import patch from 'virtual-dom/patch';
const todoListContainer = document.querySelector('#todo-items-container');
const newTodoInput = document.querySelector('#new-todo');
const todoMain = document.querySelector('#main');
const todoFooter = document.querySelector('#footer');
const inputToggleAll = document.querySelector('#toggle-all');
const ENTER_KEY = 13;
// INTENTS
const inputEnter$ = Rx.Observable.fromEvent(newTodoInput, 'keyup')
.filter(event => event.keyCode === ENTER_KEY)
.map(event => event.target.value)
.filter(value => value.trim().length)
.map(value => {
return { label: value, completed: false };
});
const inputItemClick$ = Rx.Observable.fromEvent(todoListContainer, 'click');
const inputToggleAll$ = Rx.Observable.fromEvent(inputToggleAll, 'click')
.map(event => event.target.checked);
const inputToggleItem$ = inputItemClick$
.filter(event => event.target.classList.contains('toggle'))
.map((event) => {
return {
label: event.target.nextElementSibling.innerText.trim(),
completed: event.target.checked,
};
})
const inputDoubleClick$ = Rx.Observable.fromEvent(todoListContainer, 'dblclick')
.filter(event => event.target.tagName === 'LABEL')
.do((event) => {
event.target.parentElement.classList.toggle('editing');
})
.map(event => event.target.innerText.trim());
const inputClickDelete$ = inputItemClick$
.filter(event => event.target.classList.contains('destroy'))
.map((event) => {
return { label: event.target.previousElementSibling.innerText.trim(), completed: false };
});
const list$ = new Rx.BehaviorSubject([]);
// MODEL / OPERATIONS
const addItem$ = inputEnter$
.do((item) => {
inputToggleAll.checked = false;
list$.next(list$.getValue().concat(item));
});
const removeItem$ = inputClickDelete$
.do((removeItem) => {
list$.next(list$.getValue().filter(item => item.label !== removeItem.label));
});
const toggleAll$ = inputToggleAll$
.do((allComplete) => {
list$.next(toggleAllComplete(list$.getValue(), allComplete));
});
function toggleAllComplete(arr, allComplete) {
inputToggleAll.checked = allComplete;
return arr.map((item) =>
({ label: item.label, completed: allComplete }));
}
const toggleItem$ = inputToggleItem$
.do((toggleItem) => {
let allComplete = toggleItem.completed;
let noneComplete = !toggleItem.completed;
const list = list$.getValue().map(item => {
if (item.label === toggleItem.label) {
item.completed = toggleItem.completed;
}
if (allComplete && !item.completed) {
allComplete = false;
}
if (noneComplete && item.completed) {
noneComplete = false;
}
return item;
});
if (allComplete) {
list$.next(toggleAllComplete(list, true));
return;
}
if (noneComplete) {
list$.next(toggleAllComplete(list, false));
return;
}
list$.next(list);
});
// subscribe to all the events that cause the proxy list$ subject array to be updated
Rx.Observable.merge(addItem$, removeItem$, toggleAll$, toggleItem$).subscribe();
list$.subscribe((list) => {
// DOM side-effects based on list size
todoFooter.style.visibility = todoMain.style.visibility =
(list.length) ? 'visible' : 'hidden';
newTodoInput.value = '';
});
// RENDERING
const tree$ = list$
.map(newList => renderList(newList));
const patches$ = tree$
.bufferCount(2, 1)
.map(([oldTree, newTree]) => diff(oldTree, newTree));
const todoList$ = patches$.startWith(document.querySelector('#todo-list'))
.scan((rootNode, patches) => patch(rootNode, patches));
todoList$.subscribe();
function renderList(arr, allComplete) {
return h('ul#todo-list', arr.map(val =>
h('li', {
className: (val.completed) ? 'completed' : null,
}, [h('input', {
className: 'toggle',
type: 'checkbox',
checked: val.completed,
}), h('label', val.label),
h('button', { className: 'destroy' }),
])));
}
Edit
In relation to #user3743222 very helpful answer, I can see how representing state as an additional input can make a function pure and thus scan is the best way to represent a collection evolving over time, with a snapshot of its previous state up to that point as an additional function parameter.
However, this was already how I approached my second attempt, with addedItems$ being a scanned stream of inputs:
// this list will now grow infinitely, because nothing is ever removed from it at the same time as concatenation?
const listWithItemsAdded$ = inputEnter$
.startWith([])
.scan((list, addItem) => list.concat(addItem));
const listWithItemsAddedAndRemoved$ = inputClickDelete$.withLatestFrom(listWithItemsAdded$)
.scan((list, removeItem) => list.filter(item => item !== removeItem));
// Now I have to always work from the previous list, to get the incorporated amendments...
const listWithItemsAddedAndRemovedAndToggled$ = inputToggleItem$.withLatestFrom(listWithItemsAddedAndRemoved$)
.map((item, list) => {
if (item.checked === true) {
//etc
}
})
// ... and have the event triggering a bunch of previous inputs it may have nothing to do with.
// and so if I have 400 inputs it appears at this stage to still run all the previous functions every time -any- input
// changes, even if I just want to change one small part of state
const n$ = nminus1$.scan...
The obvious solution would be to just have items = [], and manipulate it directly, or const items = new BehaviorSubject([]) - but then the only way to iterate on it appears to be using getValue to expose the previous state, which Andre Stalz (CycleJS) has commented on in the RxJS issues as something that shouldn't really be exposed (but again, if not, then how is it usable?).
I guess I just had an idea that with streams, you weren't supposed to use Subjects or represent anything via a state 'meatball', and in the first answer I'm not sure how this doesn't introduce mass chained streams which are orphaned/grow infinitely/have to build on each other in exact sequence.
I think you already found a good example with : http://jsbin.com/redeko/edit?js,output.
You take issue with the fact that this implementation
explicitly uses a state object for addition and removal of items.
However, thas is exactly the good practice you are looking for. If you rename that state object viewModel for example, it might be more apparent to you.
So what is state?
There will be other definitions but I like to think of state as follows:
given f an impure function, i.e. output = f(input), such that you can have different outputs for the same input, the state associated to that function (when it exists) is the extra variable such that f(input) = output = g(input, state) holds and g is a pure function.
So if the function here is to match an object representing a user input, to an array of todo, and if I click add on a todo list with already have 2 todos, the output will be 3 todos. If I do the same (same input) on a todo list with only one todo, the output will be 2 todos. So same input, different outputs.
The state here that allows to transform that function into a pure function is the current value of the todo array. So my input becomes an add click, AND the current todo array, passed through a function g which give a new todo array with a new todo list. That function g is pure. So f is implemented in a stateless way by making its previously hidden state explicit in g.
And that fits well with functional programming which revolves around composing pure functions.
Rxjs operators
scan
So when it comes to state management, with RxJS or else, a good practice is to make state explicit to manipulate it.
If you turn the output = g(input, state) into a stream, you get On+1 = g(In+1, Sn) and that's exactly what the scan operator does.
expand
Another operator which generalizes scan is expand, but so far I had very little use of that operator. scan generally does the trick.
Sorry for the long and mathy answer. It took me a while to get around those concepts and that's the way I made them understandable for me. Hopefully it works for you too.

Categories

Resources