How to use observable and pipes in a function rxjs? - javascript

Im trying to get this following code to move into a function that's called rather than doing it from a native element click event:
const saveRit$ = Observable.fromEvent(this.saveRitbutton.nativeElement, 'click');
console.log('here', saveRit$);
saveRit$.pipe(
withLatestFrom(this.account$)
).subscribe(([click, { accountId }]) => {
console.log('working');
this.saveRit(accountId).then(() => {
const years = this.ritForm.controls['lineItems'].value.map(row => row.year);
this.emitSuccess(years);
});
});
I have limited knowledge on pipes and observable so any input is amazing. I know I no longer need the saveRit$ variable however I am at a loss as to what to do .pipe() to once it's removed? I just want to be able to call this from some function.

Since the Observable.fromEvent just gives you an Observable we could replace it with any other Observable. As far as I can see from your snippet you want to subscribe on this.account$.
this.account$.subscribe(account => {
console.log('working');
this.saveRit(account.accountId).then(() => {
const years = this.ritForm.controls['lineItems'].value.map(row => row.year);
this.emitSuccess(years);
});
});
Good luck!

Related

Returning a JSX component from an event listener

This is more for research than a real use case. But I'm interested if something like this is even possible.
I would like to return an event listener as a component, where the content is updated based on the data that is passed.
At the moment I can return the callback from the listener, but the values never update, I wonder if it's possible to compose this function/component in a way that would update based on this param that is sent?
Example:
const ChooseRating = () => {
return (
<button class="choose-rating" onClick={() => Emitter.send('rating', 4)}>
Choose rating
</button>
)
}
const ShowRating = () => {
return Emitter.listen('rating', (rating) => (<div>{rating}</div>))
}
ps: I'm using Solid.js for this at the moment, but if you know how to do this in React or any other JSX based thing let me know!

RXJS: How do I add new values to an ovservable after creation?

Im working through the basics of observables [here][1]
The examples here show Observasbles as functions with multiple returns e.g.
import { Observable } from 'rxjs';
const foo = new Observable(subscriber => {
console.log('Hello');
subscriber.next(42);
subscriber.next(100); // "return" another value
subscriber.next(200); // "return" yet another
});
foo.subscribe(x => {
console.log(x);
});
// outputs 42, 100, 200
Question: Is it possible to add new values to an observable AFTER it has been created e.g. something like this pseudo code:
import { Observable } from 'rxjs';
const foo = new Observable(subscriber => {
subscriber.next(200);
});
// this is pseudocode so wont work, but is the basis of this post.
...
foo.next(400);
...
foo.subscribe(x => {
console.log(x);
});
// is it possible to output 200 and 400 by calling next after instantiation?
Or do I need to use a Subject to do that?
You have to use Subject for this. According to documentation
Every Subject is an Observer. It is an object with the methods
next(v), error(e), and complete(). To feed a new value to the Subject,
just call next(theValue), and it will be multicasted to the Observers
registered to listen to the Subject.
Observables do not have the next method so you can not pass values to them.
You need to use Subject or BehaviorSubject. But BehaviorSubject has an initial value, so you can try to do something like this:
var obs = new rxjs.Observable((s) => {
s.next(42);
s.next(100);
});
obs.subscribe(a => console.log(a));
var sub = new rxjs.BehaviorSubject(45);
obs.subscribe(sub);
sub.next('new value');
sub.subscribe(a => console.log(a));
Every Subject is Observable, so you can easily convert Subject to Observable via yourSubject.asObservable().
NOTE: Don't forget to unsubscribe from observable in order to prevent memory leaks.

How to correctly add View value in Module via Controller?

Actually the whole question in the title, I have a button written in HTML, let's say this is my View, which returns a number:
export class View {
addItem() {
let plus = document.querySelector('.plus');
plus.addEventListener('click', () => {
let num = document.querySelector('.count').innerHTML;
return num;
})
}
}
Here is my Module with addNum function, which actually should add a number to the array:
export class Module {
constructor() {
this.num = [];
}
addNum(num){
this.num.push(num);
}
}
Heres the Controller:
class Controller {
constructor(view, module){
this.view = view;
this.module = module;
}
getNum(){
this.cart.addNum(this.view.addItem());
}
}
The problem is that when I call the getNum controller function, it works instantly, how can I wait for an event?
This can be naturally handled with RxJS observables. They are well-suited for such purposes, i.e. building reactive UIs and extensively used in Angular.
An observable is basically a stream of values that can be transformed and in the end, subscribed. It has a lot in common with promises which were suggested in another answer, but an observable results in a sequence of values, while a promise results in one value.
RxJS contains extensive functionality, including the support for DOM events. RxJS fromEvent (see a short tutorial) replaces addEventListener and creates a stream of click events that can be mapped to another value (form input value):
addItem() {
let plus = document.querySelector('.plus');
return Observable.fromEvent(plus, 'click')
.map((event) => {
return document.querySelector('.count').innerHTML;
});
}
An observable is subscribed in place where values should be received:
getNum(){
this.numSubscription = this.view.addItem().subscribe(num => {
this.module.addNum(num);
});
}
A stream of values can be stopped by unsubscribing from an observable, this.numSubscription.unsubscribe(). This will internally trigger removeEventListener.
Here's a demo.

