Im currently trying to convert an Observable
to a Promise. But when I call that method nothing happens. Im using Angular 6.
public create(form: StoryForm): Promise<void | string> {
const key: string = this.afStore.createId();
return this.auth.authState.pipe(map(res =>
<Story>{
title: form.title, content: form.content, createdAt: new Date(), sid: key,
uid: res.uid, username: res.displayName
}
)).toPromise().then((story: Story) =>
this.afStore.doc(`stories/${key}`).set(story).catch(err => err.message));
}
public save() {
this.triedToSave = true;
if (this.storyForm.valid) {
this.storyService.create(this.storyForm.value)
.then(() => this.router.navigate(['/stories']))
.catch((err: string) => this.notify.danger(err));
}
}
What save should do is to navigate or at least displaying the error.
How authstate is implemented: It returns an observable of some user information. It is implemented in a different service and looks like this:
public get authState(): Observable<firebase.User> {
return this.afAuth.authState;
}
What confuses me is that, if I use a mock Object than it suddenly works:
public create(form: StoryForm) {
const key: string = this.afStore.createId();
return of({uid: 'blubb', displayName: 'kdsjf', photoUrl: 'kjdfkjfd'}).pipe(map(user => {
return {
title: form.title, content: form.content, createdAt: new Date(), sid: key,
uid: user.uid, username: user.displayName, photoUrl: user.photoURL
} as Story;
})).toPromise();
}
But I wonder why toPromise does not work on the example above...
Im guessing that nothing happens because when you trigger the save method, nothing comes out of the authState. Apparently you expect that the authState observable or Subject will always trigger some output, which is only the case in specific cases.
The code below creates a -new- observable that listens to authState.
return this.auth.authState.pipe(map(res =>
<Story>{
title: form.title, content: form.content, createdAt: new Date(), sid: key,
uid: res.uid, username: res.displayName
}
)).toPromise().then((story: Story) =>
this.afStore.doc(`stories/${key}`).set(story).catch(err => err.message));
This code is only triggered by the save method though. My guess is that authState is either an observable, or a subject. You code would only work when the authState is passed a new value -after- the save method is triggered.
Your code that uses the mock object works, because you create an observable that immediately emits that one value.
If authState is a subject: replace it with a ReplaySubject(1)
If it is an observable, you need to publish is as a ReplaySubject like so:
authState.pipe(
publishReplay(1),
refCount()
);
To fully understand what is going on, read this article: https://blog.mindorks.com/understanding-rxjava-subject-publish-replay-behavior-and-async-subject-224d663d452f
Its a java article, but the same principles apply.
But honestly, i cringe when i see people use the toPromise method :) You would learn rxjs much faster if you use it as intended!
If i would write this code, it would look kinda like this:
public save$: Subject<StoryForm> = Subject<StoryForm>();
private destroy$: Subject<any> = new Subject();
ngOnDestroy(): void {
this.destroy$.next();
}
onInit() {
// the (click) eventhandler in your GUI should call save$.next(storyForm)
// this will trigger this statement
this.save$.pipe(
// withLatestFrom will fetch the last value from an observable,
// it still needs to be a ReplaySubject or ReplaySubject for this to work though!
// it will pass an array down the pipe with the storyForm value, and the last value from authState
withLatestFrom(this.auth.authState),
// switchMap does the actual work: note that we do not return a value,
// but an observable that should that return a value soon, that is why we need switchMap!
switchMap(([storyForm, authInfo]) => {
// i am assuming the "set" method returns an observable
// if it returns a Promise, convert it using fromPromise
return this.afStore.doc(`stories/${key}`).set(story).pipe(
// catchError needs to be on your api call
catchError(err => console.log(err))
);
}),
// this will kill your subscriptino when the screen dies
takeUntil(this.destroy$)
).subscribe(value => {
// "value" will be the return value from the "set" call
this.router.navigate(['/stories']);
}
}