RxJS - Future insertion is not observable? - javascript

I have a single HTML element :
<ul id="animals"></ul>
And this code which observe an array and append its element as HTML elements :
const zooAnimals = ['anteater', 'bear', 'cheetah', 'donkey'];
const observable = Rx.Observable.from(zooAnimals);
const subscriber = observable
.subscribe(
onNext,
function onError(err) {
console.log('Error: ' + err);
},
function onCompleted() {
console.log('no more animals!');
}
);
function onNext(animal) {
const list = document.querySelector('#animals');
console.log('list', list)
const li = document.createElement('li');
li.innerHTML = animal;
list.appendChild(li);
}
So now the #animals element is filled with 4 ULs.
But Now I want to add another element via setTimeout
So I add :
setTimeout(function (){
zooAnimals.push('dddddd');
},4000);
But nothing happens.
Question
What am I missing here and how can I mek this code work if some other soure appends items to the array.
plunker :
https://plnkr.co/edit/xzyjCOR8lKl3tc70kzC9?p=info

That is not how Observables work, in RxJS everything is a stream and to emit new data you need to use the RxJS-api - there are many ways to achieve this for your issue, one of those would be to use a Subject - which is basically a combination of Observer and Observable, meaning that you can emit data on it and at the same time subscribe to it:
const zooAnimals = ['anteater', 'bear', 'cheetah', 'donkey'];
const subject = new Rx.Subject();
const subscriber = subject
.subscribe(
onNext,
function onError(err) {
console.log('Error: ' + err);
},
function onCompleted() {
console.log('no more animals!');
}
);
function onNext(animal) {
const list = document.querySelector('#animals');
console.log('list', list)
const li = document.createElement('li');
li.innerHTML = animal;
list.appendChild(li);
}
zooAnimals.forEach(animal => subject.onNext(animal));
setTimeout(() => subject.onNext("giraffe"), 100);
I've updated your plunker as well:
https://plnkr.co/edit/bx9NtRT0n5HuZQSCcvkH?p=preview
As a sidenote: If you are now starting to get into RxJS I would suggest you to use RxJS5, which is the latest version of the library.

Related

How can I can keep the done Todo's from vanishing

I am trying to create a Todo list and wonder how I would go about keeping the completed todos from vanishing each time I add a new task. I am aware that this is happening because I clear my div each time a new task is added, however I am unsure on how to proceed for me to keep using arrays on this and still show completed tasks along with new tasks.
Codepen: https://codepen.io/martinkariuki7-the-looper/pen/OJvQXRW
const newToDo = document.getElementById('todo-new')
const addBtn = document.getElementById('addToDo')
const main= document.getElementById('toDoList')
const taskDone = document.getElementsByClassName('task')
let toDoList = []
// populate to do list
function populateToDo(){
let todo = newToDo.value
todo !== null && todo !== '' ? toDoList.push(todo) : alert('You must write something')
updateDOM()
}
//Update DOM
function updateDOM(){
// Clear main div
main.innerHTML = `<h1>To do list </h1>`
// Show tasks on the front
toDoList.forEach(item =>{
let task = document.createElement('div')
task.classList.add('task')
task.innerHTML = `<label><input type="checkbox" >${item}</label><br>`
main.append(task)
task.addEventListener('change', () => task.classList.toggle('task-complete'))
newToDo.value = ''
})
}
// Event listeners
// Add Tasks
addBtn.addEventListener('click', populateToDo)
newToDo.addEventListener('keydown', (e) => {
if(e.code === 'Enter'){
e.preventDefault()
populateToDo()
}
})
If you want to use an array to update the list, you need to compare what you have with what will be changed.
If there aren't items with the same text, you could do something like this:
function updateNodes()
{
const nodesMap = new Map();
[...document.querySelectorAll('div label')]
.forEach(node => {
nodesMap.add(node.innerText, node);
});
const nodesUpdate = [];
toDoList.forEach(item => {
if (nodesMap.has(item)) {
nodesUpdate.push(nodesMap[item]);
} else {
const newNode = document.createElement('div');
newNode.classList.add('task');
newNode.innerHTML = `<label><input type="checkbox" >${item}</label>`;
nodesUpdate.push(newNode);
}
});
return nodesUpdate;
}
Otherwise, each item (or task) needs to have an identification. Otherwise, if there are two items with the same name, you won't know which one was removed or moved. Besides, without identifications, you can't know if an item was renamed.
The code here was not tested and, of course, you need to adapt it to your needs.

