Promise inside Observable in Angular 7+ - javascript

I have a problem that I can't solve.
In the ngOnInit event I observe the url parameter. This parameter corresponds to a folder in firebase-storage. That way when loading I get a list of folders and/or files inside that folder that is being informed and storing it inside listReferences variable which is of type Reference[].
Here is the code:
ngOnInit() {
this.route.params
.subscribe(params => {
this.getFiles(params.ref).subscribe(
(listReferences) => {
this.listReferences = listReferences;
}
);
}
);
}
getFiles(folder: string) {
return this.storage.ref('/' + folder).listAll()
.pipe(
map((data) => {
return data.items;
})
);
}
It turns out that for each item in the listReferences array I need to access the getDownloadUrl() or getMetadata() method which are promising and I am unable to retrieve the values for each item in the array. How should I proceed in this case? How best to do this?
Basically I am following the information contained in the reference guide.
https://firebase.google.com/docs/reference/js/firebase.storage.Reference

How about using ForkJoin as follows:
ngOnInit() {
this.route.params
.pipe(
mergeMap(x => this.getFiles(x.ref)),
mergeMap((listReferences: { getDownloadUrl: () => Promise<string> }[]) => {
return forkJoin(listReferences.map(x => x.getDownloadUrl()))
})
)
.subscribe(x => this.urls = x)
}

Related

How to make sequential service call on success of first service response in Angular

