How to handle different kind of data getting fron an observable? - javascript

I am trying to use switchMap operator. But it should call different http requests based upon the params. So for different http requests the data from subscribe will also be deifferent. How to properly handle that data. Right now i am getting typescript error..My code..
ngOnInit(){
this.route.params.pipe(switchMap(params => {
if (params === 'friends') {
return this.http.get('some url to get friendList')
} else if (params === 'chats') {
return this.http.get('some url to get chats')
}
})).subscribe((result: {message: string, friendList: []} | {message: string, chats: []}) => {
console.log(result)
})
}
Here basically i am getting different structured data for calling two different api based upon condition. So i am trying to work with the result which i am getting from subscribe like..
.subscribe((result: {message: string, friendList: []} | {message: string, chats: []}) => {
if(result.friendList) {
// do something
} else if (result.chats) {
//do something
}
})
Here i am getting typescript errors on using like result.friendList or result.chats . So how should i work with the result?

You can change the type of the result to any and then the conditions will work properly and the typescript will pass without errors.
.subscribe((result: any) => {
if(result.friendList) {
// do something
} else if (result.chats) {
//do something
}
})
if you want to specify the type you can make it with that:
{message: string, friendList: [] , chats: []}
and when one of the arrays is empty you can check the lengths and reach the condition

Related

Can't get data from .pipe(map())

Got some problems with observable.
I have a function, with one returns me an Observable.
public getData(userId) {
const data = this.execute({userId: userId});
return {event: "data.get", data: data}
}
private execute(input: SomeDto): Observable<SomeRefType[]> {
return this.databaseGateway.queryMany(DatabaseCommand.WebRecordGetbyparticipantid, {
parameters: {
prm_contextuserid: input.userId,
prm_filterparticipantids: null,
prm_filtertext: null
}
}).pipe(map(res => res));
}
Type what pipe(map) returns
What I'm got when trying to return or log data
Question: Why .pipe(map(res => res)) don't work? What am I doing wrong?
For sure, I can read data from .pipe(take(1)).subscribe(data => console.log(data)), but, how can I return data from construction like this?
Thanks everyone! Have a good day!
As said in the Rxjs documentation observable are lazy computation.
It means the as long as you don't subscribe to them they won't do anything. It exists two ways to trigger a subscription.
Either within a ts file using .susbcribe() or within a view when calling an endpoint.
If you're using nestjs it would be when calling the url defined within a #Controller('') with an http verb like #Get('path')
By convention you suffix observable variables with $: data$ = new Observable<any>().
At some point you'll have to convert the observable to a promise. Best to do it early and convert it using firstValueFrom immediately after the query.
Then convert your caller to an async method to use the returned value.
public async getData(userId): Promise<{ event: string, data: SomeRefType[] }> {
const data = await this.execute({userId: userId});
return { event: 'data.get', data };
}
private execute(input: SomeDto): Promise<SomeRefType[]> {
const res$ = this.databaseGateway.queryMany(DatabaseCommand.WebRecordGetbyparticipantid, {
parameters: {
prm_contextuserid: input.userId,
prm_filterparticipantids: null,
prm_filtertext: null
}
});
return firstValueFrom(res$);
}

how to conditionally subscribe to another observable on success of first observable?

