I need that createUser function returns Observable<UserEntity> but in this function I also have to make 2 queries to DB and check if this user exists. The code below uses async/await and looks pretty good and clean. But the problem is that I use rxjs everywhere in this project and would like to write it somehow using rxjs. Can it be as clean as now but with Observables?
async create(user: CreateUserDTO): Promise<UserEntity> {
const userByEmail = await this.getUserByEmail();
const userByLogin = await this.getUserByLogin();
if (userByLogin || userByEmail)
// retrun error here
return await this.createUser(user);
}
I am using RxJs 6.5
forkJoin will emit result when both async functions getUserByEmail & getUserByLogin complete their execution
If getUserByEmail & getUserByLogin returns Promise,for that using from to convert a promise into an observable
mergeMap to subscribe the inner observable.In our case createUser returns observable
create(user: CreateUserDTO): Observable < UserEntity > {
//If getUserByEmail & getUserByLogin returs Promise
const getUserByEmail$ = from(this.getUserByEmail());
const getUserByLogin$ = from(this.getUserByLogin());
//If Both returns Observable
//const getUserByEmail$ = this.getUserByEmail();
//const getUserByLogin$ = this.getUserByLogin();
return forkJoin({
userByEmail: this.getUserByEmail(),
userByLogin: this.getUserByLogin(),
}).pipe(
tap((res) => {
if (res.userByEmail || res.userByLogin) {
throw 'User exists!';
}
}),
mergeMap(() => {
return from(this.createUser(user));
//If createUser returns Observable,then
//return this.createUser(user);
})
);
}
Assuming that this.getUserByEmail(), this.getUserByLogin() and this.createUser(user) return Promises, the code could look like this
create(user: CreateUserDTO): Observable<UserEntity> {
// with the rxjs from function we turn a Promise into an Observable
const userByEmail$ = from(this.getUserByEmail());
const userByLogin$ = from(this.getUserByLogin());
// with forkjoin we create an Observable which notifies when all the
// Observables which have been passed in as parameters notify
return forkJoin([userByEmail$, userByLogin$]).pipe(
// with concatMap you wait for the upstream Observable (i.e. the
// Observable created by forkJoin) to notify and complete, and then
// you return the next Observable in the chain, which is, in this case,
// the Observable which (when subscribed) creates the user
concatMap(([userByLogin, userByEmail]) =>
if (userByLogin || userByEmail) {
// throw error here
}
return from(this.createUser(user))
})
)
}
Otherwise, if this.getUserByEmail(), this.getUserByLogin() and this.createUser(user) return Observables you do not need to use the from rxjs function and the code would be slightly simpler, like this
create(user: CreateUserDTO): Observable<UserEntity> {
return forkJoin([this.getUserByEmail(), this.getUserByLogin()]).pipe(
concatMap(([userByLogin, userByEmail]) =>
if (userByLogin || userByEmail) {
// throw error here
}
return from(this.createUser(user))
})
)
}
Related
When I press a key on a form input for some reason the async validator doesn't detect the distinctUntilChanged and it still sends API requests.
for example if I press 35, delete 5 and after that add 5 again it still sends the request.
this is the code:
(I've tried pretty much everything but still doesn't work)
validateSomeNumber(control: FormControl): Observable<any> | Promise <any> {
this.isSubmitBtnDisabled = true;
return control.valueChanges.pipe(
debounceTime(1000),
distinctUntilChanged(),
switchMap((value) => {
return this.apiService.someApiRequest({ 'to_number': control.value }).pipe(
map(res => {
console.log(res);
if (res.success) {
// console.log(res);
this.isSubmitBtnDisabled = false;
return null;
} else {
// console.log(res);
this.isSubmitBtnDisabled = true;
return{ 'invalidCharacters': true };
}
}),
);
}),
first()
);
}
By default validateSomeNumber is called after every value change.
If you return this on every value change
return control.valueChanges.pipe(
debounceTime(1000),
distinctUntilChanged(),
...
)
you're creating a new Observable of value changes on every value change. e.g if you type four characters you end up with four independent Observables each emitting one character and not with one Observable emitting four times. So debounceTime and distinctUntilChanged will only effecting the Observable you create on a particular value change but not the value change process as a whole. If they only effect an Observable that emits once they obviously don't work as you intend them to do.
You should return the http request directly
validateSomeNumber(control: FormControl): Observable<any> | Promise <any> {
this.isSubmitBtnDisabled = true;
return this.apiService.someApiRequest({ 'to_number': control.value }).pipe(
map(..),
);
}
Limiting the request frequency
Option 1: updateOn
To prevent the http request from being executed on every value change Angular recommends changing the updateOn property to submit or blur.
With template-driven forms:
<input [(ngModel)]="name" [ngModelOptions]="{updateOn: 'blur'}">
With reactive forms:
new FormControl('', {updateOn: 'blur'});
{updateOn: 'blur'} will only execute the validators when your input looses focus.
Option 2: Emulate debounceTime and distinctUntilChanged
Angular automatically unsubscribes from the previous Observable returned by the AsyncValidator if the form value changes. This allows you to emulate debounceTime with timer. To emulate distinctUntilChanged you can keep track of the last request term and do the equality check yourself.
private lastRequestTerm = null;
validateSomeNumber(control: FormControl): Observable<any> | Promise <any> {
this.isSubmitBtnDisabled = true;
// emulate debounceTime
return timer(1000).pipe(
// emulate distinceUntilChanged
filter(_ => control.value != this.lastRequestTerm),
switchMap(() => {
this.lastSearchTerm = control.value;
return this.apiService.someApiRequest({ 'to_number': control.value });
}),
map(..)
);
}
I'm implementing an interface that has a function that returns Observable.
I also need to pass some value to the Observable, but it may take some time to receive that value.
How can I still return the Observable and also make it wait for the needed value?
To be more specific, I'm implementing an HttpInterceptor and I want to set a token to the request header.
The token value could be unavailable, so need to wait a little (asynchronously) and try again, until the value is received.
Then set the token in the request header and continue.
How can I implement such mechanism?
#Injectable()
export class HttpXsrfInterceptor implements HttpInterceptor {
constructor(private tokenService: HttpXsrfTokenExtractor) { }
getToken(callback) {
let token = this.tokenService.getToken();
if (!token) {
// a valid token wasn't received. wait a little and try again
setTimeout(() => {
this.getToken(callback); //recursive call
}, 1000);
} else {
// found valid token
callback(token);
}
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// this part should set req when a token is received, but it is asynchronous
this.getToken((token) => {
req = req.clone({headers: req.headers.set('X-XSRF-TOKEN', token)});
});
// this returns Observable. I must return Observable, but req is not ready at this point
return next.handle(req);
}
}
The easiest thing to do is use RxJs operators. Using switchMap should be a good solution here. Essentially in this case, switchMap allows you to chain dependent observables together and only return the inner observable. It should look something like this:
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return getToken.pipe(
switchMap(token => {
req = req.clone({headers: req.headers.set('X-XSRF-TOKEN', token)});
return next.handle(req);
}
);
}
Please note, You'll need to adjust your getToken to return an observable as well in order for this to work.
Looking at the code, it seems that we have callback from getToken and observable from intercept. It is better to always use observable if possible.
We could convert getToken(callback) to use observable. RxJS has retryWhen operator that we could use to handle retry.
getToken() {
const tokenFromService = of(this.tokenService.getToken()); // convert to observable
return tokenFromService
.pipe(
map(token => {
if (!token) {
throw new Error('token is not specified'); // it will be caught by retryWhen
}
return token;
}),
retryWhen(error => {
return error
.pipe(
tap(() => console.log('error happened, retry request token')),
delay(1000)
)
})
)
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return this.getToken()
.pipe(
switchMap(token => {
const modifiedReq = req.clone({headers: req.headers.set('X-XSRF-TOKEN', token)});
return next.handle(modifiedReq);
})
)
}
Hope it helps
Reference:
retryWhen
I'm trying to create an observable with behavior similar to what is returned by defer, but using create method. So I try:
const obs = Observable.create(function(observer) {
from(fetch('https://jsonplaceholder.typicode.com/todos/1').then(console.log('fetch done'))).subscribe(observer)
})
setTimeout(()=>obs.subscribe((resp)=>console.log(resp.statusText)), 5000)
But when I run it in node It just prints "fetch done" (on subscription, after 5 seconds as expected) but waits forever there.
When I wrap the from(..) in setImmediate() or setTimeout(,0), then sometimes it prints both messages ("Fetch done", "OK") and exits and sometimes it only prints "fetch done" and waits forever.
code:
const obs = Observable.create(function(observer) {
setTimeout(()=>from(fetch('https://jsonplaceholder.typicode.com/todos/1').then(console.log('fetch done'))).subscribe(observer), 0)
})
setTimeout(()=>obs.subscribe((resp)=>console.log(resp.statusText)), 5000)
Why this happens? What am I doing wrong?
First thing is that Observable.create is deprecated and you should use just new Observable():
When creating Observables like this you have access to observer object where you can call next() and complete() so in your case it would be like this:
new Observable(observer => {
fetch('https://jsonplaceholder.typicode.com/todos/1').then(response => {
observer.next(response);
observer.complete();
});
});
I think this should work as well:
new Observable(observer => {
const sub = from(fetch('https://jsonplaceholder.typicode.com/todos/1')).subscribe(observer);
// Return tear-down function so you can abort request.
return () => sub.unsubscribe();
});
Obviously, this is over complicated and if you just want to wrap a Promise with Observable you can use just from().
Promise.then takes a callback function, and you need to return the argument to chain it, so it becomes then(result => { console.log('fetch done', result); return result; })
I'm trying to create actions from updates from a RX Subject
It's working but I get the error below.
Here is my Epic
export function uploadSceneFile(action$, store) {
return action$.ofType(CREATE_SCENE_SUCCESS)
.mergeMap(({payload}) =>
UploadSceneWithFile(payload)
.subscribe(res => {
if (res.progress > 0)
store.dispatch(uploadSceneProgress(res))
else if(res.progress === -1){
store.dispatch(uploadSceneSuccess(res))
requestSceneProcessing(res).map(res => {
})
}
})
)
}
And here is the Subject
export function UploadSceneWithFile(scene){
const subject$ = new Subject()
const uploader = new S3Upload({
getSignedUrl: getSignedUrl,
uploadRequestHeaders: {'x-amz-acl': 'public-read'},
contentType: scene.file.type,
contentDisposition: 'auto',
s3path: 'assets/',
onError:()=>subject$.next('error'),
onProgress: (val)=> subject$.next({...scene,progress:val}),
onFinishS3Put: ()=>subject$.next({...scene,progress:-1}),
})
uploader.uploadFile(scene.file)
return subject$
}
I read from a previous post that I'm supposed to be using .map, not .subscribe but nothing happens if I don't subscribe (the upload doesn't happen)
What's the best way of doing this?
subscribeToResult.js:74 Uncaught TypeError: You provided an invalid object where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
at Object.subscribeToResult (subscribeToResult.js:74)
at MergeMapSubscriber../node_modules/rxjs/operators/mergeMap.js.MergeMapSubscriber._innerSub (mergeMap.js:132)
at MergeMapSubscriber../node_modules/rxjs/operators/mergeMap.js.MergeMapSubscriber._tryNext (mergeMap.js:129)
at MergeMapSubscriber../node_modules/rxjs/operators/mergeMap.js.MergeMapSubscriber._next (mergeMap.js:112)
at MergeMapSubscriber../node_modules/rxjs/Subscriber.js.Subscriber.next (Subscriber.js:89)
at FilterSubscriber../node_modules/rxjs/operators/filter.js.FilterSubscriber._next (filter.js:89)
at FilterSubscriber../node_modules/rxjs/Subscriber.js.Subscriber.next (Subscriber.js:89)
at Subject../node_modules/rxjs/Subject.js.Subject.next (Subject.js:55)
at createEpicMiddleware.js:60
at createEpicMiddleware.js:59
at SafeSubscriber.dispatch [as _next] (applyMiddleware.js:35)
at
The problem is that you subscribe inside mergeMap and return a Subscription which is invalid. The callback needs to return only Observable, Promise, Array, or Iterable.
I'm not sure what exactly you need to do but if you need to perform some side-effects you can use do() operator instead of subscribing.
export function uploadSceneFile(action$, store) {
return action$.ofType(CREATE_SCENE_SUCCESS)
.mergeMap(({ payload }) => UploadSceneWithFile(payload)
.do(res => {
...
})
)
}
Or it looks like you could put do after mergeMap as well:
export function uploadSceneFile(action$, store) {
return action$.ofType(CREATE_SCENE_SUCCESS)
.mergeMap(({ payload }) => UploadSceneWithFile(payload))
.do(res => {
...
});
}
I'm using the WebRTC library which has a very specific API. The peerConnection.setRemoteDescription method's 2nd argument is supposed to be a callback for when it finishes setting the remote description:
This is one of my wrapper functions for my WebRTC class:
export function setRemoteSdp(peerConnection, sdp, callback) {
if (!sdp) return;
return peerConnection.setRemoteDescription(
new RTCSessionDescription(sdp),
callback, // <-------------
);
}
And this is a sketch of what I want to do:
function receivedSdp(action$, store) {
return action$.ofType(VideoStream.RECEIVED_SDP)
.mergeMap(action => {
const {peerConnection} = store.getState().videoStreams;
const {sdp} = action.payload;
return WebRTC.setRemoteSdp(peerConnection, sdp, () => {
return myReducer.myAction(); // <------ return action as the callback
})
})
};
This doesn't work since I'm not returning an Observable. Is there a way to do this?
P.S. this is the WebRTC API: https://github.com/oney/react-native-webrtc/blob/master/RTCPeerConnection.js#L176
martin's answer is correct about using Observable.create or new Observable--same thing (except it's not clear to me why you need the mergeAll() since the mergeMap will flatten?)
As a bonus, you could also use Observable.bindCallback for this.
// bindCallback is a factory factory, it creates a function that
// when called with any arguments will return an Observable that
// wraps setRemoteSdp, handling the callback portion for you.
// I'm using setRemoteSdp.bind(WebRTC) because I don't know
// if setRemoteSdp requires its calling context to be WebRTC
// so it's "just in case". It might not be needed.
const setRemoteSdpObservable = Observable.bindCallback(WebRTC.setRemoteSdp.bind(WebRTC));
setRemoteSdpObservable(peerConnection, sdp)
.subscribe(d => console.log(d));
Usage inside your epic would be something like this
// observables are lazy, so defining this outside of our epic
// is totally cool--it only sets up the factory
const setRemoteSdpObservable = Observable.bindCallback(WebRTC.setRemoteSdp.bind(WebRTC));
function receivedSdp(action$, store) {
return action$.ofType(VideoStream.RECEIVED_SDP)
.mergeMap(action => {
const {peerConnection} = store.getState().videoStreams;
const {sdp} = action.payload;
return setRemoteSdpObservable(peerConnection)
.map(result => myReducer.myAction());
})
};
You could use this to create Observable wrappers for all the WebRTC apis.
So the problem is that setRemoteSdp doesn't return an Observable while myReducer.myAction() does and that's the Observable you want to merge?
You can use Observable.create and wrap the WebRTC.setRemoteSdp call:
.mergeMap(action => {
return Observable.create(observer => {
WebRTC.setRemoteSdp(peerConnection, sdp, () => {
observer.next(myReducer.myAction());
observer.complete();
})
});
}
.mergeAll()
The Observable.create returns an Observable that emits another Observable from myReducer.myAction(). Now I have in fact so-called higher-order that I want to flatten using mergeAll() (concatAll would work as well).