问题
I'm trying to return a UserDetail object which consists of a User and Results, User is retrieved via the accessToken of Account (all retrieved via individual async calls). At the moment I'm stuck at figuring out how I can return this UserDetail object when I navigate to the detailcomponent (I know it is not possible to literally return the object since the call is async, but I need to use this object in my component).
The code below gives me the following error:
Type 'Observable' is not assignable to type 'Observable'
I tried using pipes and maps since I've read this is how I should 'return' async calls to the component calling the function. The component is then supposed to handle the subscription, but I can't even get that far without creating errors.
@Injectable({
providedIn: 'root'
})
export class UserResolver implements Resolve<UserDetails> {
constructor(
private as: AccountService,
private rs: ResultService,
private auth: AuthService
) {}
resolve(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<UserDetails> {
return this.as.getAccount(route.params['id']).pipe(
map((acc: Account) => {
this.as.getUserDetails(acc.access_token).pipe(
map((user: User) => {
if (user != null) {
this.rs.getResults(this.auth.token).pipe(
map((res: Result[]) => {
const ud: UserDetails = {
user,
results: res.filter(x => x.userId === acc.uid)
};
return of(ud);
})
);
}
})
);
})
);
回答1:
Try using switchMap
instead of map
. map
simply transforms one value into another, whereas switchMap
allows you to switch to another observable.
Without knowing too much about what's going on, I think you want something like this:
let user: User;
let account: Account;
return this.as.getAccount(route.params['id']).pipe(
tap((acc: Account) => account = account),
switchMap((acc: Account) => this.as.getUserDetails(acc.access_token)),
// this filter will stop the next steps from running if the user is null
// it will also mean a value isn't emitted
// if you want to emit a null, you will need to modify the pipe
filter((u: User) => u !== null),
tap((u: User) => user = u),
switchMap((u: User) => this.rs.getResults(this.auth.token))
map((res: Result[]) => {
const ud: UserDetails = {
user,
results: res.filter(x => x.userId === account.uid)
};
return ud;
})
);
Notice how it's no longer indented like yours, but a sequence of pipe operators. Whenever you want to switch to a new observable, use switchMap
or concatMap
. We are only using map
to map the result of the last observable to the value that we want to return from the function.
Whenever state needs to be saved out of the middle of a pipe, I am using tap to assign it to a variable.
Also, you are making (possibly) redundant calls to this.rs.getResults(this.auth.token)
. The call doesn't change based on the what the id
param is, so you could just fetch once, and read from a cache on subsequent calls.
Edit: concatMap
is also an option. They are slightly different. Both are sufficient for the purposes of this answer - I'm not going to make assumptions about your use case.
回答2:
first of all you should avoid nested piping when possible so it is maintainable. Instead combine Observables.
But for your problem: just remove "of(ud)" and then just return ud in your last map.
Tipp for maintainability:
I see you have one call wich don't need do be resolved in order.
const userDetails$: Observable<Userdetails> = this.rs.getResults(this.auth.token)
.pipe(map((res: Result[]) => {
const ud: UserDetails = {
user,
results: res.filter(x => x.userId === acc.uid)
};
return ud;
});
const userCheck$: Observable<User> = this.as.getAccount(route.params['id'])
.pipe(map((acc: Account) =>
this.as.getUserDetails(acc.access_token));
// Only when both values are available those observables will be ziped.
// Only one subscription is needed on hte returned Obersvable
return zip(userDetails$,userCheck$).pipe(map(([userDetails,user])) => {
if (user != null) {
return userDetails
}else{
// should probably emit a error
}));
来源:https://stackoverflow.com/questions/60436143/rxjs-returning-nested-observables