I have a shared card component (shared-card.ts), which is to be reused by more than 10 components.
Shared card component has reload button and is displaying an error message based on *ngif.
shared-card.component.html
<ng-content></ng-content>
<div *ngIf="error">
<p>Data loading is errorred</p>
<button (click)="reload()">Reload</button>
</div>
<hr />
shared-card.ts
@Input() error: boolean;
@Output() reloadAgain = new EventEmitter();
reload() {
this.reloadAgain.emit();
}
Now, when I am adding this card in 10 components (containing different UI), I have to everytime repeat the whole code and write this.error$.next(true) whenever there is error loading data from API and then I have to pass it to shared component and then shared component checks and displays this error. Moreover, I have to check in each component.ts when this error$.next is true and then pass load() function for reloading.
Other components using the code:
<app-shared-card
(reloadAgain)="reload()"
[error]="error$ | async"
>
UI in between for content projection. Data from service will be used here
error$ = new BehaviorSubject<boolean>(false);
load() {
this.data$ = this.langService.getData();
this.langService
.getData()
.pipe(
catchError(() => {
this.error$.next(true);
return of();
})
)
.subscribe();
}
reload() {
this.load();
}
I want this whole code to be restructured a bit. I did my research and found interceptor could be helpful, but I couldn't in this case due to my project's limitations.
Another solution that I found was to use the abstract class (abstract-card.ts), but I got stuck with this class and don't know how to move forward and trigger retry action from it.
Any suggestions?
You can find Slackblitz here. shared-card.ts is the shared card & small, small1 and small2 are using it to show the problem. (Their UI will be different based on data from service. Currently it's just dummy UI) load() in all 3 components is loading data from the service
Note: Please ignore data loading twice in the load() method. I won't be able to make changes in the service itself, as the same service is used at many other places.
CodePudding user response:
For starters, you could write a helper function extracting errors from stream, sth like
getErrorsStream = (stream) => stream.pipe(
switchMap(() => EMPTY),
catchError(e => of(true))
);
(maybe place it in your MyDataService
?), and reuse it like this
this.data$ = this.langService.getData();
this.error$ = this.langService.getErrorsStream(this.data$);
But, more importantly, since all versions of SmallComponent
are identical except for the name of the used method, maybe you need only one of them, with relevant stream (be it getData()
, getData2()
, getData3()
) as input.
CodePudding user response:
I would say that in this case you should not separate data and error, e.g.:
interface LangResponse {
data?: any;
errorData?: any;
}
And then your component is simplified to:
<app-shared-card
[response]="response$ | async"
(reloadAgain)="reload()"
></app-shared-card>
load() {
this.response$ = this.langService.getResponse();
}
reload() {
this.load();
}
And you add method in service:
getResponse() {
return this.getData().pipe(map(data => ({data})), catchError(errorData => ({errorData}));
}
Note: that will work a bit differently e.g. if 1st request succeeds and second fails your current code will display both data and error. Not sure if it is intended but if it is you can fix it adding some code in shared component.