How to use loops with fetch and Promise.all?

So I was following along an MDN article on promises and was wondering how to modify the following code to be able to work for any number of files (not just 3).
function fetchAndDecode(url) {
return fetch(url).then(response => {
if(!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
if(response.headers.get("content-type") === "image/jpeg") {
return response.blob();
} else if(response.headers.get("content-type") === "text/plain") {
return response.text();
}
}
})
.catch(e => {
console.log(`There has been a problem with your fetch operation for resource "${url}": ` + e.message);
})
.finally(() => {
console.log(`fetch attempt for "${url}" finished.`);
})
}
let coffee = fetchAndDecode('coffee.jpg');
let tea = fetchAndDecode('tea.jpg');
let description = fetchAndDecode('description.txt');
Promise.all([coffee, tea, description]).then(values => {
console.log(values);
// Store each value returned from the promises in separate variables; create object URLs from the blobs
let objectURL1 = URL.createObjectURL(values[0]);
let objectURL2 = URL.createObjectURL(values[1]);
let descText = values[2];
// Display the images in <img> elements
let image1 = document.createElement('img');
let image2 = document.createElement('img');
image1.src = objectURL1;
image2.src = objectURL2;
document.body.appendChild(image1);
document.body.appendChild(image2);
// Display the text in a paragraph
let para = document.createElement('p');
para.textContent = descText;
document.body.appendChild(para);
});
MDN specifically notes that "If you were improving this code, you might want to loop through a list of items to display, fetching and decoding each one, and then loop through the results inside Promise.all(), running a different function to display each one depending on what the type of code was. This would make it work for any number of items, not just three." I'm not sure how to do this though, and would appreciate help. Thanks.
The second part of the code could be generalised as follows:
let urls = ['coffee.jpg', 'tea.jpg', 'description.txt'];
Promise.all(urls.map(fetchAndDecode)).then(values => {
let elem;
for (let value of values) {
if (value instanceof Blob) {
elem = document.createElement('img');
elem.src = URL.createObjectURL(value);
} else if (typeof value === "string") {
elem = document.createElement('p');
elem.textContent = value;
} else {
console.log("unexpected value type");
continue;
}
document.body.appendChild(elem);
}
});
const resources = ['coffee.jpg', 'tea.jpg', 'description'];
const resourceRequests = resources.map(fetchAndDecode);
Promise.all(resourceRequests.then(values => {
...
Is one way to implement the suggestion. This approach allows for easier modification of the list of resources, but doesn't really change any of the Promise code.
The .map code above is equivalent to (resource => fetchAndDecode(resource)) since fetchAndDecode takes only the first argument that .map would pass to it.

asynchronous loop for in Javascript

I'm trying to iterate and print out in order an array in Javascript that contains the title of 2 events that I obtained from doing web scraping to a website but it prints out in disorder. I know Javascript is asynchronous but I'm new in this world of asynchronism. How can I implement the loop for to print the array in order and give customized info?
agent.add('...') is like console.log('...'). I'm doing a chatbot with DialogFlow and NodeJs 8 but that's not important at this moment. I used console.log() in the return just for debug.
I tried the next:
async function printEvent(event){
agent.add(event)
}
async function runLoop(eventsTitles){
for (let i = 0; i<eventsTitles.length; i++){
aux = await printEvent(eventsTitles[i])
}
}
But i got this error error Unexpected await inside a loop no-await-in-loop
async function showEvents(agent) {
const cheerio = require('cheerio');
const rp = require('request-promise');
const options = {
uri: 'https://www.utb.edu.co/eventos',
transform: function (body) {
return cheerio.load(body);
}
}
return rp(options)
.then($ => {
//** HERE START THE PROBLEM**
var eventsTitles = [] // array of event's titles
agent.add(`This mont we have these events available: \n`)
$('.product-title').each(function (i, elem) {
var event = $(this).text()
eventsTitles.push(event)
})
agent.add(`${eventsTitles}`) // The array prints out in order but if i iterate it, it prints out in disorder.
// *** IMPLEMENT LOOP FOR ***
agent.add(`To obtain more info click on this link https://www.utb.edu.co/eventos`)
return console.log(`Show available events`);
}).catch(err => {
agent.add(`${err}`)
return console.log(err)
})
}
I would like to always print out Event's title #1 and after Event's title #2. Something like this:
events titles.forEach((index,event) => {
agent.add(`${index}. ${event}`) // remember this is like console.log(`${index}. ${event}`)
})
Thanks for any help and explanation!
There no async case here but if you still face difficultly than use this loop
for (let index = 0; index < eventsTitles.length; index++) {
const element = eventsTitles[index];
agent.add(${index}. ${element})
}

Consuming APIs - How to connect to an API

I have most of my code written but I'm not sure what I'm doing wrong on this:
let url = 'https://cors-anywhere.herokuapp.com/https://newsapi.org/v2/top-headlines?sources=hacker-news&apiKey=3dcfcd098261443dae7c7d002f25c062';
fetch(url)
.then(r =>{
return r.json();
})
.then(data => {
let articles = data.articles;
let storyList = document.createElement("ul");
let body = document.querySelector("body");
body.appendChild(storyList);
})
articles.map(articles => {
let storyItem = document.createElement("li");
storyItem.innerHTML = 'a href = "' + articles.href + '">' + articles.title + "</a>";
storyList.appendChild(storyItem);
})
.catch(e => {
console.log('An error has occurred: ${e}');
});
I had taken out the < > from the API code and tried switching things around like switching some properties to say something different but could someone help me understand this a bit better? Thanks in advance!
There were several things that you were doing wrong.
No need to use a proxy when the API you are consuming allows cors requests.
You were trying to access the "articles" out of scope and before the promise was resolved
You were using the wrong method, IMO, on the "articles" array. From here: Array.prototype.map()
The map() method creates a new array with the results of calling a provided function on every element in the calling array.
but you were not trying create a new array, you just wanted to iterate the array's elements. That is what Array.prototype.forEach() is for.
You used single quotes ' on your template literal instead of back-ticks `
let url = 'https://newsapi.org/v2/top-headlines?sources=hacker-news&apiKey=3dcfcd098261443dae7c7d002f25c062';
fetch(url)
.then(response => {
return response.json();
})
.then(data => {
let list = document.createElement('ul');
document.querySelector("body").appendChild(list);
data.articles.forEach(article => {
let item = document.createElement('li');
item.innerHTML = '' + article.title + "";
list.appendChild(item);
});
})
.catch(e => {
console.log(`An error has occurred: ${e}`);
});

Rx.js, using Subject to multicast from Observable

If there are any Rx.js experts out there? I'm trying to multicast an observable by using a subject, as per the instructions on any number of websites, including rx.js docs.
var mainDataSource = Rx.Observable.from(summaries[0].added)
//add a timestamp to each live entry as it passes through
.timestamp()
.map(function(scriptElement) {
var array = [scriptElement, scriptElement.timestamp]; return array;
})
//check contents of the array
.do(function(array) { console.log(array); });
var multicaster = new Rx.Subject();
var subSource = mainDataSource.subscribe(multicaster);
//attach the inline observer to the multicaster subject
multicaster.subscribe(
function (x) { console.log('Value published to inlineScriptObserver: ' + x); },
function (e) { console.log('onError: ' + e.message); },
function () { console.log('inlineScriptObserver onCompleted'); }
);
//attach the external observer to the multicaster subject
multicaster.subscribe(
function (x) { console.log('Value published to externalScriptObserver: ' + x); },
function (e) { console.log('onError: ' + e.message); },
function () { console.log('externalScriptObserver onCompleted'); }
);
And the output I'm getting is as follows:
[Object, 1493425651491]
inlineScriptObserver onCompleted
externalScriptObserver onCompleted
So the Subject and the Observable are clearly connected as the onCompleted event is being transmitted from one to the other. However I am getting no data travelling alongside. The data in the correct format is there at the end of the Observable but it is not printing in the console from the Subject's Observer.
What am I missing? It's eluding me.
OK, it may be bad form answering your own question but in case anyone else comes along with the same problem...
The docs I read must have been outdated, relating to rx.js 4 not 5, or something. As of today, and according to this page,
https://github.com/ReactiveX/rxjs/blob/master/doc/subject.md
The correct syntax for the above example is as follows:
var multicaster = new Rx.Subject();
var mainDataSource = Rx.Observable.from(summaries[0].added)
//add a timestamp to each live entry as it passes through
.timestamp()
//log the timestamp for testing purposes
.do(function(scriptElement) { console.log("mainDataSource Timestamp: " + scriptElement.timestamp); })
//include the timestamp in array and deliver that array to subscribers
.map(function(scriptElement) { var array = [scriptElement, scriptElement.timestamp]; return array; })
//check contents of the array
do(function(array) { console.log(array); });
var multicastedDataSource = mainDataSource.multicast(multicaster);
//attach the inline observer to the multicaster subject
multicastedDataSource.subscribe(val => console.log(val), null, () => console.log('inlineScriptObserver complete'));
//attach the external observer to the multicaster subject
multicastedDataSource.subscribe(val => console.log(val), null, () => console.log('externalScriptObserver complete'));
multicastedDataSource.connect();
The key differences being the use of multicast() rather than subscribe on the observable and then the requirement to connect() to the subject piping the multicasted observable as well as having the observers subscribing.
No wonder my older rx.js book was so cheap on Amazon...
Either subscribe before firing an events or use ReplaySubject. See the working fiddle:
var mainDataSource = Rx.Observable.from([1, 2, 3])
//add a timestamp to each live entry as it passes through
.timestamp()
.map(function(scriptElement) {
var array = [scriptElement, scriptElement.timestamp]; return array;
})
//check contents of the array
.do(function(array) { console.log(array); });
var multicaster = new Rx.ReplaySubject();
var subSource = mainDataSource.subscribe(multicaster);
//attach the inline observer to the multicaster subject
multicaster.subscribe(
function (x) { console.log('Value published to inlineScriptObserver: ' + x); },
function (e) { console.log('onError: ' + e.message); },
function () { console.log('inlineScriptObserver onCompleted'); }
);
//attach the external observer to the multicaster subject
multicaster.subscribe(
function (x) { console.log('Value published to externalScriptObserver: ' + x); },
function (e) { console.log('onError: ' + e.message); },
function () { console.log('externalScriptObserver onCompleted'); }
);
The output is:
[Object, 1493467831996]
[Object, 1493467831999]
[Object, 1493467832000]
Value published to inlineScriptObserver: [object Object],1493467831996
Value published to inlineScriptObserver: [object Object],1493467831999
Value published to inlineScriptObserver: [object Object],1493467832000
inlineScriptObserver onCompleted
Value published to externalScriptObserver: [object Object],1493467831996
Value published to externalScriptObserver: [object Object],1493467831999
Value published to externalScriptObserver: [object Object],1493467832000
externalScriptObserver onCompleted

Categories