Working on dialog component with angular js and now I find out that my function is subscribed and in if condition do not quit method, but continuously executing another function afterClosed() , here is example of code :
openCreateNewContentDialog(): void {
const oldData = this.dataSource.data;
const dialogConfig = AppConstants.matDialogConfig();
const dialog = this.dialog.open(LicenseDialogComponent, dialogConfig);
dialog.beforeClosed().subscribe(licenceDate => {
for (const datesToCheck of oldData) {
const newDateFrom = new Date(licenceDate.expirationDateFrom);
const oldDateTo = new Date(datesToCheck.expirationDateTo.toString());
if (newDateFrom <= oldDateTo) {
// console.log('return?');
return;
}
}
});
dialog.afterClosed().subscribe(licence => {
if (licence) {
this._value.push(licence);
this.dataSource.data = this.value;
this.change();
}
});
}
What is the best and optimized way to unsubscribe beforeClosed() function?
So from your description, I understand that you dont want a second subscription to happen if the condition in the first subscriber is true, right? But you subscription will happen anyway because you instantiated it in the method, the code in the subscribe() it's just a callback. So if you dont want a lot of rewriting I will suggest storing
subscriptions in variables, so you will have an access to them and can unsubscribe at any time.
openCreateNewContentDialog(): void {
const oldData = this.dataSource.data;
const dialogConfig = AppConstants.matDialogConfig();
const dialog = this.dialog.open(LicenseDialogComponent, dialogConfig);
const beforeClosed = dialog.beforeClosed().subscribe(licenceDate => {
for (const datesToCheck of oldData) {
const newDateFrom = new Date(licenceDate.expirationDateFrom);
const oldDateTo = new Date(datesToCheck.expirationDateTo.toString());
if (newDateFrom <= oldDateTo) {
// console.log('return?');
afterClosed.unsubscribe();
return;
}
}
});
const afterClosed = dialog.afterClosed().subscribe(licence => {
if (licence) {
this._value.push(licence);
this.dataSource.data = this.value;
this.change();
}
});
}
I hope it helps! Also you can try https://www.digitalocean.com/community/tutorials/angular-takeuntil-rxjs-unsubscribe if you have to handle multiple subscriptions.
Related
I am building a React functional component that uses some state variables, and I am trying to modify some of these variables from an external function thats called on a button click event, but when I pass the reference to the state methods to this external function, all of them are undefined. What could be the cause? If I just put the exact same code within the functional component, it works perfectly as intended.
import React from "react";
import {CodeArea, Table, EmptyField, Button} from '../util/util.js'
import {Step, Load} from "./buttons.js" // The external function in question, Loadfunction
Core(props){
const registersInitial = new Array(32).fill(0);
let buttonNamesInitial = ['LOAD','play', 'step-forward', 'run-to', 'step-back','pause','halt', 'rerun', 'add'];
const [bigText, setText] = React.useState();
const [registers, setRegisters] = React.useState(registersInitial);
const [running, setRunning] = React.useState(false);
const [programCounter, setProgramCounter] = React.useState(0);
const [buttonNames, setButtonNames] = React.useState(buttonNamesInitial);
const [lines, setLines] = React.useState([]);
const getBigText = () => {
return bigText;
}
const getRunning = () =>{
return running;
}
const getButtonNames = () => {
return buttonNames;
}
//... some code here thats irrelevant
function getQuickbarContents(){
let functions = [ //Backend will come here
() => Load(setRunning, getRunning, setButtonNames, getButtonNames, setProgramCounter, setLines, getBigText), //Where Load gets called
() => console.log("code running..."),
() => console.log("stepping to next line..."),
() => console.log("running to location..."),
() => console.log("stepping..."),
() => console.log("pausing..."),
() => console.log("halting..."),
() => console.log("running again..."),
() => console.log("select widget to add...")
]
let contents = [];
let row = [];
for (let i = 0; i<9; i++){
row.push(<Button onClick ={functions[i]} className='quickbar' name={buttonNames[i]}/>);
contents.push(row);
row = [];
}
return contents
}
const divs = [];
let buttons = getQuickbarContents();
divs.push(<div key='left' className='left'><Table name='quickbar' rows='7' cols='1' fill={buttons}/> </div>);
//... some more code to push more components do divs
return divs;}
export default Core;`
Button looks like this:
function Button({onClick, className, name}){
return <button onClick={onClick} className={className} name={name}>{name}</button>
}
and Load like this:
export function Load({setRunning, getRunning, setButtonNames, getButtonNames, setProgramCounter, setLines, getBigText}){
let newButtonName;
if (!getRunning()){ // Functions errors out with getRunning() undefined
herenewButtonName = "Reload";
}
else{ //while running if user wants to reload
newButtonName = "LOAD";
}
let lines = getBigText().split(/\n/);
setLines(lines);
setProgramCounter(0);
setRunning(!getRunning());
const newButtonNames = getButtonNames().map((value, index) =>{
if (index === 0){
return (newButtonName);
}
return value;
})
setButtonNames(newButtonNames);
}
So essentially in my head the flow should be: state methods initialised -> button components created -> wait for click of a button -> update state variablesBut clearly, something goes wrong along the way.
I've tried using inspection mode debugging, which revealed that in fact all of the parameters to Load are undefined when they are evaluated.
Note, that everything works as intended if I change the code up like this, eg. just put the whole function within the React component;
//... everything same as before
function getQuickbarContents(){
let functions = [
() =>{
let newButtonName;
if (!getRunning()){ //User clicks to start running
newButtonName = "Reload";
}
else{
newButtonName = "LOAD";
}
let lines = getBigText().split(/\n/);
setLines(lines);
setProgramCounter(0);
setRunning(!getRunning());
const newButtonNames = getButtonNames().map((value, index) =>{
if (index === 0){
return (newButtonName);
}
return value;
})
setButtonNames(newButtonNames)},
() => console.log("code running..."),
() => console.log("stepping to next line..."),
() => console.log("running to location..."),
() => Step(setRegisters, registers, setProgramCounter, programCounter, lines[programCounter]),
() => console.log("pausing..."),
() => console.log("halting..."),
() => console.log("running again..."),
() => console.log("select widget to add...")
]
//...everything same as before
so consequently the error is somewhere in the way I pass parameters to Load, or maybe I'm doing something I shouldn't be doing in React. Either way I have no clue, any ideas?
Problem was in the way parameters were defined in Load, as #robin_zigmond pointed out. Fixed now.
i know that the problem is that let todoList is an empty array, but i dont know how to solve it.
the id tags in my created html is so e can create a delete button later
heres my code:
const textArea = document.querySelector("textarea");
const button = document.querySelector("button");
const listContainer = document.querySelector(".list-container");
let id = 0;
let todoList = [];
button.onclick = function () {
const listItem = {
title: textArea.value,
};
todoList.push(listItem);
addToStorage(todoList);
const dataFromStorage = getFromStorage();
createHtml(dataFromStorage);
};
function addToStorage(items) {
const stringify = JSON.stringify(items);
localStorage.setItem("list", stringify);
}
function getFromStorage() {
const data = localStorage.getItem("list");
const unstrigified = JSON.parse(data);
return unstrigified;
}
const createHtml = (data) => {
id++;
listContainer.innerHTML = "";
data.forEach((item) => {
listContainer.innerHTML += `<div class="list-item" data-id=${id}><p>${item.title} </p><button class="remove" data-id=${id}>Delete</button></div>`;
});
};
The problem here is you just forgot to load the data from localStorage when the page loaded like this
window.onLoad = () => {
const dataFromStorage = getFromStorage();
if(dataFromStorage){
createHtml(dataFromStorage);
} else {
createHtml([]);
}
}
The problem in the code is as follows
Initially the todolist will be an empty array. so when you do the below
todoList.push(listItem);
// adding to local storage which will override the existing todos when page is refreshed
addToStorage(todoList);
// So when the below line is executed only the latest todo will be returned
const dataFromStorage = getFromStorage();
createHtml(dataFromStorage);
Fix:
Initialise the todos from localstorage instead of an empty array
let todoList = [];
// change it as below
let todoList = getFromStorage();
Now Modify the getFromStorage() as below
// If the data is present in the localStorage then return it, else return empty array
function getFromStorage() {
const data = localStorage.getItem("list");
if (!data) return [];
const unstrigified = JSON.parse(data);
return unstrigified;
}
Now when the page is loaded, we need to display the todos. Add the below lines of code
window.onload = function () {
createHtml(todoList);
};
That's it. This will fix the issue.
Few minor improvements can be made as well.
todoList.push(listItem);
addToStorage(todoList);
const dataFromStorage = getFromStorage(); // this line is not necessary, remove it
createHtml(dataFromStorage); // change this to createHtml(todoList)
Codepen
Thanks.
I am working on some Puppeteer powered website analytics and would really need to list all events on a page.
It's easy with "normal" JavaScript, so I thought I could just evaluate it in the Puppeteer and get to other tasks.
Well - it is not so easy and "getEventListeners" is not working for example. So this code below is not working (but if I take the code that gets valuated, copy it to browser's console and run - it works well);
exports.getDomEventsOnElements = function (page) {
return new Promise(async (resolve, reject) => {
try {
let events = await page.evaluate(() => {
let eventsInDom = [];
const elems = document.querySelectorAll('*');
for(i = 0; i < elems.length; i++){
const element = elems[i];
const allEventsPerEl = getEventListeners(element);
if(allEventsPerEl){
const filteredEvents = Object.keys(allEventsPerEl).map(function(k) {
return { event: k, listeners: allEventsPerEl[k] };
})
if(filteredEvents.length > 0){
eventsInDom.push({
el: element,
ev: filteredEvents
})
}
}
}
return eventsInDom;
})
resolve(events);
} catch (e) {
reject(e);
}
})
}
I've investigated further and it looks like this will not work in Puppeteer and even tried with good old JQuery's const events = $._data( element[0], 'events' ); but it does not work either.
Then I stumbled upon Chrome DevTools Protocol (CDP) and there it should be possible to get it by defining a single element on beforehand;
const cdp = await page.target().createCDPSession();
const INCLUDE_FN = true;
const { result: {objectId} } = await cdp.send('Runtime.evaluate', {
expression: 'foo',
objectGroup: INCLUDE_FN ?
'provided' : // will include fn
'' // wont include fn
});
const listeners = await cdp.send('DOMDebugger.getEventListeners', { objectId });
console.dir(listeners, { depth: null });
(src: https://github.com/puppeteer/puppeteer/issues/3349)
But this looks too complicated when I would like to check each and every DOM element for events and add them to an array. I suspect there is a better way than looping the page elements and running CDP for each and every one. Or better said - I hope :)
Any ideas?
I would just like to have an array of all elements with (JS) events like for example:
let allEventsOnThePage : [
{el: "blutton", events : ["click"]},
{el: "input", events : ["click", "blur", "focus"]},
/* you get the picture */
];
I was curious so I looked into expanding on that CDP example you found, and came up with this:
async function describe (session, selector = '*') {
// Unique value to allow easy resource cleanup
const objectGroup = 'dc24d2b3-f5ec-4273-a5c8-1459b5c78ca0';
// Evaluate query selector in the browser
const { result: { objectId } } = await session.send('Runtime.evaluate', {
expression: `document.querySelectorAll("${selector}")`,
objectGroup
});
// Using the returned remote object ID, actually get the list of descriptors
const { result } = await session.send('Runtime.getProperties', { objectId });
// Filter out functions and anything that isn't a node
const descriptors = result
.filter(x => x.value !== undefined)
.filter(x => x.value.objectId !== undefined)
.filter(x => x.value.className !== 'Function');
const elements = [];
for (const descriptor of descriptors) {
const objectId = descriptor.value.objectId;
// Add the event listeners, and description of the node (for attributes)
Object.assign(descriptor, await session.send('DOMDebugger.getEventListeners', { objectId }));
Object.assign(descriptor, await session.send('DOM.describeNode', { objectId }));
elements.push(descriptor);
}
// Clean up after ourselves
await session.send('Runtime.releaseObjectGroup', { objectGroup });
return elements;
}
It will return an array of objects, each with (at least) node and listeners attributes, and can be used as follows:
/** Helper function to turn a flat array of key/value pairs into an object */
function parseAttributes (array) {
const result = [];
for (let i = 0; i < array.length; i += 2) {
result.push(array.slice(i, i + 2));
}
return Object.fromEntries(result);
}
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://chromedevtools.github.io/devtools-protocol', { waitUntil: 'networkidle0' });
const session = await page.target().createCDPSession();
const result = await describe(session);
for (const { node: { localName, attributes }, listeners } of result) {
if (listeners.length === 0) { continue; }
const { id, class: _class } = parseAttributes(attributes);
let descriptor = localName;
if (id !== undefined) { descriptor += `#${id}`; }
if (_class !== undefined) { descriptor += `.${_class}`; }
console.log(`${descriptor}:`);
for (const { type, handler: { description } } of listeners) {
console.log(` ${type}: ${description}`);
}
}
await browser.close();
})();
which will return something like:
button.aside-close-button:
click: function W(){I.classList.contains("shown")&&(I.classList.remove("shown"),P.focus())}
main:
click: function W(){I.classList.contains("shown")&&(I.classList.remove("shown"),P.focus())}
button.menu-link:
click: e=>{e.stopPropagation(),I.addEventListener("transitionend",()=>{O.focus()},{once:!0}),I.classList.add("shown")}
I'm currently implementing a WebSocket connection and I'm using a command pattern approach to emit some messages according to the command that users execute.
This is an abstraction of my implementation:
let socketInstance;
const globalName = 'ws'
const globalObject = window[globalName];
const commandsQueue = isArray(globalObject.q) ? globalObject.q : [];
globalObject.q = {
push: executeCommand
};
commandsQueue.forEach(command => {
executeCommand(command);
});
function executeCommand(params) {
const actions = {
create,
send
};
const [command, ...arg] = params;
if (actions[command]) {
actions[command](arg);
}
}
function send([message]) {
socketInstance.send(message);
}
function create([url]) {
socketInstance = new WebSocket(url);
}
In order to start sending messages, the user should be run:
window.ws.push('create', 'ws://url:port');
window.ws.push('send', 'This is a message');
The problem that I have is the connection is async, and I need to wait until the connection is done to continue to the next command. Is it a good idea to implement an async/await in commandsQueue.forEach or an iterator is a better approach? What other best approaches do you recommend?
The solution that I'm using right now is: I created an empty array of messages at the beginning and then every time that I call the send command I verify if the connection wasn't opened and I added to this array.
Something like that:
const messages = [];
let socketInstance;
let isConnectionOpen = false;
const globalName = "ws";
const globalObject = window[globalName];
const commandsQueue = isArray(globalObject.q) ? globalObject.q : [];
globalObject.q = {
push: executeCommand,
};
commandsQueue.forEach((command) => {
executeCommand(command);
});
function executeCommand(params) {
const actions = {
create,
send,
};
const [command, ...arg] = params;
if (actions[command]) {
actions[command](arg);
}
}
function send([message]) {
if (isConnectionOpen) {
socketInstance.send(message);
} else {
messages.push(message);
}
}
function onOpen() {
isConnectionOpen = true;
messages.forEach((m) => {
send([m]);
});
messages.length = 0;
}
function create([url]) {
socketInstance = new WebSocket(url);
socketInstance.onopen = onOpen;
}
I'm trying to improve a firestore get function, I have something like:
return admin.firestore().collection("submissions").get().then(
async (x) => {
var toRet: any = [];
for (var i = 0; i < 10; i++) {
try {
var hasMedia = x.docs[i].data()['mediaRef'];
if (hasMedia != null) {
var docData = (await x.docs[i].data()) as MediaSubmission;
let submission: MediaSubmission = new MediaSubmission();
submission.author = x.docs[i].data()['author'];
submission.description = x.docs[i].data()['description'];
var mediaRef = await admin.firestore().doc(docData.mediaRef).get();
submission.media = mediaRef.data() as MediaData;
toRet.push(submission);
}
}
catch (e) {
console.log("ERROR GETTIGN MEDIA: " + e);
}
}
return res.status(200).send(toRet);
});
The first get is fine but the performance is worst on the line:
var mediaRef = await admin.firestore().doc(docData.mediaRef).get();
I think this is because the call is not batched.
Would it be possible to do a batch get on an array of mediaRefs to improve performance?
Essentially I have a collection of documents which have foreign references stored by a string pointing to the path in a separate collection and getting those references has been proven to be slow.
What about this? I did some refactoring to use more await/async code, hopefully my comments are helpful.
The main idea is to use Promise.all and await all the mediaRefs retrieval
async function test(req, res) {
// get all docs
const { docs } = await admin
.firestore()
.collection('submissions')
.get();
// get data property only of docs with mediaRef
const datas = await Promise.all(
docs.map(doc => doc.data()).filter(data => data.mediaRef),
);
// get all media in one batch - this is the important change
const mediaRefs = await Promise.all(
datas.map(({ mediaRef }) =>
admin
.firestore()
.doc(mediaRef)
.get(),
),
);
// create return object
const toRet = datas.map((data: MediaSubmission, i) => {
const submission = new MediaSubmission();
submission.author = data.author;
submission.description = data.description;
submission.media = mediaRefs[i].data() as MediaData;
return submission;
});
return res.status(200).send(toRet);
}