I need to make multiple service call in angular one after other. need to pass the first
service call respose as input to another service.
Here is my component:
Demo(): any {
if (fileToUpload) {
this._voiceboxService.upload(fileToUpload)
.subscribe((res: any) => {
this.text=res.prediction
console.log(res);
});
}
else
console.log("FileToUpload was null or undefined.");
}
}
Here is my Service: i need to call all three service on success of one service and need to
pass first service resposnse as input for next service
upload(fileToUpload: any) {
let input = new FormData();
input.append("file", fileToUpload);
return this.http.post<any>('https://localhost:5001/', input)
language(data: any) {
return this.http.post<any>('https://localhost:5002', data)
}
getDetails(data: any) {
return this.http.post<any>('https://localhost:5003', data)
}
Use mergeMap.
I assume you want to do this in your component:
this._voiceboxService.upload(fileToUpload).pipe(mergeMap(upload =>
this._voiceboxService.language(upload)
.pipe(mergeMap(language => this._voiceboxService.getDetails(language))
))).subscribe((res: any) => {
this.text=res.prediction
console.log(res);
});
You can use map in the end organize your final value result.
You could use any of the RxJS higher order mapping operators like switchMap to map from one observable to another. You could find differences between different mapping operators here.
Service
upload(fileToUpload: any) {
let input = new FormData();
input.append("file", fileToUpload);
return this.http.post<any>('https://localhost:5001/', input).pipe(
switchMap(res => this.language(res)), // <-- `res` = response from previous request
switchMap(res => this.getDetails(res)) // <-- `res` = response from `this.language()`
);
}
language(data: any) {
return this.http.post<any>('https://localhost:5002', data)
}
getDetails(data: any) {
return this.http.post<any>('https://localhost:5003', data)
}
Component
Demo(): any {
if (fileToUpload) {
this._voiceboxService.upload(fileToUpload).subscribe({
next: (res: any) => { // <-- `res` = response from `getDetails()`
this.text = res.prediction
console.log(res);
},
error: (error: any) => {
// handle errors
}
});
} else {
console.log("FileToUpload was null or undefined.");
}
}

RxJS - Keep a cache of the list and update the existing ones

I have a WebSocket connection, that does 2 things:
Sends on the first 'DATA' event the full List(i.e: 5 Items)
On each next 'DATA' it sends information about the updated ones only.
I want to take that stream, process it, keep a cache of the items and do the following:
Keep the existing list.
If a new event arrives, and is in the list, update that based on an id(This should be generic enough).
If it doesn't exist, add it to the list.
This is what I have done so far. Which isn't much. I am appending the items every time. Any help would be appreciated:
function createCachedList$<T extends WSMessage<T>>(observable$: Observable<T>) {
const INITIAL_STATE: any[] = [];
const [fromDataPackets$, fromNonDataPackets$] = partition(
observable$,
(value) => value.type === WSMessageType.DATA
);
const pickDataPacket = fromDataPackets$.pipe(
map((value: any) => value?.data),
scan((prevState, currState: any[]) => {
const nextState = R.uniq([...prevState, ...currState]);
return [...prevState, ...nextState];
}, INITIAL_STATE),
tap((data: any) => console.log('Data:', data)),
map((data: any) => ({ type: WSMessageType.DATA, data }))
);
return merge(pickDataPacket, fromNonDataPackets$);
}
export default createCachedList$;
Your code seems OK. scan is the operator I would use.
Probably you need to elaborate a bit the logic within scan. Something like this could help
scan((prevState, currState: any[]) => {
currState.forEach(m => {
const item = prevState.find(s => s.id === m.id);
if (item) {
Object.assign(item, m)
} else {
prevState.push(m)
}
});
return prevState;
}, INITIAL_STATE),

Angular wait until subscribe is done and give values to other function

i have this following function
file: subcategory.service.ts
getSubCategoriesById(inp_subCatid: String): Observable<any>{
this.getSubCategoriesList().snapshotChanges().pipe(
map(changes =>
changes.map(c =>
({ key: c.payload.key, ...c.payload.val() })
)
)
).subscribe(subCategories => {
subCategories.filter(function (subCat) {
return subCat.id == inp_subCatid;
});
});
and i´m calling the top function in the following file
file: subcategory.page.ts
this.SubCategoryService.getSubCategoriesById(subCatid).subscribe((subCategories: any) => {
this.subCat = subCategories ;
})
the problem what i got is i´m getting following error message:
ERROR TypeError: "this.SubCategoryService.getSubCategorysById(...) is undefined"
i want to get the data when there are loaded from the file "subcategory.service.ts"
hope someone can help me.
Your method should be like this:
getSubCategories(inp_subCatid: string): Observable<any> {
return this.getSubCategoriesList().snapshotChanges().pipe(
map(changes => changes.map(c =>
({ key: c.payload.key, ...c.payload.val() })
).filter((subCat) => subCat.id === inp_subCatid)
));
}
Then you will be able to use like this:
this.subCategoryService.getSubCategories(subCatid)
.subscribe(subCategories => this.subCat = subCategories);
If I'm interpreting correclty your methods, it seems to me that you're using firebase... if so, after you call this.yourService.getSubCategories(subCatid) for the first time, your subscription will remain active so that your subcategories will be updated for every change on the database, even if you change subCatid, the previous database query will be alive. To avoid it, I suggest that you take just one emission of snapshotChanges():
getSubCategories(inp_subCatid: string): Observable<any> {
return this.getSubCategoriesList().snapshotChanges().pipe(
// finish the subscription after receiving the first value
take(1),
map(changes => changes.map(c =>
({ key: c.payload.key, ...c.payload.val() })
).filter((subCat) => subCat.id === inp_subCatid)
));
}
Thanks a lot
what if i want to filter for a specific data ?? like for "id"
getSubCategoriesbyId(inp_subCatid): Observable<any>{
this.getSubCategoriesList().snapshotChanges().pipe(
map(changes =>
changes.map(c =>
({ key: c.payload.key, ...c.payload.val() })
)
)
).subscribe(subCategories => {
subCategories.filter(function (subCat) {
return subCat.id == inp_subCatid;
});
});
}
and then to get the filtered data back
this.yourService.getSubCategoriesbyId(subCatid)
.subscribe(subCategories => console.log(subCategories));

Is there a way to use a observable returning function for each element of another observable array?

I get an Observable<Group[]> from my Firebase collection.
In this Group class is an id which I wanna use to retrieve another dataset array from Firebase, which would be messages for each unique group Observable<Message[]>.(each group has its own chat: Message[])
And it want to return an observable which hold an array of a new Type:
return { ...group, messages: Message[] } as GroupWithMessages
the final goal should be Observable<GroupWithMessages[]>
getGroupWithChat(): Observable<GroupWithMessages[]> {
const groupColl = this.getGroups(); // Observable<Group[]>
const messages = groupColl.pipe(
map(groups => {
return groups.map(meet => {
const messages = this.getMessagesFor(group.uid);
return { messages:messages, ...group} as GroupWithMessages
});
})
);
return messages;
}
}
and here the Message function
getMessagesFor(id: string): Observable<Message[]> {
return this.afs.collection<Message>(`meets/${id} /messages`).valueChanges();
}
sadly that doesnt work because when i create the new Obj I cannot bind messages:messages because messages ist vom typ Observable<Message[]>
I hope that cleares things
UPDATE:
my main problem now comes down to this:
getGroupsWithMessages() {
this.getJoinedGroups()
.pipe(
mergeMap(groups =>
from(groups).pipe(
mergeMap(group => {
return this.getMessagesFor(group.uid).pipe(
map(messages => {
return { ...group, messages } as GroupIdMess;
})
);
}),
tap(x => console.log('reaching here: ', x)),
toArray(),
tap(x => console.log('not reaching here = completed: ', x))
)
),
tap(x => console.log('not reaching here: ', x))
)
.subscribe(x => console.log('not reaching here: ', x));
}
when i call that function my console.log is as follows:
Not sure if I follow what you're doing here but the logic look like you'd want:
getGroupWithChat() {
return this.getGroups.pipe(map(groups=> {
return groups.map(group => this.getMessagesFor(group.uid));
})).subscribe(); // trigger "hot" observable
}
Let me know if I can help further after you clarify.
UPDATE:
So it looks like you need to get the UID of the group before making the call to get the GroupMessages[]?
get Group: Observable
call getMessagesFor(Group.uid)
this example gets groups result$ then
concatMap uses groups result$ to make the messages query
this.getGroups().pipe(
concatMap((group: Group) => this.getMessagesFor(group.uid))
).subscribe((messages: GroupWithMessages[]) => {
console.log(messages);
});
You may still want to map them together but it seems like you know how to do that. concatMap waits for the first to finish, then makes the second call which you need.
Is this closer?
Use forkJoin to wait for messages to be received for all groups. Then map the result of forkJoin to an array of GroupWithMessages like this -
getGroupWithChat(): Observable<GroupWithMessages[]> {
return this.getGroups()
.pipe(
switchMap(groups => {
const messagesForAllGroups$ = groups.map(group => this.getMessagesFor(group.uid));
return forkJoin(messagesForAllGroups$)
.pipe(
map(joined => {
//joined has response like -
//[messagesArrayForGroup0, messagesArrayForGroup1, messagesArrayForGroup2....];
const messagesByGroup = Array<GroupWithMessages>();
groups.forEach((group, index) => {
//assuming that GroupWithMessages has group and messages properties.
const gm = new GroupWithMessages();
gm.group = group;
gm.messages = joined[index];
messagesByGroup.push(gm);
});
return messagesByGroup;
})
)
})
)
}
I usually do that by splitting Observable<any[]> to Observable<any> and then mergeMap the results to inner Observable.
Something like this should work:
getMessagesFor(id: string): Observable<number> {
return of(1);
}
getGroups(): Observable<string[]> {
return of(["1", "2"]);
}
getGroupWithChat() {
this.getGroups().pipe(
mergeMap(groups => from(groups)), // Split the stream into individual group elements instead of an array
mergeMap(group => {
return this.getMessagesFor(group).pipe(
map(messages => {
return Object.assign(group, messages);
})
);
})
);
}
Edit:
Consider BehaviorSubject. It doesn't complete at all:
const behSub: BehaviorSubject<number[]> = new BehaviorSubject([1, 2, 3]);
setTimeout(() => {
behSub.next([4, 5, 6]);
}, 5000);
behSub
.pipe(
mergeMap(arr =>
from(arr).pipe(
tap(), // Do something with individual items, like mergeMap to messages
toArray() // Go back to array
)
)
)
.subscribe(console.log, null, () => {
console.log('Complete');
});

Setting page title dynamically in Angular

I have recently upgraded to Angular 6 and rxjs 6, since the upgrade, the following code to set the page title dynamically is no longer working
ngOnInit(): void {
this.router.events
.filter((event) => event instanceof NavigationEnd)
.map(() => this.activatedRoute)
.map((route) => {
while (route.firstChild) {
route = route.firstChild;
};
return route;
})
.filter((route) => route.outlet === 'primary')
.mergeMap((route) => route.data)
.subscribe((event) => this.titleService.setTitle(event['title']));
};
This gives me an error
this.router.events.filter is not a function
I tried wrapping the filter in a pipe like
this.router.events
.pipe(filter((event) => event instanceof NavigationEnd))
But I get the error
this.router.events.pipe(...).map is not a function
I have imported the filter like
import { filter, mergeMap } from 'rxjs/operators';
What am I missing here?
This is the correct way to use pipeable/lettables.
this.router.events.pipe(
filter(event => event instanceof NavigationEnd),
map(() => this.activatedRoute),
map((route) => {
while (route.firstChild) {
route = route.firstChild;
};
return route;
}),
filter((route) => route.outlet === 'primary'),
mergeMap((route) => route.data),
).subscribe((event) => this.titleService.setTitle(event['title']));
In RxJs 6 all the operators are pipeable, which means they should be used inside pipe method call. More info about that here.
So the code that you have should become something like:
this.router.events.pipe(
filter((event) => event instanceof NavigationEnd),
map(() => this.activatedRoute),
map((route) => {
while (route.firstChild) {
route = route.firstChild;
};
return route;
}),
filter((route) => route.outlet === 'primary'),
mergeMap((route) => route.data)
).subscribe((event) => this.titleService.setTitle(event['title']));
If you have a larger app I suggest you have a look at the rxjs-tslint project as it will allow you to update automatically the code.

Categories