RepeatWhen in combination Observable.of(x) has unexpected behavior - rxjs5

I am having unexpected behavior with Observable.of() and repeatWhen. I was wondering if this is correct behavior or not, and why?
const value = 5;
let variable = 0;
const getValue = () => {
variable = variable + 1;
return value * variable;
}
function test () {
Observable.of(getValue())
.repeatWhen(obs => obs.delay(1000))
.subscribe(value => console.log(value);
}
Expected: 5 10 15 20 ...
Result: 5 5 5 5 ...
Apparently, the value returned by Observable.of() is reused for each subsequent subscribe. How, why?

The problem is that getValue() is evaluated only once and immediately. That's got nothing to do with rxjs, it's just how Javascript works. You need to evaluate it on each retry instead, which you can do by using defer:
Observable.defer(() => Observable.of(getValue()))
.repeatWhen(obs => obs.delay(1000))
.subscribe(console.log);

The problem is with use of value. You are changing variable not value(also the value is available in two scopes i.e. Global and closer scope).
To solve the issue, Change the definition of getValue as following:
const getValue = () => {
variable = variable + 1;
value = value * variable;
return value;
}
So, corrected code will look like:
const value = 5;
let variable = 0;
const getValue = () => {
variable = variable + 1;
value = value * variable;
return value;
}
function test () {
Observable.of(getValue())
.repeatWhen(obs => obs.delay(1000))
.subscribe(value => console.log(value);
}

Related

Get text from element & use it for next action

I'm beginner Cypress user, currently facing a problem with getting text from DOM element and then using it for next action.
Steps which were done by me:
1) Get element + text
Then(
"User change quantity of product: {string} to {int}",
(productName, qty) => {
var currentQty;
cy.get("[data-cy ='basketContainer']")
.contains(productName)
.parent()
.parent()
.find("div > span")
.then((variable) => {
var quantityText = variable.text();
cy.wrap(quantityText).as("quantityText");
});
In this case I'm completely not sure if entire: "then" is ok. The case which I was investigation is that in cypress there is no "normal" variable assignment. It may be done with combination of wrap and then alliasing it.
2) Get the alliased value
cy.get("#quantityText");
Not very needed step but I was wondering if we can reference to aliases by '#'
3) Use that alias inside the "method"
var increaseButton = cy.get("[data-cy='decreaseQtyProduct']");
var decreaseButton = cy.get("[data-cy='increaseQtyProduct']");
var timesToClick = cy.get("#quantityText") + qty;
cy.log(timesToClick)
if (currentQty == qty) {
console.log("Do nothing, values are equal!");
} else if (currentQty > qty) {
for (let i = 1; i < timesToClick; i++) decreaseButton.click();
} else if (currentQty < qty) {
timesToClick = Math.abs(timesToClick);
for (let i = 1; i < timesToClick; i++) increaseButton.click();
}
When I'm cy.logging that variable: "timesToClick" I'm receiving [object Object] instead of text inside an element. For sure what also needs to be done is to parse that string value to int value.
There's nothing wrong with the way you create the alias, but when using it don't save the result of cy.get(...) to variables, either getting the alias or selecting the elements.
It can lead to unpredictable results. For element selection, you can save the selector string instead. For using the alias, you must use .then().
Some other optimizations included too.
const increaseButton = "[data-cy='decreaseQtyProduct']"
const decreaseButton = "[data-cy='increaseQtyProduct']"
cy.get('#quantityText').then(quantityText => {
if (currentQty === qty) return
const timesToClick = Math.abs(parseInt(quantityText) + qty)
const selector = currentQty > qty ? increaseSelector : decreaseSelector;
Cypress._.times(timesToClick, () => cy.get(selector).click())
})
You can alias the inner text like this:
Then(
'User change quantity of product: {string} to {int}',
(productName, qty) => {
var currentQty
cy.get("[data-cy ='basketContainer']")
.contains(productName)
.parent()
.parent()
.find('div > span')
.invoke('text')
.as('quantityText')
}
)
Then you can fetch the value from the alias like this:
var increaseButton = cy.get("[data-cy='decreaseQtyProduct']")
var decreaseButton = cy.get("[data-cy='increaseQtyProduct']")
cy.get('#quantityText').then((quantityText) => {
var timesToClick = +quantityText + qty //+quantityText changes string to number
console.log(timesToClick) //prints the number
if (currentQty == qty) {
console.log('Do nothing, values are equal!')
} else if (currentQty > qty) {
for (let i = 1; i < timesToClick; i++) decreaseButton.click()
} else if (currentQty < qty) {
timesToClick = Math.abs(timesToClick)
for (let i = 1; i < timesToClick; i++) increaseButton.click()
}
})

how to to convert for to foreach

jslint tell Unexpected 'for'.
so i think that i must convert for with foreach
but how?
if someone can help
thanks
// Grab the original element
var original = document.getElementsByTagName("noscript")[0];
// Create a replacement tag of the desired type
var replacement = document.createElement("span");
var i;
// Grab all of the original's attributes, and pass them to the replacement
for(i = 0, l = original.attributes.length; i < l; ++i){
var nodeName = original.attributes.item(i).nodeName;
var nodeValue = original.attributes.item(i).nodeValue;
replacement.setAttribute(nodeName, nodeValue);
}
// Persist contents
replacement.innerHTML = original.innerHTML;
// Switch!
original.parentNode.replaceChild(replacement, original);
You have a comma after i = 0, <========
it should be semicolon.
Another issue is declaring l = original.attributes.length you don't need the variable l
just use it as for(i = 0; i < original.attributes.length; ++i){
if you still wanna use a forEach you can do it as:
original.attributes.forEach(element => {
var nodeName = element.nodeName;
var nodeValue = element.nodeValue;
replacement.setAttribute(nodeName, nodeValue);
});
thanks for your answer, i got Uncaught TypeError: original.attributes.forEach is not a function
function Switch() {
var original = document.getElementsByTagName("noscript")[0];
var replacement = document.createElement("span");
original.attributes.forEach(element => {
var nodeName = element.nodeName;
var nodeValue = element.nodeValue;
replacement.setAttribute(nodeName, nodeValue);
});
// Persist contents
replacement.innerHTML = original.innerHTML;
// Switch!
original.parentNode.replaceChild(replacement, original);
}

TypeError: Cannot read property 'object' of null at recallSavedValue in dat.gui.module.js:3218

In three.js, I am trying to add gui by custom values. but it gives me error.
this is my code
var arm = document.getElementById('arm');
var model_arr = [];
model_arr['narm'] = (arm)? arm:0;
function init(){
...create scene, camera, load obj,mtl render it etc.
initGUI();
}
function initGUI() {
initGUICalled = true;
if (typeof model_arr !== 'undefined' && model_arr.length > 0) {
params = {
Arm: (model_arr['narm'])? model_arr['narm']:20.75,
resetval: function() { resetBody(); }
};
}
// Set up dat.GUI to control targets
lower = gui.addFolder( 'Lower Measurement' );
let ArmCtrl = lower.add( params, 'Arm', 18.75, 22.75 ).step( 0.21 ).name("Arm in Inch").listen();
ArmCtrl.onChange( function ( value ) {
mesh.morphTargetInfluences[ 0 ] = (value - 20.75)/2.1;
} );
lower.close();
gui.close();
}
function resetBody(){
initGUICalled = true ;
params.Arm = 20.75;
mesh.morphTargetInfluences[ 0 ] = 0;
}
I am trying to give value from model_arr object. So for this I have tried.
let ArmCtrl = lower.add( params, 'Arm', 18.75, 22.75 ).step( 0.21 ).name("Arm in Inch").listen();
ArmCtrl.onChange( function ( value ) {
mesh.morphTargetInfluences[ 0 ] = (value - model_arr['narm'])/2.1;
} );
and got error
Uncaught TypeError: folder.add(...).step is not a function
at new_women_xl_initGUI
I have check these reference
Saving parameters with dat.gui seems broken?
Uncaught TypeError: $(...).steps is not a function
but not get luck.
With model_arr, you're flip-flopping between using it as an array and as an object:
// Here you initialize it as an array
var model_arr = [];
// Here you're accessing it as an object, not adding any values to the array
model_arr['narm'] = (arm) ? arm : 0;
// The array's length is still 0, so params never gets a value
if (model_arr.length > 0) {
params = {
Arm: xxx
};
}
// Now when you try to access params.Arm, it doesn't exist
let ArmCtrl = lower.add( params, 'Arm', 18.75, 22.75 )
If you want to use an array, stick to an array throughout the lifetime of the variable. If you want to create an object, start with a new variable so you don't confuse the two.
If you want to add a value at the end of an array, you could use the .push() method:
var narm = (arm) ? arm : 0;
// Add value to the end of the array
model_arr.push(narm);
// Now the array's length is 1

Calling n times the same observable

I have a http get webservice which I need to call n times, adding the return of my last call each time (first time there is a default value) how can I do it ?
You can use 'expand' operator from rxjs. It will loop until it's supplied with empty() observable. Here is example:
import { empty } from 'rxjs';
private service; <--- service that gives us the observable by some value
private initialValue: number = 5;
private counter: number = 0;
private source$: Observable<number> = this.service.getSourceWithValue(initialValue);
source$.pipe(
expand(value => isCounterExceeded()
? incrementCounterAndGetNextSourceObservableWithValue(value);
: empty()
);
// if counter is not exceeded we will increment the counter and create another
// observable based on current value. If it is exceeded, we are stopping the loop by
// returning the empty() observable
private incrementCounterAndGetNextSourceObservableWithValue(value: number): Observable<number> {
this.counter++;
return this.service.getSourceWithValue(value);
}
private isCounterExceeded() {
return this.counter >= 4;
}
This sounds like you could use expand:
const N = 4;
const source = of(1).pipe(
expand((previous, index) => index === 4 ? EMPTY : of(previous * 2))
);
source.subscribe(console.log);
Live demo: https://stackblitz.com/edit/rxjs-fcpin2

rxjs ofObjectChanges obsolete

As ofObjectChanges is built on Object.observe() which is obsolete (https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/observe) I'm looking for an alternative for watching object property changes. Anyone know of one?
Perhaps using a Proxy is an option, though it's needed to replace the original object
const { Subject } = require('rxjs');
// Take an object, and return a proxy with an 'observation$' stream
const toObservableObject = targetObject => {
const observation$ = new Subject();
return new Proxy(targetObject, {
set: (target, name, value) => {
const oldValue = target[name];
const newValue = value;
target[name] = value;
observation$.next({ name, oldValue, newValue });
},
get: (target, name) => name == 'observation$' ? observation$ : target[name]
});
}
const observableObject = toObservableObject({ });
observableObject.observation$
.filter(modification => modification.name == 'something')
.subscribe(({ name, oldValue, newValue }) => console.log(`${name} changed from ${oldValue} to ${newValue}`));
observableObject.something = 1;
observableObject.something = 2;
The output
something changed from undefined to 1
something changed from 1 to 2
Look for Proxy in the compatibility table current node versions has full support)
https://kangax.github.io/compat-table/es6/
And documentation of the Proxy at
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Proxy

Resources