I want to export a file containing a list of data when the user clicks a button. I have an observable (user$) which contains the user's identity. If the user is not authorised to get this data, they will be redirected to another page. If the user is indeed authorised, then the data is retrieved from the back-end and exported into a spreasheet (this part doesn't matter, I know how to do it). The title of the file depends on the identity of the user.
I am stuck at:
onClick(){
this.user$.pipe(
tap((user: User) => {
if (!user.isAuthorised) {
this.router.navigate(["/someOtherPage"]);
}
}),
switchMap((user: User) => {
let myData$: Observable<Array<Data>> =
this.myDataService.getDataByUserName(user.name);
this.exportService.exportFile(myData$, user.name);
})
);
}
The exportFile(Array, string) method needs the data to be exported as input, and it needs the name of the user to form the title of the spreasheet. How do I combine all these observables in a clean and efficient way?
Picci is correct that you should return EMPTY so that observable is completed. Also, in 99% of cases, I agree that you should only perform side effects inside of tap
operator, so that it's clear a side effect is happening.
However, in this case the code is somewhat simple, so I think you can get away with the router navigation within a guard clause that returns EMPTY
. As for getting the data
and the user
in the same scope for you call to exportService.exportFile()
, you can simply add a .pipe()
onto your first call to myDataService.getDataByUserName
:
onClick() {
this.user$.pipe(
switchMap(user => {
if (!user.isAuthorised) {
this.router.navigate(["/someOtherPage"]);
return EMPTY;
}
return this.myDataService.getDataByUserName(user.name).pipe(
switchMap(data => this.exportService.exportFile(data, user.name))
);
})
).subscribe();
}
It's sometimes handy to pass data from the view, to your controller methods. If the view happens to have access to the current user, you could pass to your onClick()
method. This would simplify things a bit:
onClick(user: User) {
if(!user.isAuthorised) {
this.router.navigate(["/someOtherPage"]);
return;
}
this.myDataService.getDataByUserName(user.name).pipe(
switchMap(data => this.exportService.exportFile(data, user.name))
).subscribe();
}
Here we don't need to subscribe to user$
to get the user, since it's passed it. This means we don't even involve observables at all when the user is not authorized. In the case they are authorized, we only need a single switchMap
since we only use 2 observables, instead of 3.
One last alternative, assuming the view model has the current user, is to put the conditional in the template, and break up your onClick
method into to smaller methods: downloadFile
and goToOtherPage
:
goToOtherPage() {
this.router.navigate(["/someOtherPage"]);
}
downloadFile(user: User) {
this.myDataService.getDataByUserName(user.name).pipe(
switchMap(data => this.exportService.exportFile(data, user.name))
).subscribe();
}
<button (click)="user.isAuthorised ? downloadFile(user) : goToOtherPage()">
Download Data
</button>