I m tring to use concatmap and complete the observables sequentially when queryParams has id it should only call getbearer tokens or else it should navigate to error with securekey but in reality its going to error page with bearer-token
const result = this.activatedRoute.queryParams.pipe(
concatMap((params) => {
if (params.hasOwnProperty('id')) {
sessionStorage.setItem('secureKey', params.id);
return this.httpServiceService.getBearerTokens();
} else {
this.router.navigate(['error'], {
queryParams: { 'error-key': 'secure-key' },
});
}
})
);
result.subscribe(
(response: any) => {
if (response.access_token) {
sessionStorage.setItem('bearerToken', response.access_token);
this.router.navigate(['summary']);
} else {
this.router.navigate(['error'], {
queryParams: { 'error-key': 'bearer-token' },
});
}
},
(error) => {
this.router.navigate(['error'], {
queryParams: { 'error-key': 'bearer-token' },
});
}
);
I think concatMap is just asking for a race condition (I don't know how often and how easy the query parameters change, though). I would use switchMap instead, because I imagine that the moment we navigate from "?id=alice" to "?id=bob", the matter of Alice's token becomes immaterial.
Generally, conditionally subscribing to another stream should work just fine with switchMap and filter:
firstStream$.pipe(
filter(result => someCondition(result)),
switchMap(result => getAnotherStream(result))
).subscribe(//...
Oh, and by the way, your concatMap returns a stream only if (params.hasOwnProperty('id')). Does that even compile? Return an observable in the else branch as well, it could even be EMPTY, but probably should be throwError.

Angular subscribe within subscribe: data doesn't load at the same time within view

I know it is bad practice to call subscribe within subscribe but I don't know how to handle it differently with my special case.
The code as it is now works, but my problem is that if I update my website for example every second, parts of the table are loaded first and other parts are loaded afterwards (the content of the subscibe within my subscribe).
I have a service containing a function that returns an Observable of a list of files for different assets.
Within that function I request the filelist for each asset by calling another service and this service returns observables.
I then iterate over the elements of that list and build up my data structures to return them later on (AssetFilesTableItems).
Some files can be zip files and I want to get the contents of those files by subscribing to another service (extractZipService). To be able to get that correct data I need the name of the file which I got by requesting the filelist. I then add some data of the zip contents to my AssetFilesTableItems and return everything at the end.
The code of that function is as follows:
getAssetfilesData(assetIds: Array<string>, filter: RegExp, showConfig: boolean): Observable<AssetFilesTableItem[][]> {
const data = assetIds.map(assetId => {
// for each assetId
return this.fileService.getFileList(assetId)
.pipe(
map((datasets: any) => {
const result: AssetFilesTableItem[] = [];
// iterate over each element
datasets.forEach((element: AssetFilesTableItem) => {
// apply regex filter to filename
if (filter.test(element.name)) {
this.logger.debug(`Filter ${filter} matches for element: ${element.name}`);
// build up AssetFilesTableItem
const assetFilesItem: AssetFilesTableItem = {
name: element.name,
type: element.type,
asset: assetId
};
// save all keys of AssetFilesTableItem
const assetFilesItemKeys = Object.keys(assetFilesItem);
// if file is of type ZIP, extract 'config.json' from it if available
if (showConfig && element.type.includes('zip')) {
this.extractZipService.getJSONfromZip(assetId, element.name, 'config.json')
.subscribe((configJson: any) => {
const jsonContent = JSON.parse(configJson);
const entries = Object.entries(jsonContent);
entries.forEach((entry: any) => {
const key = entry[0];
const value = entry[1];
// only add new keys to AssetFilesTableItem
if (!assetFilesItemKeys.includes(key)) {
assetFilesItem[key] = value;
} else {
this.logger.error(`Key '${key}' of config.json is already in use and will not be displayed.`);
}
});
});
}
result.push(assetFilesItem);
}
});
return result;
}));
});
// return combined result of each assetId request
return forkJoin(data);
}
}
I update my table using the following code within my component:
getValuesPeriodically(updateInterval: number) {
this.pollingSubscription = interval(updateInterval)
.subscribe(() => {
this.getAssetfilesFromService();
}
);
}
getAssetfilesFromService() {
this.assetfilesService.getAssetfilesData(this.assetIds, this.filterRegEx, this.showConfig)
.subscribe((assetFilesTables: any) => {
this.assetFilesData = [].concat.apply([], assetFilesTables);
});
}
Edit: I tried ForkJoin, but as far as I understandit is used for doing more requests in parallel. My extractZipService though depends on results that I get from my fileService. Also I have a forkJoin at the end already which should combine all of my fileList requests for different assets. I don't understand why my view is not loaded at once then.
EDIT: The problem seems to be the subscribe to the extractZipService within the forEach of my fileService subscribe. It seems to finish after the fileService Subscribe. I tried lots of things already, like SwitchMap, mergeMap and the solution suggested here, but no luck. I'm sure it's possible to make it work somehow but I'm running out of ideas. Any help would be appreciated!
You are calling this.extractZipService.getJSON inside a for loop. So this method gets called asynch and your function inside map is not waiting for the results. When result does come as your items are same which is in your view they get refreshed.
To solve this you need to return from this.extractZipService.getJSON and map the results which will give you a collections of results and then you do forkJoin on results ( Not sure why you need to forkjoin as there are just the objects and not API's which you need to call )
this.logger.debug(`ConfigJson found for file '${element.name}': ${configJson}`);
const jsonContent = JSON.parse(configJson);
const entries = Object.entries(jsonContent);
entries.forEach((entry: any) => {
// code
});
complete code should look on similar lines :-
getAssetfilesData(assetIds: Array<string>, filter: RegExp, showConfig: boolean): Observable<AssetFilesTableItem[][]> {
const data = assetIds.map(assetId => {
// for each assetId
return this.fileService.getFileList(assetId)
.pipe(
map((datasets: any) => {
// iterate over each element
datasets.forEach((element: AssetFilesTableItem) => {
return this.extractZipService.getJSONfromZip(assetId, element.name,
'config.json')
});
})).map((configJson: any) => {
// collect your results and return from here
// return result
});;
});
// return combined result of each assetId request
return forkJoin(data);
}
}
I have created a Stackblitz(https://stackblitz.com/edit/nested-subscribe-solution) which work along the same lines. You need to use concatMap and forkJoin for getting all the results.
Hope this helps.

Reading an specific key from JSON using Service

I am new to all JavaScript and angular. so I am struggling to do the following:
I have the following service, to read X from a local JSON file. The X is what user select from a dropdownbox:
getBySector(sector){
this.http.get('../../assets/Sectors.json').map(res => res).subscribe
(res => {
this.SectorsArray = res as ISectors[];
this.SectorsArray= res.find(item=>item.Sector===sector);
console.log(this.industrySectorsArray);
return this.industrySectorsArray;
},
(err: HttpErrorResponse) => {
console.log (err.message);
}
)
}
as an additional note, I have an interface which is ISector and matches the JSOn file.
The above code give me in Console the exact thing I expect. which is the following:
{IndustrySector: "Households", isSelected: "false", dataSubjectCategories: Array(2), dataTypeCategories: "Data", SubIndustries: Array(2)}
HOW can I return the above object/json output to ms TS file where I have called the service?
I have done the followings which are failed:
//even this failed:
console.log(this.readjsonService.getBySector(mission));
//
var output:Isector;
output=this.readjsonService.getBySector(mission)
// cannot subscribe to it as well
BTW, the find gives me the following error:
error TS2339: Property 'find' does not exist on type 'Object'.
UPDATE:
I solved the issue the code had with the help of people who replied. But the code got another error, although it works fine. t says:
"Cannot read property 'dataSubjectCategories' of undefined"
dataSubjectCategories is one of the key in the ISector: here is the ISector:
export interface ISectors {
IndustrySector: string;
isSelected: string;
dataSubjectCategories:string[];
dataTypeCategories:string[];
SubIndustries:[{
IndustrySector: string;
isSelected: string;
dataSubjectCategories:string[];
dataTypeCategories:string[];
SubIndustries:[{}]
}]
}
Please help to resolve this. Thanks a lot.
Normally, your service should just be returning the Observable and should not include the subscribe. Best practice suggests that you subscribe as close to the UI as possible.
My service methods look like this:
getProducts(): Observable<IProduct[]> {
return this.http.get<IProduct[]>(this.productUrl).pipe(
tap(data => console.log('All: ' + JSON.stringify(data))),
catchError(this.handleError)
);
}
getProduct(id: number): Observable<IProduct | undefined> {
return this.getProducts().pipe(
map((products: IProduct[]) => products.find(p => p.productId === id))
);
}
Using the generic parameter on the get: get<IProduct[]> helps Angular automatically map the returned response to an array of data, ISectors in your example.
The calling code in the component then looks like this:
getProduct(id: number) {
this.productService.getProduct(id).subscribe(
product => this.product = product,
error => this.errorMessage = <any>error);
}
Notice that here is where we subscribe. It then gets the product in the first function passed to the subscribe method.
You can find the complete example here: https://github.com/DeborahK/Angular-GettingStarted/tree/master/APM-Final

Why does Redux Promise return unresolved promise if more than type and payload options are specified?

I'm having tough time figuring out why this is happening, but essentially Redux Promise was working fine for me while returning something like:
return {
type: STORY_ACTIONS.STORY_SPOTIFY_REQUEST,
payload: request
}
However, I now need to pass another information with it like so
return {
order: 0, // New field
type: STORY_ACTIONS.STORY_SPOTIFY_REQUEST,
payload: request
}
This results in an unresolved promise instead of data. I tried renaming order to something like position or index... still nothing.
You should use the meta field, which is required by Redux Promise. Redux Promise uses Flux Standard Actions (FSA), which validates the action with this code:
import isPlainObject from 'lodash.isplainobject';
const validKeys = [
'type',
'payload',
'error',
'meta'
];
function isValidKey(key) {
return validKeys.indexOf(key) > -1;
}
export function isFSA(action) {
return (
isPlainObject(action) &&
typeof action.type !== 'undefined' &&
Object.keys(action).every(isValidKey)
);
}
export function isError(action) {
return action.error === true;
}
As you can see, there are only four reserved words for valid keys. So you should add the order property to the 'payload' or maybe 'meta' instead.

Categories

Resources