Javascript/Typescript 'this' scope

I am working with Ionic2 and Meteor. I do however have a Javascript/Typescript issue relating to the scope of the this object.
I have read that I should use bind when I don't have handle on this at the appropriate level.
I probably don't understand the concept, because I try the following, but get an error trying to call a function.
this.subscribe('messages', this.activeChat._id, this.senderId, () => {
this.autorun(() => {
let promiseMessages: Promise<Mongo.Collection<Message>> = this.findMessages();
promiseMessages.then((messageData: Mongo.Collection<Message>) => {
messageData.find().forEach(function (message: Message) {
setLocalMessage.bind(message);
});
});
});
and
private setLocalMessage(message: Message): void {
this.localMessageCollection.insert(message);
}
I get the following error when I try build the app:
ERROR in ./app/pages/messages/messages.ts
(72,19): error TS2304: Cannot find name 'setLocalMessage'.
UPDATE
Thank you for the advise below.
I am now using the following, and it works.
let promiseMessages: Promise<Mongo.Collection<Message>> = this.findMessages();
promiseMessages.then((messageData: Mongo.Collection<Message>) => {
messageData.find().forEach((message: Message) => {
this.setLocalMessage(message);
});
});
I have read that I should use bind when I don't have handle on this at the appropriate level.
That's a bit outdated now, better have a look at How to access the correct `this` context inside a callback? these days which also shows you how to use arrow functions.
You're getting the error message because setLocalMessage is not a variable but still a property of this so you have to access it as such. There are basically three solutions in your case:
bind
messageData.find().forEach(this.setLocalMessage.bind(this));
the context argument of forEach (assuming it's the Array method):
messageData.find().forEach(this.setLocalMessage, this);
another arrow function:
messageData.find().forEach((message: Message) => {
this.setLocalMessage(message);
});
There are a few things wrong here.
In ES6 (and thus TypeScript), you need to refer to instance members using explicit this, such as this.setLocalMessage. Just writing setLocalMessage is invalid no matter where the code is.
Inside a function, the this object will probably not be what you expect anyway. You need to capture the this object from outside the function and put it in a variable, like so:
this.subscribe('messages', this.activeChat._id, this.senderId, () => {
this.autorun(() => {
let self = this;
let promiseMessages: Promise<Mongo.Collection<Message>> = this.findMessages();
promiseMessages.then((messageData: Mongo.Collection<Message>) => {
messageData.find().forEach(function (message: Message) {
self.setLocalMessage(message);
});
});
});
Alternatively, you can use an arrow expression, in which this is the same as what it is in the code around it:
this.subscribe('messages', this.activeChat._id, this.senderId, () => {
this.autorun(() => {
let promiseMessages: Promise<Mongo.Collection<Message>> = this.findMessages();
promiseMessages.then((messageData: Mongo.Collection<Message>) => {
messageData.find().forEach(message => this.setLocalMessage(message));
});
});
});
It's not an issue of TypeScript itself. Without it, the code will just fail at runtime.

How to unsubscribe from a socket.io subscription?

Suppose there are objects making subscriptions to a socket server like so:
socket.on('news', obj.socketEvent)
These objects have a short life span and are frequently created, generating many subscriptions. This seems like a memory leak and an error prone situation which would intuitively be prevented this way:
socket.off('news', obj.socketEvent)
before the object is deleted, but alas, there isn't an off method in the socket. Is there another method meant for this?
Edit: having found no answer I'm assigning a blank method to overwrite the wrapper method for the original event handler, an example follows.
var _blank = function(){};
var cbProxy = function(){
obj.socketEvent.apply(obj, arguments)
};
var cbProxyProxy = function(){
cbProxy.apply ({}, arguments)
}
socket.on('news', cbProxyProxy);
// ...and to unsubscribe
cbProxy = _blank;
From looking at the source of socket.io.js (couldn't find it in documentation anywhere), I found these two functions:
removeListener = function(name, fn)
removeAllListeners = function(name)
I used removeAllListeners successfully in my app; you should be able to choose from these:
socket.removeListener("news", cbProxy);
socket.removeAllListeners("news");
Also, I don't think your solution of cbProxy = _blank would actually work; that would only affect the cbProxy variable, not any actual socket.io event.
If you want to create listeners that "listens" only once use socket.once('news',func). Socket.io automatically will distroy the listener after the event happened - it's called "volatile listener".
Looking at the code of current version of Socket.io Client (1.4.8) it seems that off, removeAllListeners, removeEventListener are all pointing to the same function.
Calling any of those, providing event name and/or callback, gives the desired result. Not providing anything at all seems to reset everything.
Please do be cautious about the fn/callback argument. It has to be the same instance used in the code.
Example:
var eventCallback = function(data) {
// do something nice
};
socket.off('eventName', eventCallback);
Would work as expected.
Example (will also work):
function eventCallback(data) {
// do something nice
}
socket.off('eventName', eventCallback);
Please be cautious that the callback you are trying to remove is the one that you passed in (this one can bring a lot of confusion and frustration).
This example implements a wrapper around initial callback, trying to remove that would not work as the real callback being added is an undisclosed closure instance: http://www.html5rocks.com/en/tutorials/frameworks/angular-websockets/
Here is the link to that specific line in the codebase: https://github.com/socketio/socket.io-client/blob/master/socket.io.js#L1597
Socket.io version 0.9.16 implements removeListener but not off.
You can use removeListener instead of off when unsubscribing, or simply implement off as follows:
var socket = io.connect(url);
socket.off = socket.removeListener;
If you are using the Backbone listenTo event subscription approach, you'll need to implement the above as Backbone calls off when unsubscribing events.
I found that in socket.io 0.9.11 and Chrome24 socket.io removeListener doesn't work.
this modified version works for me:
EventEmitter.prototype.removeListener = function (name, fn) {
if (this.$events && this.$events[name]) {
var list = this.$events[name];
if (io.util.isArray(list)) {
var pos = -1;
for (var i = 0, l = list.length; i < l; i++) {
if (list[i].toString() === fn.toString() || (list[i].listener && list[i].listener === fn)) {
pos = i;
break;
}
}
if (pos < 0) {
return this;
}
list.splice(pos, 1);
if (!list.length) {
delete this.$events[name];
}
} else {
if (list.toString() === fn.toString() || (list.listener && list.listener === fn)) {
delete this.$events[name];
}
}
}
return this;
};
Since I had a spot of troubles making this work figured I'd chime in here as well, along with a nice updated answer for 2017. Thanks to #Pjotr for pointing out that it has to be the same callback instance.
Example with Angular2 TypeScript in a socket-io.subscriber service. Note the "newCallback" wrapper
private subscriptions: Array<{
key: string,
callback: Function
}>;
constructor() {
this.subscriptions = [];
}
subscribe(key: string, callback: Function) {
let newCallback = (response) => callback(response);
this.socket.on(key, newCallback);
return this.subscriptions.push({key: key, callback: newCallback}) - 1;
}
unsubscribe(i: number) {
this.socket.removeListener(this.subscriptions[i].key, this.subscriptions[i].callback);
}
Removing an event listener on the client
var Socket = io.connect();
Socket.removeListener('test', test);
Also on java client, it can be done the same way with the Javascript client. I've pasted from socket.io.
// remove all listeners of the connect event
socket.off(Socket.EVENT_CONNECT);
listener = new Emitter.Listener() { ... };
socket.on(Socket.EVENT_CONNECT, listener);
// remove the specified listener
socket.off(Socket.EVENT_CONNECT, listener);
Pre-store the events using an array, and by the time you need to unsubscribe them, use the off method, which is a built in method from socket.io:
// init
var events = []
// store
events.push("eventName")
// subscribe
socket.on("eventName", cb)
// remove
events = events.filter(event => event!="eventName")
// unsubscribe
socket.off("eventName")
To add to #Andrew Magee, here is an example of unsubscribing socket.io events in Angular JS, and of course works with Vanilla JS:
function handleCarStarted ( data ) { // Do stuff }
function handleCarStopped ( data ) { // Do stuff }
Listen for events:
var io = $window.io(); // Probably put this in a factory, not controller instantiation
io.on('car.started', handleCarStarted);
io.on('car.stopped', handleCarStopped);
$scope.$on('$destroy', function () {
io.removeListener('car.started', handleCarStarted);
io.removeListener('car.stopped', handleCarStopped);
});
This has helped me in both Angular 8 and React 16.8:
receiveMessage() {
let newCallback = (data) => {
this.eventEmitter.emit('add-message-response', data);
};
this.socket.on('add-message-response', newCallback);
this.subscriptions.push({key: 'add-message-response', callback: newCallback});
}
receiveMessageRemoveSocketListener() {
this.findAndRemoveSocketEventListener('add-message-response');
}
findAndRemoveSocketEventListener (eventKey) {
let foundListener = this.subscriptions.find( (subscription) => subscription.key === eventKey );
if(!foundListener) {
return;
}
this.socket.removeListener(foundListener.key, foundListener.callback);
this.subscriptions = this.subscriptions.filter( (subscription) => subscription.key !== eventKey );
}
Reason for using an Array of Subscriptions is that when you Subscribe to an event multiple times and you don't remove an unsubscribed subscription from the Subscription list you will most probably be right at first time you remove the subscription from the list, but later subscriptions will not be removed as you will be finding first instance only every time you unsubscribe the event.
You can simply call receiveMessage(); to subscribe to an the event and receiveMessageRemoveSocketListener(); to Unsubscribe.

Categories

Resources