Reputation: 6726
Angular2 component displays some object. In addition to base object info, extra data needs to be loaded via a service which returns Observable and is quite slow.
Base part of the object info should be displayed immediately and the extra data once available.
Something like this:
SomeObjectComponent {
someObject: SomeObject;
extraData: ExtraData;
@Input()
set item(v: SomeObject) {
this.someObject = v;
backendService.getExtraData(v.id)
.subscribe(d => this.extraData = d);
}
}
// in the template
<some-object [item]="currentlySelected"></some-object>
When loading is slow and user navigates between different values of SomeObject
, asynchronous loading may load and assign wrong extraData
for the current item.
What would be the best way to solve this? What if multiple extra data items need to be loaded each time (and each appearing as soon as loaded)?
Upvotes: 0
Views: 146
Reputation: 16892
If I understood you correctly, basically what you are looking for is a way to cancel "old" rest-calls, as soon as a new item is selected -> the rxjs-way would be to utilize an .takeUntil(...)
SomeObjectComponent implements OnDestroy {
// to clean up any open subscriptions it is helpful to have a destroy-event
private destroyed$: Subject<any> = new Subject<any>();
// your vars
someObject: SomeObject;
extraData: ExtraData;
@Input()
set item(v: SomeObject) {
this.someObject = v;
this.loadExtraData$.next(v.id);
}
// a trigger to load extra data
private loadExtraData$: Subject<number> = new Subject<number>(); // type "number" is just an assumption, this can be whatever type "v.id" is
// the stream to load extra data
private extraData$: Observable<any> = this.loadExtraData$
.takeUntil(this.destroyed$) // when the component is destroyed this will ensure that any open subscription will be closed as well to prevent memory leaks
.switchMap(id => {
return this.backendService
.getExtraData(id)
.takeUntil(this.loadExtraData$); // will discard the rest-call the next time loadExtraData$ is triggered (if it is still running)
})
.do(extraData => this.extraData = extraData) // if you only need the data in the template, then don't use this line and use the "| async"-pipe instead
.share();
// this is called by the angular2-lifecycle, when the component is removed
ngOnDestroy() {
this.destroyed$.next();
}
}
If you just need the extra data in the template, then you might want to use the async
-pipe:
<span>Some extra data: {{(extraData$ | async)?.someAttribute}}</span>
As a small side-note and slightly off-topic: It is a better practice to not load the extra data within the component, but outside of the component and use an @Input()
for the extra-data, for a number of reasons:
@Input()
s and @Output()
sThat being said, without knowing your current architecture, this might bear bigger refactoring that might not be worth the time, but it is something to keep in mind for future applications.
Upvotes: 1