Reputation: 1950
I try to refactor the code below with two subscriptions (one in the method and one in the service) and I don't find a way to do it.
I am using akita to handle my states. So it is recommended to use subscriptions in services as mentioned in the documentation.
This code is to handle the case of a denormalization: my language field of the User collection in the Service collection with MongoDB.
Here is the models I use:
user model
export interface UserInfo {
[...]
services: ID[];
}
export interface User {
_id: ID;
[...]
userInfo: Partial<UserInfo>;
}
service model
export interface ServiceInfo {
[...]
userFamilyName: string;
userGivenName: string;
userLanguages: Language[];
}
export interface Service {
_id: ID;
[...]
serviceInfo: Partial<ServiceInfo>;
}
In my method
private addLanguage(languageForm: FormGroup) {
[...]
this.usersService.addLanguage(user).pipe(
takeUntil(this.ngUnsubscribe)
).subscribe(
(entity: User) => {
if (entity.userInfo.services) {
entity.userInfo.services.forEach(serviceId => {
const service = createService({
_id: serviceId,
serviceInfo: {
userLanguages: [language]
}
});
this.servicesService.addLanguage(service); <--- have also a subscribe in its method
})
}
languageForm.reset();
this.onNavigateBack();
}
);
}
In the service
addLanguage(service: Service) {
const url = `${this.servicesUrl}/${service._id}/languages`;
const accessToken = this.authQuery.getSnapshot().accessToken;
this.http.post<Service>(url, service, {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': accessToken
})
}).subscribe(
(entity: Service) => {
if (this.servicesQuery.hasEntity(service._id)) {
this.servicesStore.update(service._id, entity);
}
}
);
}
What I tried so far:
this.usersService.addLanguage(user).pipe(
filter((entity: User) => entity.userInfo.services !== undefined),
switchMap((entity: User) => {
entity.userInfo.services.forEach(serviceId => {
const service = createService({
_id: serviceId,
serviceInfo: {
userLanguages: [language]
}
});
return this.servicesService.addLanguage(service);
})
}
);
But I am stuck because of the entity.userInfo.services.forEach
.
Is there a way to do it?
Thanks for your help.
Upvotes: 2
Views: 81
Reputation: 8478
Whenever you are seeing a subscribe inside your service, probably there is something wrong with the code. You will need to use .tap()
to do some side effect manipulation. Never subscribe in a service, let the component handles the subscription.
In service:
addLanguage(service: Service) {
const url = `${this.servicesUrl}/${service._id}/languages`;
const accessToken = this.authQuery.getSnapshot().accessToken;
this.http.post<Service>(url, service, {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': accessToken
})
})
.pipe(
tap((entity: Service) => {
if (this.servicesQuery.hasEntity(service._id)) {
this.servicesStore.update(service._id, entity);
}
})
);
}
In your component though, you can easily achieve this using a filter()
and a switchMap()
from rxjs
. The trick for the forEach
, is that you use .map()
from the native Javascript array function. After you have created such array of Observables, you can use forkJoin()
to combine them and have them fired in parallel:
In component:
private addLanguage(languageForm: FormGroup) {
this.usersService.addLanguage(user).pipe(
takeUntil(this.ngUnsubscribe),
//filter away the conditions
filter((entity: User) => entity.userInfo.services),
switchMap((entity: User) => {
//use .map() to create an array of services.
let serviceObs$ = entity.UserInfo.services.map(serviceId =>
this.servicesService.addLanguage(createService({
_id: serviceId,
serviceInfo: {
userLanguages: [language]
}
})));
//use forkJoin to combine all of them
return forkJoin(serviceObs$);
})
).subscribe(() => {
languageForm.reset();
this.onNavigateBack();
}
);
}
Upvotes: 2