I'm still learning rxjs, and I'm a little stuck on how to write a custom compare function for the operator distinctUntilChanged.
I looked into using distinctUntilKeyChanged, which works great for a single key...But I have two keys that I need to compare.
It seems I might need to incorprate the scan operator to compare the current value emitted to the last value emitted....?
Ok, here is my code. I'm streaming map center changes from google maps. I don't need the map center GeoLocation to be very precise, so I'm rounding off most of the decimals returned by google maps.
searchStream$
.map((value)=>{
return {
lat: round(value[1].lat, 1),
lng: round(value[1].lng, 1)
}
}).distinctUntilKeyChanged('lat')
.do((position)=>{console.log(position)})
.subscribe((position)=>{ this._store.dispatch(new QueryUpdateGeoPositionAPIAction({latitude: position.lat, longitude: position.lng})) });
So back to my question, how can I compare both properties(lat & lng) to ensure that it's only emitting values when either one of the values change?
Thanks for the help!
I was having the same problem and this situation isn't covered by the docs.
this will add the .distinctUntilKeysChanged operator. just pass it any keys to "watch" for
const {
Observable,
Subject
} = Rx;
Observable.prototype.distinctUntilKeysChanged = function(...keys) {
return this.distinctUntilChanged((old, current) =>
// if no value changed,
// the array will only have true values,
// includes(false) will be false,
// convert to oposite (!),
// returns true;
// => true = do nothing
// if any value changed,
// the array will include some false,
// includes(false) will be true,
// convert to oposite (!),
// returns false;
// => false = emit
!keys
.map(key => old[key] === current[key]) // converts to array of boolean
.includes(false) // if any value changed
);
};
const stream = new Subject();
stream
.distinctUntilKeysChanged('prop', 'prop2')
.subscribe(obj => console.log(obj));
// should log
stream.next({
prop: 42,
prop2: 54,
});
// should log
stream.next({
prop: 12346,
prop2: 54,
});
// shouldn't log because neither prop nor prop2 changed
stream.next({
prop: 12346,
prop2: 54,
});
// should log
stream.next({
prop: 12346,
prop2: 5454665654645,
});
<script src="https://unpkg.com/#reactivex/rxjs#5.0.3/dist/global/Rx.js"></script>
the only downside to this is that you can't specify a custom comparison function. if you do want to specify one you could use this instead (which is more similar to the implementation of .distinctUntilKeyChanged where the first argument is the key and the second is the comparison function). Notice that this one takes an array of keys where the first one took the keys as separate arguments
const {
Observable,
Subject
} = Rx;
Observable.prototype.distinctUntilKeysChanged = function(keys, compare) {
return this.distinctUntilChanged((old, current) =>
// if no value changed,
// the array will only have true values,
// includes(false) will be false,
// convert to oposite (!),
// returns true;
// => true = do nothing
// if any value changed,
// the array will include some false,
// includes(false) will be true,
// convert to oposite (!),
// returns false;
// => false = emit
!keys
.map(key => compare ? compare(old[key], current[key]) : old[key] === current[key]) // converts to array of boolean
.includes(false) // if any value changed
);
};
const stream = new Subject();
stream
.distinctUntilKeysChanged(['prop', 'prop2'])
.subscribe(obj => console.log(obj));
// should log
stream.next({
prop: 42,
prop2: 54,
});
// should log
stream.next({
prop: 12346,
prop2: 54,
});
// shouldn't log because neither prop nor prop2 changed
stream.next({
prop: 12346,
prop2: 54,
});
// should log
stream.next({
prop: 12346,
prop2: 5454665654645,
});
<script src="https://unpkg.com/#reactivex/rxjs#5.0.3/dist/global/Rx.js"></script>
hope you find it useful
From the RxJS section of the distinct documentation:
In RxJS, the distinct operator has two optional parameters:
a function that accepts an item emitted by the source Observable and returns a key which will be used instead of the item itself when comparing two items for distinctness
a function that accepts two items (or two keys) and compares them for distinctness, returning false if they are distinct (an equality function is the default if you do not supply your own function here)
So it looks to me (with no testing) that you could simply pass something like
(a, b) => a.lat === b.lat && a.lon === b.lon
I'm not sure about RxJS conventions to know how you should pass this (second optional) parameter.
Related
I am using https://github.com/jquense/yup#yup
I want to have an object validation schema for:
subObjectField: {
[thisKeyCanBeAnyString]: string | string[] // allow string or array of strings
}
I cannot find an example or a starting point to achieve this, any ideas?
I've put together a function which makes this easy:
export const objectOf = (schema) => ({
name: 'objectOf',
exclusive: false,
message: "Object values don't match the given schema",
test: value => {
return value === null || Object.values(value).every(schema.isValidSync(value));
}
});
example:
yup.object().test(objectOf(yup.number())).nullable()
this successfully passes for null and for objects of numbers like { foo: 52, bar: -12 }
As stated here, reactive Vuex objects are returned as Proxy objects. This may not be a problem in most cases, but how do I determine if the Proxy originated from an array or object?
The Proxy is a transparent layer on top of the array/object, so you would not need to determine the Proxy's original source.
The variable itself should be treated as if the Proxy layer were not there. If it's a Proxy of an Array, treat the variable as an Array, and the same for Object. Run the following code snippet for examples.
const arr = [1,2,3]
const arrProxy = new Proxy(arr, {}) // value is identical to `arr`
console.log(arrProxy.map(x => x * 10)) // => [ 10, 20, 30 ]
console.log('isArray', Array.isArray(arrProxy)) // => true
const obj = { foo: true, bar: false }
const objProxy = new Proxy(obj, {}) // value is identical to `obj`
console.log(Object.keys(objProxy)) // => [ 'foo', 'bar' ]
console.log('objArray type:', typeof objProxy) // => object
In JavaScript you have the nice .filter method to remove null or falsy values from arrays. So far I haven't been able to find a method to remove the same from JavaScript Objects.
Why would this be?
Currently you can create a function for arrays like :
function stripNulls(arr) {
return arr.filter(Boolean);
}
Is there a similar function that can be created for JS Objects, or is the way filter works not practical on JS Objects.
The answer to "can I do x to an object" (or an array for that matter) is usually "yes" and it frequently involves some form of reduce.
If you want to filter falsy values you could do something like this:
function filterFalsy(obj) {
return Object.keys(obj).reduce((acc, key) => {
if (obj[key]) {
acc[key] = obj[key]
}
return acc
}, {})
}
const testObj = {
a: 'test',
b: 321,
c: false
}
console.log(filterFalsy(testObj))
This returns a new object without falsy values and leaves the existing object alone.
WARNING: There are better answers provided here. Also, thanks to comments made below user's should be warned using delete may provide suboptimal performance.
Filtering invalid values is a little more complex in objects. At face value this will do what you want:
var arr = [ 'apple', 43, false ];
var trueArr = arr.filter(Boolean);
console.log(trueArr);
var obj = { 'title': 'apple', 'id': 43, 'isOrange': false, 'test': 'asd' };
Object.keys(obj)
.filter(key => !obj[key])
.forEach(key => delete obj[key]);
console.log(obj);
However, this will not iterate over child objects / functions. This logic also directly modifies the original object (which may or may not be desired).
That can easily changed by adding this logic to a function like so:
function removeFalseyProperties(obj) {
Object.keys(obj)
.filter(key => !obj[key])
.forEach(key => delete obj[key]);
return obj;
}
var testObj = { 'title': 'apple', 'id': 43, 'isOrange': false, 'test': 'asd' };
var trutheyObj = removeFalseyProperties(testObj);
console.log(trutheyObj);
falsy values are 0, undefined, null, false, etc.
myArray
.map(item => {
// ...
})
// Get rid of bad values
.filter(Boolean);
By passing Boolean we can remove all the falsy values.
Im using immutableJs
My state object looks like this:
export const initialToolbarState = Map({
temSelectionCtrl : Map({
temSelect : true,
}),
functionalCtrl : Map({
addEle : true,
grpSelect : true,
drawShape : true
}),
operationalCtrl : Map({
zoomIn : true,
zoomOut : true,
pan : true,
temSide : true
}),
referenceCtrl : Map({
saveCtrl : true
})
});
So there are objects with keys which have boolean values.
I want to map (loop) over these objects & get their keys. The boolean values tell whether to render the key or not. Immutable lets us map over Maps using its custom map function. So, the following works, however not as intended:
// map over the initialToolbarState Map object
let ctrls = initialToolbarState.map(( eachToolbar ) => {
// map over the child Map objects like temSelectionCtrl, functionalCtrl, operationalCtrl etc
return eachToolbar.map(( eachCtrl, i ) => {
// get the key corresponding to 'eachCtrl' value
let propKey = eachToolbar.keyOf( eachCtrl );
// propKey is always the first property (1st prop) of 'eachToolbar'
console.log( propKey );
...
Using immutableJs, is there a way to get the correct key corresponding to the currect 'eachCtrl' value within the loop? Could I make sure of the i to help pointing it towards the correct value for which to match the key?
You can use .map again on your objects. The second argument is the key, with the full argument signature being (mapper (value: V, key: K, iter: this))
So, this snippet:
initialToolbarState.map(( eachToolbar ) => {
eachToolbar.map((value, key) => {
console.log(key, ' ==> ', value);
});
});
Will log:
temSelect ==> true
addEle ==> true
grpSelect ==> true
drawShape ==> true
// etc…
Now just chain your returns to create the data structure that you need or do whatever with your keys.
Also, reconsider if this “Map of Maps” is the best structure for the problems you are solving. Perhaps a “List of Maps” is better if you need to iterate often. You won’t have instant read/update for individual items, but if your list consists of only a couple of items, then the performance will not suffer.
I'm planning to make a collection to hold different app-wide settings, like, say, amount of logged in users today, Google analytics tracking ID, etc. So I made a schema like this:
options_schema = new SimpleSchema({
key: {
type: String,
unique: true
},
value: {
},
modified: {
type: Date
}
});
Now the main problem is that I want value to be of any type: Number, String, Date, or even custom Objects. Though it has to be present, can't be null.
But of course it gets angry about not specifying the type. Is there a workaround for this?
You can use Match patterns for your fields' type which allow you to do pretty much anything :
const notNullPattern = Match.Where(val => val !== null)
value : {
type : notNullPattern
}
(See Arrow functions)
Note that this will allow everything but null, including undefined.
Defining patterns this way allow you to use them everywhere in your application including in check :
check({
key : 'the key',
modified : Date.now(),
value : {} // or [], 42, false, 'hello ground', ...
}, optionsSchema)
Match.test(undefined, notNullPattern) //true
Match.test({}, notNullPattern) //true
Match.test(null, notNullPattern) //false
A more general solution to exclude one value would simply be:
const notValuePattern =
unwantedValue => Match.Where(val => val !== unwantedValue))
The use of which is similar to the above:
Match.test(42, notValuePattern(null)) // true
Note that due to the use of the identity operator === it will notably fail for NaN:
Match.test(NaN, notValuePattern(NaN)) // true :(
A solution could be:
const notValuePattern =
unwantedValue => Match.Where(val => Number.isNaN(unwantedValue)?
!Number.isNaN(val)
: val !== unwantedValue
)
Should you want a solution to exclude some specific values in a schema (kind of the contrary of Match.OneOf), you could use the following:
const notOneOfPattern = (...unwantedValues) =>
Match.Where(val => !unwantedValues.includes(val)
)
This uses Array.prototype.includes and the ... spread operator. Use as follow:
Match.test(42, notOneOfPattern('self-conscious whale', 43)) // true
Match.test('tuna', notOneOfPattern('tyranny', 'tuna')) // false
Match.test('evil', notOneOfPattern('Plop', 'kittens')) // true
const disallowedValues = ['coffee', 'unicorns', 'bug-free software']
Match.test('bad thing', notOneOfPattern(...disallowedValues)) // true