Reputation: 3198
So I have a method that relies on the data from two observables and the only way I have found to get it to work is by nesting the subscriptions of the observables and then calling the method inside the second subscribe method. This seems bad and there has to be a better way of accomplishing this can I get some help on a better way to do this?
Here is the controller:
meta$: Subscription;
meta: any;
data$: Subscription;
data: any;
ngOnInit() {
this.route.params.subscribe(params => this.id = params['id']);
this.getPageInfo();
}
private getPageInfo(): void {
this.meta$ = this.api.get(`${Endpoints.LOCATIONS}all/metadata`).subscribe(m => {
this.meta = m;
this.data$ = this.api.get(`${Endpoints.LOCATIONS}/${this.id}`).subscribe(d => {
this.data = d;
this.setSelectedAttributes(); // <-- relies on this.meta and this.data
}, err => console.error(err));
}, err => console.error(err));
}
setSelectedAttributes(): void {
const viewableAttributes = this.meta.columns.filter(c => !c.hidden && c.type === 'String');
const selectedAttributes: Array<any> = [];
for (const attr of viewableAttributes) {
if (this.data[attr.field]) {
selectedAttributes.push({name: attr.title, value: this.data[attr.field]});
}
}
this.selectedAttributes = selectedAttributes;
}
ngOnDestroy() {
this.data$.unsubscribe();
this.meta$.unsubscribe();
}
Upvotes: 0
Views: 767
Reputation: 9124
Without any subscription you could do it like this:
id$: Observable<number> = this.route.params.pipe(
map(params => +params.id),
);
data$: Observable<Data> = this.id$.pipe(
switchMap(id => this.api.get(`${Endpoints.LOCATIONS}/${this.id}`)),
);
export interface MetaData {
columns: Atribute[];
}
export interface Attribute {
hidden: boolean;
type: string;
title: string;
field: string;
}
viewableAttributes$: Observable<Attribute[]> = this.api.get<MetaData>(`${Endpoints.LOCATIONS}all/metadata`).pipe(
map(meta => meta.columns),
filter(column => !column.hidden && c.type === 'String'),
);
export interface AttributeSelection {
name: string;
value: string;
}
selectedAttributes$: Observable<AttributeSelection[]> = forkJoin([this.data$, this.viewableAttributes$]).pipe(
map(([data, viewableAttributes]) => viewableAttributes.map(attr => ({ name: attr.title, value: data[attr.field]})).filter(attr => attr.value)),
);
You then only need to use async pipe on selectedAttributes$
Upvotes: 0
Reputation: 11345
Looks like those two api call can happen in parallel, use forkJoin()
and tap()
for side effect -> instance property assignment
private getPageInfo(): void {
forkJoin(this.api.get(`${Endpoints.LOCATIONS}all/metadata`),this.api.get(`${Endpoints.LOCATIONS}/${this.id}`)).pipe(tap(([m,d])=>{
this.data$=d
this.meta$=m
this.setSelectedAttributes()
}))
}
Upvotes: 0
Reputation: 96891
It looks like you could use just concatMap
and then map the second response to contain also the response from the first response and maybe you won't even need to use any properties:
this.api.get(`${Endpoints.LOCATIONS}all/metadata`).pipe(
concatMap(response1 => this.api.get(`${Endpoints.LOCATIONS}/${this.id}`)).pipe(
map(response2 => [response1, response2])
)
).subscribe(([response1, response2]) => {
// Whatever here. You can set `this.meta = response1` etc...
});
Upvotes: 2
Reputation: 5181
You can use flatMap()
. This is the same as mergeMap()
which is used to prevent nested subscriptions:
this.meta$ = this.api.get(...).map(
flatMap((m) => {
this.meta = m;
...
return this.api.get(...)
}),
// You could also make another request if you need
// flatMap((m) => {
// ...
// return this.api.get(...)
// }),
).subscribe((d) => {
this.data = d;
});
For more information: https://www.learnrxjs.io/operators/transformation/mergemap.html
Another tip:
If you want to destory everything in 1 variable use takeUntil()
bound to an attribute to destory:
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
destroy$ = new Subject<boolean>();
request() {
return this.apiService.get(...).pipe(
takeUntil(this.destroy$)
)
}
ngOnDestroy(): void {
this.destroy$.next(true);
}
}
Upvotes: -1