How to use fromEvent instead of bindCallback on an object method? - javascript

I need to connect rn-fetch-blob's onData callback method to an observable.
As far as I know, this is not an event, fromEventPattern couldn't be used.
I can't see how to use create if this is the solution to my problem.
I have found bindCallback that looked promising, but the docs says that I probably should use fromEvent instead:
Note that the Observable created by the output function will always emit a single value and then complete immediately. If func calls the callback multiple times, values from subsequent calls will not appear in the stream. If you need to listen for multiple calls, you probably want to use fromEvent or fromEventPattern instead.
I need to listen to multiple calls indeed.
Anyway I'm trying to use bindCallback on an object method as shown in the doc.
In my Typescript file:
import { bindCallback } from 'rxjs';
In my class:
private emitter!: Observable<any>;
in a private method:
RNFetchBlob.fs
.readStream(
filePath,
"utf8",
-1,
10
)
.then(ifstream => {
ifstream.open();
this.emitter = bindCallback(ifstream.onData);
but it fails to compile:
error TS2322: Type '() => Observable<string | number[]>' is not assignable to type 'Observable<any>'.
Property '_isScalar' is missing in type '() => Observable<string | number[]>'.
I really can't see how to use fromEvent in my case.
Any help appreciated.
EDIT: Added working code for those looking for the answer:
RNFetchBlob.fs
.readStream(
// file path
peripheral.name,
// encoding, should be one of `base64`, `utf8`, `ascii`
"utf8",
// (optional) buffer size, default to 4096 (4095 for BASE64 encoded data)
// when reading file in BASE64 encoding, buffer size must be multiples of 3.
-1,
10
)
.then(ifstream => {
ifstream.open();
this.emitter = new Observable(subscriber => {
ifstream.onData(chunk => {
// chunk will be a string when encoding is 'utf8'
logging.logWithTimestamp(`Received [${chunk}]`);
subscriber.next(chunk);
});
ifstream.onError(err => {
logging.logWithTimestamp(`oops [${err}]`);
subscriber.error(err);
});
ifstream.onEnd(() => {
subscriber.complete();
});
});
this.rxSubscription = this.emitter
.pipe(
concatMap(value =>
this.handleUpdatedValuesComingFromCSVFile(value)
)
)
.subscribe();

Not too familiar with rn-fetch-blob but hope you get the idea, also you return an function to run clean up logic.
const onDataObserevable=new Obserevable(obs=>{
ifstream.onData(data=>obs.next(data))
return ()=>{... you can add some clean up logic here like unsubcribe from source onData event}
});
UPDATE
converted the whole chain to observable, hope you get the idea
from(RNFetchBlob.fs.readStream(
peripheral.name,
"utf8",
-1,
10
))
.pipe(mergeMap(ifstream => {
ifstream.open();
return new Observable(subscriber => {
ifstream.onData(chunk => {
// chunk will be a string when encoding is 'utf8'
logging.logWithTimestamp(`Received [${chunk}]`);
subscriber.next(chunk);
});
ifstream.onError(err => {
logging.logWithTimestamp(`oops [${err}]`);
subscriber.error(err);
});
ifstream.onEnd(() => {
subscriber.complete();
});
});),
mergeMap(value =>this.handleUpdatedValuesComingFromCSVFile(value)
)
)

Here is how to do it with fromEventPattern:
this.emitter = fromEventPattern(
// tell fromEventPattern how to subscribe to the data
handler => ifstream.onData(handler),
// tell fromEventPattern how to unsubscribe from the data
handler => ifstream.onData(null)
)

Related

Use data from one observable in another and then return result as other observable

I am solving this issue:
The application flow:
I have to call the first API endpoint (let's call it EP-A for simplicity) which takes Blob as body and fileType as a request parameter. Its performed via calling automatically generated class
uploadFile$Response(params?: {
fileType?: 'USER_AVATAR' | 'UNKNOWN' | 'DELIVERY_LOGO' | 'PAYMENT_LOGO' | 'ITEM_PICTURE';
body?: { 'file'?: Blob }
}): Observable<StrictHttpResponse<FileUploadResponse>> {
const rb = new RequestBuilder(this.rootUrl, FileControllerService.UploadFilePath, 'post');
if (params) {
rb.query('fileType', params.fileType, {});
rb.body(params.body, 'application/json');
}
return this.http.request(rb.build({
responseType: 'blob',
accept: '*/*'
})).pipe(
filter((r: any) => r instanceof HttpResponse),
map((r: HttpResponse<any>) => {
return r as StrictHttpResponse<FileUploadResponse>;
})
);
}
The StrictHttpResponse<T> is simply an interface holding a "generic" body (so you can retrieve data that will have a structure defined by swagger from which this method is generated).
Then the result FileUploadResponse which is an object like
{
uuid: string,
time: Timestamp
...
Other members omitted for simplicity
...
}
is sent to another EP (let's call it EP-B) right after EP-A call returns a value, EP-B takes an object below as a body and currently logged person as a path variable.
{
uuid: string
}
So before calling EP-B the result from EP-A should be parsed (in this case, the uuid field should be taken and put into a new object for EP-B calling)
Again via the generated method with a similar signature as the one above (and I will omit it for simplicity).
If everything performed well, I´d like to let the caller know about that. If anything failed (any of these 2 EP calls), I´d like to let it know to call of this method to react somehow (show alert, change page somehow, ...)
The method I have is now incomplete, I do not know how to "connect" these 2 Observables, I´ve read about mergeMap, flatMap, etc. but I am not sure how to use it in my case.
updateUserAvatar(avatar: Blob): Observable<boolean> {
return new Observable<boolean>((observer) => {
// Calling EP-A
this.avatarFormChangeRequestSubscription = this.fileControllerService.uploadFile$Response({
fileType: 'USER_AVATAR',
body: {
file: avatar
}
})
.subscribe((response: StrictHttpResponse<FileUploadResponse>) => {
// Handle returned UUID and somehow pass it into an observable belog
console.log(response);
},
(error: any) => {
observer.error(error);
});
// Creating object for EP-B calling
const avatarUpdateParams = {
id: 1, // Just dummy ID for now, will be dynamically changed
body: {
avatarUUID: '' // the UUID from observable response above should be placed here
}
};
// Calling EP-B
this.avatarFormChangeRequestSubscription = this.userControllerService.updateUserAvatar$Response(avatarUpdateParams)
.subscribe((response: StrictHttpResponse<string>) => {
// Handle successfull avatar upload (change the "Logged user" object avatar to change it everywhere etc
console.log(response);
observer.next(true);
},
(error: any) => {
observer.error(error);
});
});
}
At the end I would like to add "use case" flow too to understand what I am trying to achieve from user view:
User uploads his photo which is firstly uploaded into a file system (and linked with database record) on BE side, then this file is linked to his profile as his profile picture.
You could do it using rxjs. Something like that might works :
this.fileControllerService.uploadFile$Response({
fileType: 'USER_AVATAR',
body: {
file: avatar,
},
})
.pipe(
tap((responseOfFirstApiCall: StrictHttpResponse<FileUploadResponse>) => {
// Do whatever you want here, but you might not need that since you get the response below as well (in the flatMap)
// Handle returned UUID and somehow pass it into an observable belog
console.log(response);
}),
flatMap(
(responseOfFirstApiCall: StrictHttpResponse<FileUploadResponse>) => {
// Creating object for EP-B calling
const avatarUpdateParams = {
id: 1, // Just dummy ID for now, will be dynamically changed
body: {
avatarUUID: '', // the UUID from observable response above should be placed here
},
};
return this.userControllerService.updateUserAvatar$Response(avatarUpdateParams);
}
),
tap((responseOfTheSecondApiCall: StrictHttpResponse<string>) => {
// Handle successfull avatar upload (change the "Logged user" object avatar to change it everywhere etc
console.log(response);
observer.next(true);
}),
catchError((err: any) => of(err))
)
.subscribe(); // Empty subscribe() call to trigger the http request. Not needed if you get the result somewhere else (eg if your method return an observable that you want to handle the result somewhere else)
flatMap() is the same as mergeMap. Change it as you wish, there's a lot of option like map or switchMap that you should learn about since they are useful.
Basically, the pipe allow you to chain functions, and if there is an error, then the catchError is triggered.
Tip: Note that what is in the pipe is executed BEFORE the result of your api call. So if you want to do something with your result before to get it, then think about rxjs:
service
getUser(id: string) {
return this._http.get<any>(url).pipe(
map(result => result.email), // Return only the email
);
}
component:
ngUnsubscribe = new Subject();
ngOnInit() {
this._userService.getUser(1)
.pipe(takeUntil(this.ngUnsubscribe)) // Don't forget to unsubscribe !
.subscribe(email => console.log('email = ', email))
}
ngOnDestroy() {
this.ngUnsubscribe.unsubscribe();
// or
// this.ngUnsubscribe.next();
// this.ngUnsubscribe.complete();
}

I am confused about this simple component in React with TypeScript

I'm new in TypeScript and in this simple example I've created a component to fetch data from a fake API and show them in a map iteration:
import React, { FC, useState } from 'react';
const GetData = (): Promise<[]> => {
return fetch('https://randomuser.me/api/?results=5')
.then((response: any) => response.json())
.then((data: any) => data.results);
}
const App: FC<{}> = () => {
const [users, setUsers] = useState<[]>([]);
const getUsers = (): void => {
GetData().then((data: []) => setUsers(data));
}
return (
<div>
{
<ul>
{
users.map((item: any, index: number) => (
<li key={`user-${index}`}>{`${item.name.first} {item.name.last}`}</li>
))
}
</ul>
}
<button onClick={getUsers}>Load</button>
</div>
)
}
export default App;
This code works well ! but I'm not sure about true of my codes... In this example I've :any type for functions input (in promise chain eg.) so, is it correct to this example? am I able to use many of any type in arguments when I'll have a nested array in output?
and the second one, I didn't add type with : for GetData, but that is a const and I should declare them something like this:
const age: number = 40;
but I didn't get any error?
Thank you
When you are using Promise generic to define one, you shouldn't add type for callback functions into chain. for example write your chain like this:
const GetData = (): Promise<User[]> => {
return fetch('https://randomuser.me/api/?results=5')
.then((response) => response.json())
.then((data) => data.results);
}
And for second one question about const age: number = 40;
when you define a const and assign function to that, means you are create a function and TypeScript considers it will be a function and not a primitive value, therefore, you shouldn't add type for const after that name.
but when you are set something like this: const age: number = 40;
the above code means, your are defining a primitive value and should set type after name.
When you use any you miss out on most of typescript's error-finding abilities. You are basically saying, "trust me, this is fine" since any is assignable to any type.
There are a few places that we can improve your types and also a few types that are inaccurate. Promise<[]> actually means that this is a promise of the empty array []. You want it to return an array of users: Promise<User[]>.
You might have this User object typed somewhere else in your app already. Here I am just defining the properties that we need for this component.
interface User {
name: {
first: string;
last: string;
}
}
Calling fetch returns Promise<Response> where Response is a built-in object type that supports the .json() method. So instead of (response: any) you are better off just doing (response) and using the inferred type. That .json() method returns Promise<any>, so the inferred type for data is any. You can choose to assert at this point that data is an object with a property results containing an array of User objects, but you can also just stick with the inferred any and rely on your function's return type to assert that.
const GetData = (): Promise<User[]> => {
return fetch('https://randomuser.me/api/?results=5')
.then((response) => response.json())
.then((data) => data.results);
}
Now to the component. Our state is an array of User, not an empty array.
const [users, setUsers] = useState<User[]>([]);
Once you change that, you can remove the types from (data: []) and (item: any, index: number). You generally don't need to assert types in a callback when you have proper types higher up in the chain. When you call users.map, the item is already known to have type User because users is typed as User[].
Typescript Playground Link
Using any is a brute force approach that discards all the benefits of using TypeScript on the first place. It works, but isn’t recommended.
Given const age = 40, TypeScript knows that you are assigning a number. The expression on the right-hand side cannot be anything other than a number. It can infer the type from that so you don’t need to be explicit.

RxJS: Mapping object keys to observables

I have an app where questions are shown to users.
Drafts for the questions are loaded from a SharePoint list. Each draft contains a key which is used to load proper responses to the question from another SharePoint list. Here's how I currently implemented it:
interface QuestionDraft {
title: string;
responseKey: string;
}
interface Question {
title: string;
responses: string[];
}
const drafts: QuestionDraft[] = [];
const questions: Question[] = [];
// stub
private getDrafts(): Observable<QuestionDraft> {
return from(drafts);
}
// stub
private getResponses(key: string): Observable<string> {
return of(key, key, key);
}
main(): void {
getDrafts().subscribe(
data => {
const res: string[] = [];
getResponses(data.responseKey).subscribe(
d => res.push(d),
error => console.error(error),
() => questions.push({
title: data.title,
responses: res
})
);
}, error => console.error(error),
() => console.log(questions)
);
}
This solution works fine, but I think the code in main() looks messy. Is there an easier way to do the same thing, for example using mergeMap or something similar?
You can use mergeMap to map to a new Observable and toArray to collect the emitted values in an array. Use catchError to handle errors in your streams and map to an alternative Observable on errors.
This code will work just like your code with the emitted questions array containing all questions up until getDrafts throws an error and exluding questions for which getResponses threw an error.
getDrafts().pipe(
mergeMap(draft => getResponses(draft.responseKey).pipe(
toArray(),
map(responses => ({ title: draft.title, responses } as Question)),
catchError(error => { console.error(error); return EMPTY; })
)),
catchError(error => { console.error(error); return EMPTY; }),
toArray()
).subscribe(qs => { console.log(qs); questions = qs; })
Keep in mind that the questions in the final array will not necessarily be in the same order as the drafts coming in. The order depends on how fast a getResponses Observable completes for a specific draft. (This is the same behaviour as your current code)
To ensure that the questions will be in the same order as the drafts you can use concatMap instead of mergeMap. But this might slow down the overall execution of the task as the responses for the next draft will only be fetched after the responses for the previous draft completed.
You can try and use flatMap to make it cleaner.
RxJs Observables nested subscriptions?
this.getDrafts()
.flatMap(function(x){return functionReturningObservableOrPromise(x)})
.flatMap(...ad infinitum)
.subscribe(...final processing)
If you use RxJS version 6 you must use pipe() method and turned flatMap into mergeMap.
in rxjs 6, example of #emcee22 will be look:
this.getDrafts()
.pipe(
.mergeMap(function(x){return functionReturningObservableOrPromise(x)}),
.mergeMap(...ad infinitum)
).subscribe(...final processing)

DebounceTime emits all the events which was captred during the time

I need to write the async validator for the reactive form type in angular.
I have implemented it trough promise. But the issue is the validator triggers for each keystroke it strike the server for every keystroke.For implementing the debounce i have implemented the setTimeout for the promise but the issue i faced is it triggers for after the certain millisecon i have defined.
Finally I have implemented the Observable inside the promise to achive all debounceTime, But the issue i faced here is the debounceTime emits all the events.
For example: If I type 'Prem' from input field the following code triggers the server for four time as timeout works.
If any issue in implemetation of the async validator please clarify me.
//Latest code
static hasDuplicateEmail(formControl: FormControl) {
return new Promise((resolve, reject) => {
return new Observable(observer =>
observer.next(formControl.value)).pipe(
debounceTime(600),
distinctUntilChanged(),
switchMap((value) => {
//server side
return MotUtil.fetch('checkForRegisterEmail', {e: formControl.value});
})
).subscribe((res) => {
return (JSONUtil.isEmpty(res)) ? resolve(null) : resolve({duplicate: true});
});
});
}
The debounceTime should work as mentioned in the Docs.
You are trying to approach it in a difficult way. Validator takes argument - AbstractControl. AbstractControl has property - valueChanges which return stream of changes in your formControl. So here you add debouceTime and later do other operations and finaly return this stream back to FormControl:
hasDuplicateEmail(control: AbstractControl) {
return control.valueChanges.pipe(
debounceTime(600),
switchMap(e =>
this.http.get('checkForRegisterEmail', {e}).pipe(
map((res: any) => JSONUtil.isEmpty(res) ? null : { duplicate: true })
)
)
)
}
As you notice I use HttpClient as it is the way you make HTTP calls in Angular (it is designed to work on streams rather then Promises)
emailValidator(/** params that you need inside switchMap */): AsyncValidatorFn {
return (control: AbstractControl): Observable<ValidationErrors | null> => {
return of(control.value).pipe(
delay(500), //for me works with delay, debounceTime are not working with asyncValidator
filter(email=> !!email), //length or another logic there is not emails with less than 10 characters
distinctUntilChanged(),
switchMap(/** as you need */),
map(exist => (exist ? {duplicate: true} : null)),
catchError(() => of(null))
);
};
}

Return an empty Observable

The function more() is supposed to return an Observable from a get request
export class Collection {
public more = (): Observable<Response> => {
if (this.hasMore()) {
return this.fetch();
} else {
// return empty observable
}
};
private fetch = (): Observable<Response> => {
return this.http.get("some-url").map((res) => {
return res.json();
});
};
}
In this case I can only do a request if hasMore() is true, else I get an error on subscribe() function subscribe is not defined, how can I return an empty Observable?
this.collection.more().subscribe(
(res) => {
console.log(res);
}, (err) => {
console.log(err);
}
);
With the new syntax of RxJS 5.5+, this becomes as the following:
// RxJS 6
import { EMPTY, empty, of } from "rxjs";
// rxjs 5.5+ (<6)
import { empty } from "rxjs/observable/empty";
import { of } from "rxjs/observable/of";
empty(); // deprecated use EMPTY
EMPTY;
of({});
Just one thing to keep in mind, EMPTY completes the observable, so it won't trigger next in your stream, but only completes. So if you have, for instance, tap, they might not get trigger as you wish (see an example below).
Whereas of({}) creates an Observable and emits next with a value of {} and then it completes the Observable.
E.g.:
EMPTY.pipe(
tap(() => console.warn("i will not reach here, as i am complete"))
).subscribe();
of({}).pipe(
tap(() => console.warn("i will reach here and complete"))
).subscribe();
For typescript you can specify generic param of your empty observable like this:
import 'rxjs/add/observable/empty'
Observable.empty<Response>();
RxJS6 (without compatibility package installed)
There's now an EMPTY constant and an empty function.
import { Observable, empty, EMPTY, of } from 'rxjs';
//This is now deprecated
var delay = empty().pipe(delay(1000));
var delay2 = EMPTY.pipe(delay(1000));
Observable.empty() doesn't exist anymore.
Several ways to create an Empty Observable:
They just differ on how you are going to use it further (what events it will emit after: next, complete or do nothing) e.g.:
Observable.never() - emits no events and never ends.
Observable.empty() - emits only complete.
Observable.of({}) - emits both next and complete (Empty object literal passed as an example).
Use it on your exact needs)
In my case with Angular2 and rxjs, it worked with:
import {EmptyObservable} from 'rxjs/observable/EmptyObservable';
...
return new EmptyObservable();
...
Yes, there is am Empty operator
Rx.Observable.empty();
For typescript, you can use from:
Rx.Observable<Response>.from([])
Since all the answers are outdated, I will post the up to date answer here
In RXJS >= 6
import { EMPTY } from 'rxjs'
return EMPTY;
You can return Observable.of(empty_variable), for example
Observable.of('');
// or
Observable.of({});
// etc
Differents way to return empty observable :
Observable.from({});
Observable.of({});
EMPTY
https://www.learnrxjs.io/learn-rxjs/operators/creation/empty
Or you can try ignoreElements() as well
RxJS 6
you can use also from function like below:
return from<string>([""]);
after import:
import {from} from 'rxjs';
Came here with a similar question, the above didn't work for me in: "rxjs": "^6.0.0", in order to generate an observable that emits no data I needed to do:
import {Observable,empty} from 'rxjs';
class ActivatedRouteStub {
params: Observable<any> = empty();
}
Try this
export class Collection{
public more (): Observable<Response> {
if (this.hasMore()) {
return this.fetch();
}
else{
return this.returnEmpty();
}
}
public returnEmpty(): any {
let subscription = source.subscribe(
function (x) {
console.log('Next: %s', x);
},
function (err) {
console.log('Error: %s', err);
},
function () {
console.log('Completed');
});
}
}
let source = Observable.empty();
You can return the empty observable with all different ways but challenge is to to return it with the expected type -
Here is the way to create a empty observable with type -
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(this.setHeaders(req))
.pipe(
catchError((error: HttpErrorResponse) => {
// you write your logic and return empty response if required
return new Observable<HttpEvent<any>>();
}));
}
there is another: EMPTY const
Replaced with the EMPTY constant or scheduled (e.g. scheduled([], scheduler)). Will be removed in v8. (got this form phpstorm hint)

Categories