Array in Svelte not reactive from Tauri listen(), even with $: - events

I have this code, which displays a list of messages and should add a message that was sent by a Tauri event and update the list:
let messages: Message[] = [];
$: sortedMessages = [...messages].sort((a, b) => a.created - b.created);
.
.
.
async function getMessages() {
const newMessages = <Message[]> await invoke('get_messages', {userId: currentChatInfo?.user.id});
messages = <Message[]>[...messages, ...newMessages];
}
listen('new-message', (event) => {
const message: Message = <Message> event.payload;
if(message.userId === currentChatInfo?.user.id) {
messages = [...messages, message];
}
});
.
.
.
{#await getMessages()}
<div>loading...</div>
{:then sortedMessages}
{#each sortedMessages as message}
<MessageBox {message}/>
{/each}
{/await}
but the update/re-render doesn't happen. And I don't see why. According to the docs and StackOverflow
$: sortedMessages = [...messages].sort((a, b) => a.created - b.created);
should do the trick. But it doesn't :(
I start to suspect it's the {#await} around the {#each}. But I'm not sure how to test that easily. And what to do if that's the case. I don't like the idea of passing the messages as a prop to this component. As I like how self-contained it is.
Edit: Turns out I was partially right with my suspicion, see #Corrl's answer for the details.

{:then sortedMessages}
creates a new variable in the scope of the block with the result of the first getMessages() call
Replace with
{:then _}
so that sortedMessages inside the each block points to the reactive variable

Related

Call a function when Observer subscribes to an RxJS ipe

I have an RxJS observable-producing method that works but it seems a bit hacky and I was wondering if it can be improved with better use of RxJs operators.
What I have implemented is an object cache (in an Angular service) where events are generated when objects are loaded, edited, or changed. The event object looks like this:
export interface DNodeEvent {
type: 'load' | 'unload' | 'update' | 'create' | 'error';
node: DNode;
}
So I have a method that generates an observable for components to use. The caller provides an id/serial number and gets a stream of node objects. My current code looks like this:
obById(id: number): Observable<DNode> {
const unload = { type: 'unload' } as DNode;
const pipeId = this.nodeEvent$.pipe(
filter(ne => ne.node.serial === id &&
(ne.type === 'load' || ne.type === 'update')),
map(ne => ne.node)
);
const pipeLoad = of(id).pipe(
tap(loadTarget => this.loadId(loadTarget)),
switchMap(loadTarget => of(unload)),
filter(dn => dn.type === 'load')
);
return merge(pipeId, pipeLoad);
}
The pipeId observable generates the stream of nodes and I don't see what to improve there.
However the function of the pipeload observable is to call the this.loadId method which triggers the generation of an event out of the nodeEvent$ Subject. If it finds the requested object in the cache it sends an event immediately otherwise it will subscribe to an http observable to fetch it from the server then generate the event.
The only problem I have is that I don't want pipeLoad to emit anything. But I ended up putting the switchMap/filter operators there to close complete the of operator. Also I had to stick the awkward unload constant in there to keep the compiler from complaining about type matching.
Is there a more elegant/robust way of getting this done?
You can put ignoreElements() to mute the stream yet still subscribe to it.
const pipeLoad = of(id).pipe(
tap(loadTarget => this.loadId(loadTarget)),
switchMap(loadTarget => of(unload)),
ignoreElements()
);
I don't understand the purpose of doing this:
const unload = { type: 'unload' } as DNode;
...
const pipeLoad = of(id).pipe(
tap(loadTarget => this.loadId(loadTarget)),
switchMap(loadTarget => of(unload)), <--- ?
filter(dn => dn.type === 'load') <--- ?
);
return merge(pipeId, pipeLoad);
wouldn't it be the same to do this?
obById(id: number): Observable<DNode> {
const pipeId$ = this.nodeEvent$.pipe(
filter(ne => ne.node.serial === id &&
(ne.type === 'load' || ne.type === 'update')),
map(ne => ne.node)
);
this.loadId(id);
return pipeId$;
}
UPDATE:
Using a BehaviorSubject instead of a Subject using the code suggested above may solve the issue. I've tried to reproduce your code in Stackblitz, check it out here.

Invoke method when no observers for RxJs Subject

How to invoke a method when all the observers have unsubscribed from a subject.
Update
const alphaStore = new BehaviourSubject(0);
observer1 = alphaStore.subscribe(console.log);
observer2 = alphaStore.subscribe(console.log);
And when all of these observers unsubscribe. I want a method to be invoked. Like...
Observer1 unsubscribed
Observer2 unsubscribed
All observers left
What you describe already does the finalize() operator. Better said finalize() calls its callback when the chain disposes which means it's called when all observers unsubscribes, the chain completes or errors.
const subject = new Subject();
const shared = subject.pipe(
finalize(() => console.log('finalize')),
share(),
);
https://stackblitz.com/edit/rxjs-rebfba
When all observers unsubscribe share() unsubscribes from its source which triggers finalize().
Currently there's no way to distinguish why finalize() was invoked. See this issue https://github.com/ReactiveX/rxjs/issues/2823 and examples there on how to do it.
You can create a custom Observable, that will track the subscription count.
Heres a simple example:
let count = 0;
const tracked$ = new Observable(() => {
count++;
return ()=>{
count--;
if (count === 0) {
console.log('I am empty');
}
};
})
And then merge it with Observable that does actual work.
For simplicity sake, lets imagine its just a timer
// const tracked$ = ...
const data$ = Observable.timer(0, 5);
const result$ = data$
.merge(tracked$)
.take(5)
.subscribe(value => console.log('v:', value));
After 5 values were emitted -- it will log I am empty.
Heres a live example (with some rewrite and two subscriptions):
https://observable-playground.github.io/gist/4a7415f3528aa125fb686204041138cb
NOTE: this code uses rxjs-compat notation, which is easier to read. Above linked example uses .pipe notation, which is more common now.
Hope this helps.

How to Return From Observable in TypeScript Method with Return Type

I have a Google geocode service in TypeScript. I need this method to return a "GoogleMap" class, after it fetches the lat/long for a given address.
I created a TypeScript method that returns a "GoogleMap" type. But, I'm getting a
function that is neither void nor any must return a value...
Here's my method:
getLatLongFromAddress(streetAddress: string): GoogleMap {
this.geoCodeURL = GOOGLE_GEOCODE_BASE_URL + streetAddress +
"&key=" + GOOGLE_MAPS_API_KEY;
this.googleMap = new GoogleMap();
this.httpService
.get(this.geoCodeURL)
.subscribe((data) => {
this.googleMap.lat = data.results.geometry.location.lat;
this.googleMap.long = data.results.geometry.location.lng;
return this.googleMap;
},
(error) => {
console.error(this.geoCodeURL + ". " + error);
return Observable.throw("System encountered an error: " + error);
},
() => {
console.info("ok: " + this.geoCodeURL);
return this.googleMap;
});
}
I can understand the http call will be async and the flow ought to continue to the bottom of the method, possibly before the response returns data. To return a "GoogleMap", do I need to await this Observable? How do I go about doing this?
Thanks!
UPDATE: 4/21/16
I finally stumbled on an approach that I'm finding some satisfaction. I know there's a number of posts from developers begging for a "real" service. They want to pass a value to the service and get an object back. Instead, many of the answers don't fully solve the problem. The simplistic answer usually includes a subscribe() on the caller's side. The down-side to this pattern, not usually mentioned, is that you're having to map the raw data retrieved in the service in the caller's callback. It might be ok, if you only called this service from this one location in your code. But, ordinarily, you'll be calling the service from different places in your code. So, everytime, you'll map that object again and again in your caller's callback. What if you add a field to the object? Now, you have to hunt for all the callers in your code to update the mapping.
So, here's my approach. We can't get away from subscribe(), and we don't want to. In that vein, our service will return an Observable<T> with an observer that has our precious cargo. From the caller, we'll initialize a variable, Observable<T>, and it will get the service's Observable<T>. Next, we'll subscribe to this object. Finally, you get your "T"! from your service.
Take my example, now modified. Take note of the changes. First, our geocoding service:
getLatLongFromAddress(streetAddress: string): Observable<GoogleMap> {
...
return Observable.create(observer => {
this.httpService
.get(this.geoCodeURL)
.subscribe((data) => {
...
observer.next(this.googleMap);
observer.complete();
}
So, we're wrapping the googleMap object inside the "observer". Let's look at the caller, now:
Add this property:
private _gMapObservable: Observable<GoogleMap>;
Caller:
getLatLongs(streetAddress: string) {
this._gMapObservable = this.geoService.getLatLongFromAddress(this.streetAddr);
this._gMapObservable.subscribe((data)=>{
this.googleMap = data;
});
}
If you notice, there's no mapping in the caller! you just get your object back. All the complex mapping logic is done in the service in one place. So code maintainability is enhanced. Hope this helps.
Your getLatLongFromAddress's signature says it will return a GoogleMap, however, nothing is ever returned (ie the return value of your function, as it stands, will be undefined).
You can get rid of this compilation error by updating your method signature:
// Return type is actually void, because nothing is returned by this function.
getLatLongFromAddress(streetAddress: string): void {
this.geoCodeURL = GOOGLE_GEOCODE_BASE_URL + streetAddress +
"&key=" + GOOGLE_MAPS_API_KEY;
this.httpService
.get(this.geoCodeURL)
.subscribe((data) => {
this.googleMap = new GoogleMap();
this.googleMap.lat = data.results.geometry.location.lat;
this.googleMap.long = data.results.geometry.location.lng;
},
(error) => {
console.error(this.geoCodeURL + ". " + error);
return Observable.throw("System encountered an error: " + error);
},
() => {
console.info("ok: " + this.geoCodeURL);
return this.googleMap;
});
}
Additional tidbit, I don't think the onError and onComplete callback return values are used by Rx (looking at the documentation, the signature for these callbacks has a return value of void), although I could be mistaken.

koa-route Unable to run

Why below code output as 'one', not 'one' 'two'? but using express-route is ok
app.use(route.get('/admin',requiredUser,index));
function *requiredUser(next){
console.log("one"); //required session
yield next;
}
function *index(next) {
console.log("two"); //ok save login
this.body = yield render('admin');
}
koa-route only takes one handler - whatever you give its 2nd argument. That's why it's only executing your first handler.
You can use https://github.com/koajs/compose to combine an array of handlers into one:
var compose = require('koa-compose');
app.use(route.get('/', compose([requiredUser, index])));
Or you can use another routing library like https://github.com/alexmingoia/koa-router which has the behavior that you originally expected from koa-route:
var app = require('koa')();
var router = require('koa-router')();
router.get('/', requiredUser, index);
app.use(router.routes());
app.listen(3000);

ko.validation.group is not working as expected ? Knockout

I been trying to clear display of all errors onload but those errors should display on further action after load .
I got solution for this but i felt it can been done in a better way
Working solution: This will clear onload messages
ko.utils.arrayForEach(self.MAIN(), function (s) {
ko.utils.arrayForEach(s.STData(), function (s1) {
ko.utils.arrayForEach(s1.SData(), function (s2) {
if (s2.Validation.errors().length > 0) {
s2.Validation.errors.showAllMessages(false);
}
});
});
});
Tried usign group but stuck with error i.e
object doesn't support property or method 'ismodified' in knokcout validation .
Not working :
var result= ko.validation.group(self.MAIN(), { deep: true });
if (!self.MAIN().isValid()) { // rightly i am getting isvalid false and also showing my error text under it .
result.showAllMessages(false); // error at this line as metioned above.
return true;
}
Additional code :
function data(){
var inner = this;
self.name=ko.observable(""); // i have many observables
self.validation = ko.validatedObservable([
self.name.extend({required true, //custom validations also written})
]);
}
But i have this function in observable array which is deeper in self.MAIN
My levels : function data object is pushed into self.SData observable array later this one is pushed into self.STData and finally i pushed this one into 'self.MAIN' .
So you can clearly see i am trying to clear messages onLoad but i get that error . Actually in the mean time i went into validation script file i found i am getting error at this line
ko.utils.arrayForEach(validatables(), function (observable) {
observable.isModified(show); // here isModified is undefined
});
Any suggestions are appreciated .

Resources